/* * Copyright (C) 2014-2017 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" #include "CSSStyleDeclaration.h" #include "ElementChildIterator.h" #include "Glyph.h" #include "HTMLParserIdioms.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 #include #include namespace WebCore { template 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 releaseResult() { return WTFMove(m_result); } bool error() const { return m_error; } private: struct GlyphData { GlyphData(Vector&& 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 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_ENABLED , m_active(other.m_active) #endif { #if ASSERT_ENABLED 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::max()); m_converter.overwrite16(m_location, delta); #if ASSERT_ENABLED m_active = false; #endif } ~Placeholder() { ASSERT(!m_active); } private: SVGToOTFFontConverter& m_converter; const size_t m_baseOfOffset; const size_t m_location; #if ASSERT_ENABLED 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& boundingBox); typedef void (SVGToOTFFontConverter::*FontAppendingFunction)(); void appendTable(const char identifier[4], FontAppendingFunction); void appendFormat12CMAPTable(const Vector>& codepointToGlyphMappings); void appendFormat4CMAPTable(const Vector>& 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 transcodeGlyphPaths(float width, const SVGElement& glyphOrMissingGlyphElement, std::optional& boundingBox) const; void addCodepointRanges(const UnicodeRanges&, HashSet& glyphSet) const; void addCodepoints(const HashSet& codepoints, HashSet& glyphSet) const; void addGlyphNames(const HashSet& glyphNames, HashSet& glyphSet) const; void addKerningPair(Vector&, SVGKerningPair&&) const; template size_t appendKERNSubtable(std::optional (T::*buildKerningPair)() const, uint16_t coverage); size_t finishAppendingKERNSubtable(Vector, uint16_t coverage); void appendLigatureSubtable(size_t subtableRecordLocation); void appendArabicReplacementSubtable(size_t subtableRecordLocation, const char arabicForm[]); void appendScriptSubtable(unsigned featureCount); Vector glyphsForCodepoint(UChar32) const; Glyph firstGlyph(const Vector&, UChar32) const; template T scaleUnitsPerEm(T value) const { return value * s_outputUnitsPerEm / m_inputUnitsPerEm; } Vector m_glyphs; HashMap m_glyphNameToIndexMap; // SVG 1.1: "It is recommended that glyph names be unique within a font." HashMap> m_codepointsToIndicesMap; Vector m_result; Vector 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>& 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>& 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(2 * segCount)); // segCountX2: "2 x segCount" uint16_t originalSearchRange = roundDownToPowerOfTwo(segCount); uint16_t searchRange = clampTo(2 * originalSearchRange); // searchRange: "2 x (2**floor(log2(segCount)))" append16(searchRange); append16(integralLog2(originalSearchRange)); // entrySelector: "log2(searchRange/2)" append16(clampTo((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(mapping.second) - static_cast(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(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> mappings; UChar32 previousCodepoint = std::numeric_limits::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> 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(m_boundingBox.x())); append16(clampTo(m_boundingBox.y())); append16(clampTo(m_boundingBox.maxX())); append16(clampTo(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 } void SVGToOTFFontConverter::appendHHEATable() { append32(0x00010000); // Version append16(clampTo(m_ascent)); append16(clampTo(-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(m_lineGap)); append16(clampTo(m_advanceWidthMax)); append16(clampTo(m_boundingBox.x())); // Minimum left side bearing append16(clampTo(m_minRightSideBearing)); // Minimum right side bearing append16(clampTo(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(glyph.horizontalAdvance)); append16(clampTo(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; auto horizAdvX = parseHTMLInteger(m_fontElement.attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr)); if (!horizAdvX && m_missingGlyphElement) horizAdvX = parseHTMLInteger(m_missingGlyphElement->attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr)); if (horizAdvX) averageAdvance = clampTo(scaleUnitsPerEm(*horizAdvX)); append16(2); // Version append16(clampTo(averageAdvance)); append16(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 segments = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::panose_1Attr).string().split(' '); if (segments.size() == panoseSize) { for (auto& segment : segments) { if (auto value = parseIntegerAllowingTrailingJunk(segment)) 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(m_ascent)); // Typographical ascender append16(clampTo(-m_descent)); // Typographical descender append16(clampTo(m_lineGap)); // Typographical line gap append16(clampTo(m_ascent)); // Windows-specific ascent append16(clampTo(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(m_xHeight)); // x-height append16(clampTo(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_ENABLED 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(m_boundingBox.x())); m_result.append(operand32Bit); append32(clampTo(m_boundingBox.y())); m_result.append(operand32Bit); append32(clampTo(m_boundingBox.width())); m_result.append(operand32Bit); append32(clampTo(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 = "UnknownChar"_s; 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& v, UChar32 codepoint) const { #if !ASSERT_ENABLED UNUSED_PARAM(codepoint); #endif ASSERT(!v.isEmpty()); if (v.isEmpty()) return 0; #if ASSERT_ENABLED 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, Glyph> LigaturePair; Vector ligaturePairs; for (Glyph glyphIndex = 0; glyphIndex < m_glyphs.size(); ++glyphIndex) { ligaturePairs.append(LigaturePair(Vector(), glyphIndex)); Vector& 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::max()) ligaturePairs.clear(); std::sort(ligaturePairs.begin(), ligaturePairs.end(), [](auto& lhs, auto& rhs) { return lhs.first[0] < rhs.first[0]; }); Vector 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 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> 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::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 auto vertOriginY = parseHTMLInteger(m_fontElement.attributeWithoutSynchronization(SVGNames::vert_origin_yAttr)); if (!vertOriginY && m_missingGlyphElement) vertOriginY = parseHTMLInteger(m_missingGlyphElement->attributeWithoutSynchronization(SVGNames::vert_origin_yAttr)); append16(clampTo(scaleUnitsPerEm(vertOriginY.value_or(0)))); 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 (auto verticalOriginY = parseHTMLInteger(glyph->attributeWithoutSynchronization(SVGNames::vert_origin_yAttr))) { append16(i); append16(clampTo(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(height / 2)); // Vertical typographic ascender (vertical baseline to the right) append16(clampTo(-static_cast(height / 2))); // Vertical typographic descender append16(clampTo(s_outputUnitsPerEm / 10)); // Vertical typographic line gap // FIXME: m_unitsPerEm is almost certainly not correct append16(clampTo(m_advanceHeightMax)); append16(clampTo(s_outputUnitsPerEm - m_boundingBox.maxY())); // Minimum top side bearing append16(clampTo(m_boundingBox.y())); // Minimum bottom side bearing append16(clampTo(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(glyph.verticalAdvance)); append16(clampTo(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 SVGToOTFFontConverter::glyphsForCodepoint(UChar32 codepoint) const { return m_codepointsToIndicesMap.get(codepointToString(codepoint)); } void SVGToOTFFontConverter::addCodepointRanges(const UnicodeRanges& unicodeRanges, HashSet& 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& codepoints, HashSet& glyphSet) const { for (auto& codepointString : codepoints) { for (auto index : m_codepointsToIndicesMap.get(codepointString)) glyphSet.add(index); } } void SVGToOTFFontConverter::addGlyphNames(const HashSet& glyphNames, HashSet& glyphSet) const { for (auto& glyphName : glyphNames) { if (Glyph glyph = m_glyphNameToIndexMap.get(glyphName)) glyphSet.add(glyph); } } void SVGToOTFFontConverter::addKerningPair(Vector& data, SVGKerningPair&& kerningPair) const { HashSet glyphSet1; HashSet 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(-scaleUnitsPerEm(kerningPair.kerning)))); } } template inline size_t SVGToOTFFontConverter::appendKERNSubtable(std::optional (T::*buildKerningPair)() const, uint16_t coverage) { Vector kerningData; for (auto& element : childrenOfType(m_fontElement)) { if (auto kerningPair = (element.*buildKerningPair)()) addKerningPair(kerningData, WTFMove(*kerningPair)); } return finishAppendingKERNSubtable(WTFMove(kerningData), coverage); } size_t SVGToOTFFontConverter::finishAppendingKERNSubtable(Vector 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::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_ENABLED auto subtablesOffset = m_result.size(); #endif size_t sizeOfHorizontalSubtable = appendKERNSubtable(&SVGHKernElement::buildHorizontalKerningPair, 1); ASSERT_UNUSED(sizeOfHorizontalSubtable, subtablesOffset + sizeOfHorizontalSubtable == m_result.size()); size_t sizeOfVerticalSubtable = appendKERNSubtable(&SVGVKernElement::buildVerticalKerningPair, 0); ASSERT_UNUSED(sizeOfVerticalSubtable, subtablesOffset + sizeOfHorizontalSubtable + sizeOfVerticalSubtable == m_result.size()); } template static void writeCFFEncodedNumber(V& vector, float number) { vector.append(0xFF); // Convert to 16.16 fixed-point append32(vector, clampTo(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& 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 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& m_cffData; FloatPoint m_startingPoint; FloatPoint m_current; std::optional m_boundingBox; float m_unitsPerEmScalar; }; Vector SVGToOTFFontConverter::transcodeGlyphPaths(float width, const SVGElement& glyphOrMissingGlyphElement, std::optional& boundingBox) const { Vector 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(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& 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 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 ligatureCodepoints; HashSet 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(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& 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(m_fontElement).first()) , m_missingGlyphElement(childrenOfType(m_fontElement).first()) , m_advanceWidthMax(0) , m_advanceHeightMax(0) , m_minRightSideBearing(std::numeric_limits::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 boundingBox; if (m_missingGlyphElement) processGlyphElement(*m_missingGlyphElement, nullptr, defaultHorizontalAdvance, defaultVerticalAdvance, String(), boundingBox); else { m_glyphs.append(GlyphData(Vector(m_emptyGlyphCharString), nullptr, s_outputUnitsPerEm, s_outputUnitsPerEm, FloatRect(), String())); boundingBox = FloatRect(0, 0, s_outputUnitsPerEm, s_outputUnitsPerEm); } for (auto& glyphElement : childrenOfType(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()); appendLigatureGlyphs(); if (m_glyphs.size() > std::numeric_limits::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()).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) { for (auto segment : StringView(m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_weightAttr)).split(' ')) { if (equalLettersIgnoringASCIICase(segment, "bold")) { m_weight = 7; break; } if (auto value = parseIntegerAllowingTrailingJunk(segment); value && *value < 1000) { m_weight = (*value + 50) / 100; break; } } for (auto segment : StringView(m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_styleAttr)).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(m_result[offset + 3]) | (static_cast(m_result[offset + 2]) << 8) | (static_cast(m_result[offset + 1]) << 16) | (static_cast(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> convertSVGToOTFFont(const SVGFontElement& element) { SVGToOTFFontConverter converter(element); if (converter.error()) return std::nullopt; if (!converter.convertSVGToOTFFont()) return std::nullopt; return converter.releaseResult(); } }