summaryrefslogtreecommitdiff
path: root/Source/WebCore/svg/SVGToOTFFontConversion.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/svg/SVGToOTFFontConversion.cpp')
-rw-r--r--Source/WebCore/svg/SVGToOTFFontConversion.cpp1599
1 files changed, 1599 insertions, 0 deletions
diff --git a/Source/WebCore/svg/SVGToOTFFontConversion.cpp b/Source/WebCore/svg/SVGToOTFFontConversion.cpp
new file mode 100644
index 000000000..c708c5af4
--- /dev/null
+++ b/Source/WebCore/svg/SVGToOTFFontConversion.cpp
@@ -0,0 +1,1599 @@
+/*
+ * Copyright (C) 2014-2016 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 "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"
+
+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, std::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, std::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; // SVG 1.1: "It is recommended that glyph names be unique within a font."
+ 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;
+ char 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)
+{
+ // Braindead scheme: One segment for each character
+ ASSERT(m_glyphs.size() < 0xFFFF);
+ auto subtableLocation = m_result.size();
+ append32(12 << 16); // Format 12
+ append32(0); // Placeholder for byte length
+ append32(0); // Language independent
+ append32(0); // Placeholder for nGroups
+ for (auto& mapping : mappings) {
+ append32(mapping.first); // startCharCode
+ append32(mapping.first); // endCharCode
+ append32(mapping.second); // startGlyphCode
+ }
+ 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); // Format 4
+ append16(0); // Placeholder for length in bytes
+ append16(0); // Language independent
+ uint16_t segCount = bmpMappings.size() + 1;
+ 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
+ for (auto& mapping : bmpMappings)
+ append16(mapping.first); // startCharCode
+ append16(0xFFFF);
+
+ append16(0); // reserved
+
+ // Starting character codes
+ for (auto& mapping : bmpMappings)
+ append16(mapping.first); // startCharCode
+ append16(0xFFFF);
+
+ // idDelta
+ for (auto& mapping : bmpMappings)
+ append16(static_cast<uint16_t>(mapping.second) - static_cast<uint16_t>(mapping.first)); // startCharCode
+ append16(0x0001);
+
+ // idRangeOffset
+ for (size_t i = 0; i < bmpMappings.size(); ++i)
+ append16(0); // startCharCode
+ 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>(m_result.size() - subtableLocation));
+}
+
+void SVGToOTFFontConverter::appendCMAPTable()
+{
+ auto startingOffset = m_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 = m_result.size();
+ append32(0); // Byte offset of subtable
+
+ append16(3); // Microsoft
+ append16(10); // Unicode
+ append32(28); // Byte offset of subtable
+
+ 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;
+ // Don't map ligatures here.
+ 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); // Version
+ append32(0x00010000); // Revision
+ append32(0); // Checksum placeholder; to be overwritten by the caller.
+ append32(0x5F0F3CF5); // Magic number.
+ append16((1 << 9) | 1);
+
+ append16(s_outputUnitsPerEm);
+ 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>(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); // Smallest readable size in pixels
+ append16(0); // Might contain LTR or RTL glyphs
+ append16(0); // Short offsets in the 'loca' table. However, CFF fonts don't have a 'loca' table so this is irrelevant
+ append16(0); // Glyph data format
+}
+
+// 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);
+}
+
+void SVGToOTFFontConverter::appendHHEATable()
+{
+ append32(0x00010000); // Version
+ append16(clampTo<int16_t>(m_ascent));
+ append16(clampTo<int16_t>(-m_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>(m_lineGap));
+ append16(clampTo<uint16_t>(m_advanceWidthMax));
+ append16(clampTo<int16_t>(m_boundingBox.x())); // Minimum left side bearing
+ append16(clampTo<int16_t>(m_minRightSideBearing)); // Minimum right side bearing
+ append16(clampTo<int16_t>(m_boundingBox.maxX())); // 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(m_glyphs.size()); // Number of advance widths in HMTX table
+}
+
+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); // Version
+ append16(m_glyphs.size());
+ 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(m_glyphs.size()); // Maximum number of glyphs referenced at top level
+ append16(0); // No compound glyphs
+}
+
+void SVGToOTFFontConverter::appendNAMETable()
+{
+ append16(0); // Format selector
+ append16(1); // Number of name records in table
+ append16(18); // Offset in bytes to the beginning of name character strings
+
+ append16(0); // Unicode
+ append16(3); // Unicode version 2.0 or later
+ append16(0); // Language
+ append16(1); // Name identifier. 1 = Font family
+ append16(m_fontFamily.length() * 2);
+ append16(0); // Offset into name data
+
+ 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); // Version
+ append16(clampTo<int16_t>(averageAdvance));
+ append16(clampTo<uint16_t>(m_weight)); // 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
+
+ unsigned numPanoseBytes = 0;
+ const unsigned panoseSize = 10;
+ char panoseBytes[panoseSize];
+ if (m_fontFaceElement) {
+ Vector<String> segments;
+ m_fontFaceElement->attributeWithoutSynchronization(SVGNames::panose_1Attr).string().split(' ', segments);
+ if (segments.size() == panoseSize) {
+ for (auto& segment : segments) {
+ bool ok;
+ int value = segment.toInt(&ok);
+ if (ok && value >= 0 && value <= 0xFF)
+ panoseBytes[numPanoseBytes++] = value;
+ }
+ }
+ }
+ if (numPanoseBytes != panoseSize)
+ memset(panoseBytes, 0, panoseSize);
+ m_result.append(panoseBytes, panoseSize);
+
+ for (int i = 0; i < 4; ++i)
+ append32(0); // "Bit assignments are pending. Set to 0"
+ append32(0x544B4257); // Font Vendor. "WBKT"
+ append16((m_weight >= 7 ? 1 << 5 : 0) | (m_italic ? 1 : 0)); // Font Patterns.
+ append16(0); // First unicode index
+ append16(0xFFFF); // Last unicode index
+ append16(clampTo<int16_t>(m_ascent)); // Typographical ascender
+ append16(clampTo<int16_t>(-m_descent)); // Typographical descender
+ append16(clampTo<int16_t>(m_lineGap)); // Typographical line gap
+ append16(clampTo<uint16_t>(m_ascent)); // Windows-specific ascent
+ append16(clampTo<uint16_t>(m_descent)); // 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>(m_xHeight)); // x-height
+ append16(clampTo<int16_t>(m_capHeight)); // 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 SVGToOTFFontConverter::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"
+}
+
+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();
+
+ // Header
+ m_result.append(1); // Major version
+ m_result.append(0); // Minor version
+ m_result.append(4); // Header size
+ m_result.append(4); // Offsets within CFF table are 4 bytes long
+
+ // Name INDEX
+ String fontName;
+ if (m_fontFaceElement) {
+ // FIXME: fontFamily() here might not be quite what we want.
+ String potentialFontName = m_fontFamily;
+ if (isValidStringForCFF(potentialFontName))
+ fontName = potentialFontName;
+ }
+ append16(1); // INDEX contains 1 element
+ m_result.append(4); // Offsets in this INDEX are 4 bytes long
+ append32(1); // 1-index offset of name data
+ append32(fontName.length() + 1); // 1-index offset just past end of name data
+ 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);
+
+ // Top DICT INDEX.
+ append16(1); // INDEX contains 1 element
+ m_result.append(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
+#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); // Offset of Charset info. Will be overwritten later.
+ m_result.append(charsetIndexKey);
+ m_result.append(operand32Bit);
+ unsigned charstringsOffsetLocation = m_result.size();
+ append32(0); // Offset of CharStrings INDEX. Will be overwritten later.
+ m_result.append(charstringsIndexKey);
+ m_result.append(operand32Bit);
+ append32(0); // 0-sized private dict
+ m_result.append(operand32Bit);
+ append32(0); // no location for private dict
+ m_result.append(privateDictIndexKey); // Private dict size and offset
+ ASSERT(m_result.size() == topDictStart + sizeOfTopIndex);
+
+ // String INDEX
+ String unknownCharacter = ASCIILiteral("UnknownChar");
+ append16(2 + (hasWeight ? 1 : 0)); // Number of elements in INDEX
+ m_result.append(4); // Offsets in this INDEX are 4 bytes long
+ 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); // Empty subroutine INDEX
+
+ // Charset info
+ overwrite32(charsetOffsetLocation, m_result.size() - startingOffset);
+ m_result.append(0);
+ for (Glyph i = 1; i < m_glyphs.size(); ++i)
+ append16(userDefinedStringStartIndex + 1);
+
+ // CharStrings INDEX
+ overwrite32(charstringsOffsetLocation, m_result.size() - startingOffset);
+ append16(m_glyphs.size());
+ m_result.append(4); // Offsets in this INDEX are 4 bytes long
+ 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();
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=138592 This needs to be done in codepoint space, not glyph space
+ 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); // Format 1
+ append16(0); // Placeholder for offset to coverage table, relative to beginning of substitution table
+ append16(ligaturePairs.size()); // Number of LigatureSet tables
+ grow(overlappingFirstGlyphSegmentLengths.size() * 2); // Placeholder for offset to LigatureSet table
+
+ 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]); // LigatureCount
+ grow(overlappingFirstGlyphSegmentLengths[i] * 2); // Placeholder for offset to Ligature table
+ }
+ 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());
+
+ // Coverage table
+ overwrite16(subtableLocation + 2, m_result.size() - subtableLocation);
+ append16(1); // CoverageFormat
+ append16(ligatureSetTableLocations.size()); // GlyphCount
+ 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); // Format 2
+ Placeholder toCoverageTable = placeholder(subtableLocation);
+ append16(arabicFinalReplacements.size()); // GlyphCount
+ for (auto& pair : arabicFinalReplacements)
+ append16(pair.second);
+
+ toCoverageTable.populate();
+ append16(1); // CoverageFormat
+ append16(arabicFinalReplacements.size()); // GlyphCount
+ for (auto& pair : arabicFinalReplacements)
+ append16(pair.first);
+}
+
+void SVGToOTFFontConverter::appendScriptSubtable(unsigned featureCount)
+{
+ auto dfltScriptTableLocation = m_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, m_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(m_featureCountGSUB++); // Features indices
+}
+
+void SVGToOTFFontConverter::appendGSUBTable()
+{
+ auto tableLocation = m_result.size();
+ auto headerSize = 10;
+
+ append32(0x00010000); // Version
+ append16(headerSize); // Offset to ScriptList
+ Placeholder toFeatureList = placeholder(tableLocation);
+ Placeholder toLookupList = placeholder(tableLocation);
+ ASSERT(tableLocation + headerSize == m_result.size());
+
+ // ScriptList
+ auto scriptListLocation = m_result.size();
+ append16(2); // Number of ScriptRecords
+ append32BitCode("DFLT");
+ append16(0); // Placeholder for offset of Script table, relative to beginning of ScriptList
+ append32BitCode("arab");
+ append16(0); // Placeholder for offset of Script table, relative to beginning of ScriptList
+
+ overwrite16(scriptListLocation + 6, m_result.size() - scriptListLocation);
+ appendScriptSubtable(1);
+ overwrite16(scriptListLocation + 12, m_result.size() - scriptListLocation);
+ appendScriptSubtable(4);
+
+ const unsigned featureCount = 5;
+
+ // FeatureList
+ toFeatureList.populate();
+ auto featureListLocation = m_result.size();
+ size_t featureListSize = 2 + 6 * featureCount;
+ size_t featureTableSize = 6;
+ append16(featureCount); // FeatureCount
+ append32BitCode("liga");
+ append16(featureListSize + featureTableSize * 0); // Offset of feature table, relative to beginning of FeatureList table
+ append32BitCode("fina");
+ append16(featureListSize + featureTableSize * 1); // Offset of feature table, relative to beginning of FeatureList table
+ append32BitCode("medi");
+ append16(featureListSize + featureTableSize * 2); // Offset of feature table, relative to beginning of FeatureList table
+ append32BitCode("init");
+ append16(featureListSize + featureTableSize * 3); // Offset of feature table, relative to beginning of FeatureList table
+ append32BitCode("rlig");
+ append16(featureListSize + featureTableSize * 4); // Offset of feature table, relative to beginning of FeatureList table
+ ASSERT_UNUSED(featureListLocation, featureListLocation + featureListSize == m_result.size());
+
+ for (unsigned i = 0; i < featureCount; ++i) {
+ auto featureTableStart = m_result.size();
+ append16(0); // FeatureParams "= NULL ... reserved"
+ append16(1); // LookupCount
+ append16(i); // LookupListIndex
+ ASSERT_UNUSED(featureTableStart, featureTableStart + featureTableSize == m_result.size());
+ }
+
+ // LookupList
+ toLookupList.populate();
+ auto lookupListLocation = m_result.size();
+ append16(featureCount); // LookupCount
+ for (unsigned i = 0; i < featureCount; ++i)
+ append16(0); // Placeholder for offset to feature table, relative to beginning of LookupList
+ 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); // Type 3: "Replace one glyph with one of many glyphs"
+ break;
+ case 0:
+ append16(4); // Type 4: "Replace multiple glyphs with one glyph"
+ break;
+ default:
+ append16(1); // Type 1: "Replace one glyph with one glyph"
+ break;
+ }
+ append16(0); // LookupFlag
+ append16(1); // SubTableCount
+ append16(0); // Placeholder for offset to subtable, relative to beginning of Lookup table
+ }
+
+ appendLigatureSubtable(subtableRecordLocations[0]);
+ appendArabicReplacementSubtable(subtableRecordLocations[1], "terminal");
+ appendArabicReplacementSubtable(subtableRecordLocations[2], "medial");
+ appendArabicReplacementSubtable(subtableRecordLocations[3], "initial");
+
+ // Manually append empty "rlig" subtable
+ overwrite16(subtableRecordLocations[4] + 6, m_result.size() - subtableRecordLocations[4]);
+ append16(1); // Format 1
+ append16(6); // offset to coverage table, relative to beginning of substitution table
+ append16(0); // AlternateSetCount
+ append16(1); // CoverageFormat
+ append16(0); // GlyphCount
+}
+
+void SVGToOTFFontConverter::appendVORGTable()
+{
+ append16(1); // Major version
+ append16(0); // Minor version
+
+ 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); // Place to write table size.
+ 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); // Version
+ append16(clampTo<int16_t>(height / 2)); // Vertical typographic ascender (vertical baseline to the right)
+ append16(clampTo<int16_t>(-static_cast<int>(height / 2))); // Vertical typographic descender
+ append16(clampTo<int16_t>(s_outputUnitsPerEm / 10)); // Vertical typographic line gap
+ // FIXME: m_unitsPerEm is almost certainly not correct
+ append16(clampTo<int16_t>(m_advanceHeightMax));
+ append16(clampTo<int16_t>(s_outputUnitsPerEm - m_boundingBox.maxY())); // Minimum top side bearing
+ append16(clampTo<int16_t>(m_boundingBox.y())); // Minimum bottom side bearing
+ append16(clampTo<int16_t>(s_outputUnitsPerEm - m_boundingBox.y())); // Y 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); // "Set to 0"
+ append16(m_glyphs.size()); // Number of advance heights in VMTX table
+}
+
+void SVGToOTFFontConverter::appendVMTXTable()
+{
+ for (auto& glyph : m_glyphs) {
+ append16(clampTo<uint16_t>(glyph.verticalAdvance));
+ append16(clampTo<int16_t>(s_outputUnitsPerEm - glyph.boundingBox.maxY())); // top side bearing
+ }
+}
+
+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);
+
+ // FIXME: Use table format 2 so we don't have to append each of these one by one.
+ 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); // Version of subtable
+ append16(sizeOfKerningDataTable); // Length of this subtable
+ append16(coverage); // Table coverage bitfield
+
+ uint16_t roundedNumKerningPairs = roundDownToPowerOfTwo(kerningData.size());
+
+ append16(kerningData.size());
+ append16(roundedNumKerningPairs * 6); // searchRange: "The largest power of two less than or equal to the value of nPairs, multiplied by the size in bytes of an entry in the table."
+ append16(integralLog2(roundedNumKerningPairs)); // entrySelector: "log2 of the largest power of two less than or equal to the value of nPairs."
+ append16((kerningData.size() - roundedNumKerningPairs) * 6); // rangeShift: "The value of nPairs minus the largest power of two less than or equal to nPairs,
+ // and then multiplied by the size in bytes of an entry in the table."
+
+ for (auto& kerningDataElement : kerningData) {
+ append16(kerningDataElement.glyph1);
+ append16(kerningDataElement.glyph2);
+ append16(kerningDataElement.adjustment);
+ }
+
+ return sizeOfKerningDataTable;
+}
+
+void SVGToOTFFontConverter::appendKERNTable()
+{
+ append16(0); // Version
+ append16(2); // Number of subtables
+
+#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());
+
+#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED <= 101000
+ // Work around a bug in Apple's font parser by adding some padding bytes. <rdar://problem/18401901>
+ for (int i = 0; i < 6; ++i)
+ m_result.append(0);
+#endif
+}
+
+template <typename V>
+static void writeCFFEncodedNumber(V& vector, float number)
+{
+ vector.append(0xFF);
+ // Convert to 16.16 fixed-point
+ 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)); // hmtx table can't encode fractional FUnit values, and the CFF table needs to agree with hmtx.
+ writeCFFEncodedNumber(m_cffData, origin.x());
+ writeCFFEncodedNumber(m_cffData, origin.y());
+ m_cffData.append(rMoveTo);
+ }
+
+ std::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;
+ std::optional<FloatRect> m_boundingBox;
+ float m_unitsPerEmScalar;
+};
+
+Vector<char> SVGToOTFFontConverter::transcodeGlyphPaths(float width, const SVGElement& glyphOrMissingGlyphElement, std::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;
+ }
+
+ // FIXME: If we are vertical, use vert_origin_x and vert_origin_y
+ 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, std::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);
+
+ std::optional<FloatRect> glyphBoundingBox;
+ auto path = transcodeGlyphPaths(horizontalAdvance, glyphOrMissingGlyphElement, glyphBoundingBox);
+ if (!path.size()) {
+ // It's better to use a fallback font rather than use a font without all its glyphs.
+ 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.value_or(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());
+
+ // Some platforms, including OS X, use 0 ascent and descent to mean that the platform should synthesize
+ // a value based on a heuristic. However, SVG fonts can legitimately have 0 for ascent or descent.
+ // Specifing a single FUnit gets us as close to 0 as we can without triggering the synthesis.
+ 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);
+
+ std::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()) // If we can never actually trigger this glyph, ignore it completely
+ processGlyphElement(glyphElement, &glyphElement, defaultHorizontalAdvance, defaultVerticalAdvance, unicodeAttribute, boundingBox);
+ }
+
+ m_boundingBox = boundingBox.value_or(FloatRect());
+
+#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED <= 101000
+ // <rdar://problem/20086223> Cocoa has a bug where glyph bounding boxes are not correctly respected for frustum culling. Work around this by
+ // inflating the font's bounding box
+ m_boundingBox.extend(FloatPoint(0, 0));
+#endif
+
+ 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;
+ // Prefer isolated arabic forms
+ if (glyph.glyphElement && equalLettersIgnoringASCIICase(glyph.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), "isolated"))
+ glyphVector.insert(0, i);
+ else
+ glyphVector.append(i);
+ }
+ }
+
+ // FIXME: Handle commas.
+ if (m_fontFaceElement) {
+ Vector<String> segments;
+ m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_weightAttr).string().split(' ', segments);
+ for (auto& segment : segments) {
+ if (equalLettersIgnoringASCIICase(segment, "bold")) {
+ m_weight = 7;
+ break;
+ }
+ bool ok;
+ int value = segment.toInt(&ok);
+ if (ok && value >= 0 && value < 1000) {
+ m_weight = (value + 50) / 100;
+ break;
+ }
+ }
+ m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_styleAttr).string().split(' ', segments);
+ for (auto& segment : segments) {
+ 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; // searchRange: "(Maximum power of 2 <= numTables) x 16."
+
+ m_result.append('O');
+ m_result.append('T');
+ m_result.append('T');
+ m_result.append('O');
+ append16(numTables);
+ append16(searchRange);
+ append16(integralLog2(roundedNumTables)); // entrySelector: "Log2(maximum power of 2 <= numTables)."
+ append16(numTables * 16 - searchRange); // rangeShift: "NumTables x 16-searchRange."
+
+ ASSERT(m_result.size() == headerSize);
+
+ // Leave space for the directory entries.
+ 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);
+
+ // 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, m_result.size()));
+ return true;
+}
+
+std::optional<Vector<char>> convertSVGToOTFFont(const SVGFontElement& element)
+{
+ SVGToOTFFontConverter converter(element);
+ if (converter.error())
+ return std::nullopt;
+ if (!converter.convertSVGToOTFFont())
+ return std::nullopt;
+ return converter.releaseResult();
+}
+
+}
+
+#endif // ENABLE(SVG_FONTS)