| 1 | /* |
| 2 | * Copyright (C) 2014-2017 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "SVGToOTFFontConversion.h" |
| 28 | |
| 29 | #if ENABLE(SVG_FONTS) |
| 30 | |
| 31 | #include "CSSStyleDeclaration.h" |
| 32 | #include "ElementChildIterator.h" |
| 33 | #include "Glyph.h" |
| 34 | #include "SVGFontElement.h" |
| 35 | #include "SVGFontFaceElement.h" |
| 36 | #include "SVGGlyphElement.h" |
| 37 | #include "SVGHKernElement.h" |
| 38 | #include "SVGMissingGlyphElement.h" |
| 39 | #include "SVGPathParser.h" |
| 40 | #include "SVGPathStringSource.h" |
| 41 | #include "SVGVKernElement.h" |
| 42 | #include <wtf/Optional.h> |
| 43 | #include <wtf/Vector.h> |
| 44 | #include <wtf/text/StringView.h> |
| 45 | |
| 46 | namespace WebCore { |
| 47 | |
| 48 | template <typename V> |
| 49 | static inline void append32(V& result, uint32_t value) |
| 50 | { |
| 51 | result.append(value >> 24); |
| 52 | result.append(value >> 16); |
| 53 | result.append(value >> 8); |
| 54 | result.append(value); |
| 55 | } |
| 56 | |
| 57 | class SVGToOTFFontConverter { |
| 58 | public: |
| 59 | SVGToOTFFontConverter(const SVGFontElement&); |
| 60 | bool convertSVGToOTFFont(); |
| 61 | |
| 62 | Vector<char> releaseResult() |
| 63 | { |
| 64 | return WTFMove(m_result); |
| 65 | } |
| 66 | |
| 67 | bool error() const |
| 68 | { |
| 69 | return m_error; |
| 70 | } |
| 71 | |
| 72 | private: |
| 73 | struct GlyphData { |
| 74 | GlyphData(Vector<char>&& charString, const SVGGlyphElement* glyphElement, float horizontalAdvance, float verticalAdvance, FloatRect boundingBox, const String& codepoints) |
| 75 | : boundingBox(boundingBox) |
| 76 | , charString(charString) |
| 77 | , codepoints(codepoints) |
| 78 | , glyphElement(glyphElement) |
| 79 | , horizontalAdvance(horizontalAdvance) |
| 80 | , verticalAdvance(verticalAdvance) |
| 81 | { |
| 82 | } |
| 83 | FloatRect boundingBox; |
| 84 | Vector<char> charString; |
| 85 | String codepoints; |
| 86 | const SVGGlyphElement* glyphElement; |
| 87 | float horizontalAdvance; |
| 88 | float verticalAdvance; |
| 89 | }; |
| 90 | |
| 91 | class Placeholder { |
| 92 | public: |
| 93 | Placeholder(SVGToOTFFontConverter& converter, size_t baseOfOffset) |
| 94 | : m_converter(converter) |
| 95 | , m_baseOfOffset(baseOfOffset) |
| 96 | , m_location(m_converter.m_result.size()) |
| 97 | { |
| 98 | m_converter.append16(0); |
| 99 | } |
| 100 | |
| 101 | Placeholder(Placeholder&& other) |
| 102 | : m_converter(other.m_converter) |
| 103 | , m_baseOfOffset(other.m_baseOfOffset) |
| 104 | , m_location(other.m_location) |
| 105 | #if !ASSERT_DISABLED |
| 106 | , m_active(other.m_active) |
| 107 | #endif |
| 108 | { |
| 109 | #if !ASSERT_DISABLED |
| 110 | other.m_active = false; |
| 111 | #endif |
| 112 | } |
| 113 | |
| 114 | void populate() |
| 115 | { |
| 116 | ASSERT(m_active); |
| 117 | size_t delta = m_converter.m_result.size() - m_baseOfOffset; |
| 118 | ASSERT(delta < std::numeric_limits<uint16_t>::max()); |
| 119 | m_converter.overwrite16(m_location, delta); |
| 120 | #if !ASSERT_DISABLED |
| 121 | m_active = false; |
| 122 | #endif |
| 123 | } |
| 124 | |
| 125 | ~Placeholder() |
| 126 | { |
| 127 | ASSERT(!m_active); |
| 128 | } |
| 129 | |
| 130 | private: |
| 131 | SVGToOTFFontConverter& m_converter; |
| 132 | const size_t m_baseOfOffset; |
| 133 | const size_t m_location; |
| 134 | #if !ASSERT_DISABLED |
| 135 | bool m_active = { true }; |
| 136 | #endif |
| 137 | }; |
| 138 | |
| 139 | struct KerningData { |
| 140 | KerningData(uint16_t glyph1, uint16_t glyph2, int16_t adjustment) |
| 141 | : glyph1(glyph1) |
| 142 | , glyph2(glyph2) |
| 143 | , adjustment(adjustment) |
| 144 | { |
| 145 | } |
| 146 | uint16_t glyph1; |
| 147 | uint16_t glyph2; |
| 148 | int16_t adjustment; |
| 149 | }; |
| 150 | |
| 151 | Placeholder placeholder(size_t baseOfOffset) |
| 152 | { |
| 153 | return Placeholder(*this, baseOfOffset); |
| 154 | } |
| 155 | |
| 156 | void append32(uint32_t value) |
| 157 | { |
| 158 | WebCore::append32(m_result, value); |
| 159 | } |
| 160 | |
| 161 | void append32BitCode(const char code[4]) |
| 162 | { |
| 163 | m_result.append(code[0]); |
| 164 | m_result.append(code[1]); |
| 165 | m_result.append(code[2]); |
| 166 | m_result.append(code[3]); |
| 167 | } |
| 168 | |
| 169 | void append16(uint16_t value) |
| 170 | { |
| 171 | m_result.append(value >> 8); |
| 172 | m_result.append(value); |
| 173 | } |
| 174 | |
| 175 | void grow(size_t delta) |
| 176 | { |
| 177 | m_result.grow(m_result.size() + delta); |
| 178 | } |
| 179 | |
| 180 | void overwrite32(unsigned location, uint32_t value) |
| 181 | { |
| 182 | ASSERT(m_result.size() >= location + 4); |
| 183 | m_result[location] = value >> 24; |
| 184 | m_result[location + 1] = value >> 16; |
| 185 | m_result[location + 2] = value >> 8; |
| 186 | m_result[location + 3] = value; |
| 187 | } |
| 188 | |
| 189 | void overwrite16(unsigned location, uint16_t value) |
| 190 | { |
| 191 | ASSERT(m_result.size() >= location + 2); |
| 192 | m_result[location] = value >> 8; |
| 193 | m_result[location + 1] = value; |
| 194 | } |
| 195 | |
| 196 | static const size_t = 12; |
| 197 | static const size_t directoryEntrySize = 16; |
| 198 | |
| 199 | uint32_t calculateChecksum(size_t startingOffset, size_t endingOffset) const; |
| 200 | |
| 201 | void processGlyphElement(const SVGElement& glyphOrMissingGlyphElement, const SVGGlyphElement*, float defaultHorizontalAdvance, float defaultVerticalAdvance, const String& codepoints, Optional<FloatRect>& boundingBox); |
| 202 | |
| 203 | typedef void (SVGToOTFFontConverter::*FontAppendingFunction)(); |
| 204 | void appendTable(const char identifier[4], FontAppendingFunction); |
| 205 | void appendFormat12CMAPTable(const Vector<std::pair<UChar32, Glyph>>& codepointToGlyphMappings); |
| 206 | void appendFormat4CMAPTable(const Vector<std::pair<UChar32, Glyph>>& codepointToGlyphMappings); |
| 207 | void appendCMAPTable(); |
| 208 | void appendGSUBTable(); |
| 209 | void appendHEADTable(); |
| 210 | void appendHHEATable(); |
| 211 | void appendHMTXTable(); |
| 212 | void appendVHEATable(); |
| 213 | void appendVMTXTable(); |
| 214 | void appendKERNTable(); |
| 215 | void appendMAXPTable(); |
| 216 | void appendNAMETable(); |
| 217 | void appendOS2Table(); |
| 218 | void appendPOSTTable(); |
| 219 | void appendCFFTable(); |
| 220 | void appendVORGTable(); |
| 221 | |
| 222 | void appendLigatureGlyphs(); |
| 223 | static bool compareCodepointsLexicographically(const GlyphData&, const GlyphData&); |
| 224 | |
| 225 | void appendValidCFFString(const String&); |
| 226 | |
| 227 | Vector<char> transcodeGlyphPaths(float width, const SVGElement& glyphOrMissingGlyphElement, Optional<FloatRect>& boundingBox) const; |
| 228 | |
| 229 | void addCodepointRanges(const UnicodeRanges&, HashSet<Glyph>& glyphSet) const; |
| 230 | void addCodepoints(const HashSet<String>& codepoints, HashSet<Glyph>& glyphSet) const; |
| 231 | void addGlyphNames(const HashSet<String>& glyphNames, HashSet<Glyph>& glyphSet) const; |
| 232 | void addKerningPair(Vector<KerningData>&, const SVGKerningPair&) const; |
| 233 | template<typename T> size_t appendKERNSubtable(bool (T::*buildKerningPair)(SVGKerningPair&) const, uint16_t coverage); |
| 234 | size_t finishAppendingKERNSubtable(Vector<KerningData>, uint16_t coverage); |
| 235 | |
| 236 | void appendLigatureSubtable(size_t subtableRecordLocation); |
| 237 | void appendArabicReplacementSubtable(size_t subtableRecordLocation, const char arabicForm[]); |
| 238 | void appendScriptSubtable(unsigned featureCount); |
| 239 | Vector<Glyph, 1> glyphsForCodepoint(UChar32) const; |
| 240 | Glyph firstGlyph(const Vector<Glyph, 1>&, UChar32) const; |
| 241 | |
| 242 | template<typename T> T scaleUnitsPerEm(T value) const |
| 243 | { |
| 244 | return value * s_outputUnitsPerEm / m_inputUnitsPerEm; |
| 245 | } |
| 246 | |
| 247 | Vector<GlyphData> m_glyphs; |
| 248 | HashMap<String, Glyph> m_glyphNameToIndexMap; // SVG 1.1: "It is recommended that glyph names be unique within a font." |
| 249 | HashMap<String, Vector<Glyph, 1>> m_codepointsToIndicesMap; |
| 250 | Vector<char> m_result; |
| 251 | Vector<char, 17> m_emptyGlyphCharString; |
| 252 | FloatRect m_boundingBox; |
| 253 | const SVGFontElement& m_fontElement; |
| 254 | const SVGFontFaceElement* m_fontFaceElement; |
| 255 | const SVGMissingGlyphElement* m_missingGlyphElement; |
| 256 | String m_fontFamily; |
| 257 | float m_advanceWidthMax; |
| 258 | float m_advanceHeightMax; |
| 259 | float m_minRightSideBearing; |
| 260 | static const unsigned s_outputUnitsPerEm = 1000; |
| 261 | unsigned m_inputUnitsPerEm; |
| 262 | int m_lineGap; |
| 263 | int m_xHeight; |
| 264 | int m_capHeight; |
| 265 | int m_ascent; |
| 266 | int m_descent; |
| 267 | unsigned m_featureCountGSUB; |
| 268 | unsigned m_tablesAppendedCount; |
| 269 | uint8_t m_weight; |
| 270 | bool m_italic; |
| 271 | bool m_error { false }; |
| 272 | }; |
| 273 | |
| 274 | static uint16_t roundDownToPowerOfTwo(uint16_t x) |
| 275 | { |
| 276 | x |= x >> 1; |
| 277 | x |= x >> 2; |
| 278 | x |= x >> 4; |
| 279 | x |= x >> 8; |
| 280 | return (x >> 1) + 1; |
| 281 | } |
| 282 | |
| 283 | static uint16_t integralLog2(uint16_t x) |
| 284 | { |
| 285 | uint16_t result = 0; |
| 286 | while (x >>= 1) |
| 287 | ++result; |
| 288 | return result; |
| 289 | } |
| 290 | |
| 291 | void SVGToOTFFontConverter::appendFormat12CMAPTable(const Vector<std::pair<UChar32, Glyph>>& mappings) |
| 292 | { |
| 293 | // Braindead scheme: One segment for each character |
| 294 | ASSERT(m_glyphs.size() < 0xFFFF); |
| 295 | auto subtableLocation = m_result.size(); |
| 296 | append32(12 << 16); // Format 12 |
| 297 | append32(0); // Placeholder for byte length |
| 298 | append32(0); // Language independent |
| 299 | append32(0); // Placeholder for nGroups |
| 300 | for (auto& mapping : mappings) { |
| 301 | append32(mapping.first); // startCharCode |
| 302 | append32(mapping.first); // endCharCode |
| 303 | append32(mapping.second); // startGlyphCode |
| 304 | } |
| 305 | overwrite32(subtableLocation + 4, m_result.size() - subtableLocation); |
| 306 | overwrite32(subtableLocation + 12, mappings.size()); |
| 307 | } |
| 308 | |
| 309 | void SVGToOTFFontConverter::appendFormat4CMAPTable(const Vector<std::pair<UChar32, Glyph>>& bmpMappings) |
| 310 | { |
| 311 | auto subtableLocation = m_result.size(); |
| 312 | append16(4); // Format 4 |
| 313 | append16(0); // Placeholder for length in bytes |
| 314 | append16(0); // Language independent |
| 315 | uint16_t segCount = bmpMappings.size() + 1; |
| 316 | append16(clampTo<uint16_t>(2 * segCount)); // segCountX2: "2 x segCount" |
| 317 | uint16_t originalSearchRange = roundDownToPowerOfTwo(segCount); |
| 318 | uint16_t searchRange = clampTo<uint16_t>(2 * originalSearchRange); // searchRange: "2 x (2**floor(log2(segCount)))" |
| 319 | append16(searchRange); |
| 320 | append16(integralLog2(originalSearchRange)); // entrySelector: "log2(searchRange/2)" |
| 321 | append16(clampTo<uint16_t>((2 * segCount) - searchRange)); // rangeShift: "2 x segCount - searchRange" |
| 322 | |
| 323 | // Ending character codes |
| 324 | for (auto& mapping : bmpMappings) |
| 325 | append16(mapping.first); // startCharCode |
| 326 | append16(0xFFFF); |
| 327 | |
| 328 | append16(0); // reserved |
| 329 | |
| 330 | // Starting character codes |
| 331 | for (auto& mapping : bmpMappings) |
| 332 | append16(mapping.first); // startCharCode |
| 333 | append16(0xFFFF); |
| 334 | |
| 335 | // idDelta |
| 336 | for (auto& mapping : bmpMappings) |
| 337 | append16(static_cast<uint16_t>(mapping.second) - static_cast<uint16_t>(mapping.first)); // startCharCode |
| 338 | append16(0x0001); |
| 339 | |
| 340 | // idRangeOffset |
| 341 | for (size_t i = 0; i < bmpMappings.size(); ++i) |
| 342 | append16(0); // startCharCode |
| 343 | append16(0); |
| 344 | |
| 345 | // Fonts strive to hold 2^16 glyphs, but with the current encoding scheme, we write 8 bytes per codepoint into this subtable. |
| 346 | // 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. |
| 347 | // FIXME: If we hit this limit in the wild, use a more compact encoding scheme for this subtable. |
| 348 | overwrite16(subtableLocation + 2, clampTo<uint16_t>(m_result.size() - subtableLocation)); |
| 349 | } |
| 350 | |
| 351 | void SVGToOTFFontConverter::appendCMAPTable() |
| 352 | { |
| 353 | auto startingOffset = m_result.size(); |
| 354 | append16(0); |
| 355 | append16(3); // Number of subtables |
| 356 | |
| 357 | append16(0); // Unicode |
| 358 | append16(3); // Unicode version 2.2+ |
| 359 | append32(28); // Byte offset of subtable |
| 360 | |
| 361 | append16(3); // Microsoft |
| 362 | append16(1); // Unicode BMP |
| 363 | auto format4OffsetLocation = m_result.size(); |
| 364 | append32(0); // Byte offset of subtable |
| 365 | |
| 366 | append16(3); // Microsoft |
| 367 | append16(10); // Unicode |
| 368 | append32(28); // Byte offset of subtable |
| 369 | |
| 370 | Vector<std::pair<UChar32, Glyph>> mappings; |
| 371 | UChar32 previousCodepoint = std::numeric_limits<UChar32>::max(); |
| 372 | for (size_t i = 0; i < m_glyphs.size(); ++i) { |
| 373 | auto& glyph = m_glyphs[i]; |
| 374 | UChar32 codepoint; |
| 375 | auto codePoints = StringView(glyph.codepoints).codePoints(); |
| 376 | auto iterator = codePoints.begin(); |
| 377 | if (iterator == codePoints.end()) |
| 378 | codepoint = 0; |
| 379 | else { |
| 380 | codepoint = *iterator; |
| 381 | ++iterator; |
| 382 | // Don't map ligatures here. |
| 383 | if (iterator != codePoints.end() || codepoint == previousCodepoint) |
| 384 | continue; |
| 385 | } |
| 386 | |
| 387 | mappings.append(std::make_pair(codepoint, Glyph(i))); |
| 388 | previousCodepoint = codepoint; |
| 389 | } |
| 390 | |
| 391 | appendFormat12CMAPTable(mappings); |
| 392 | |
| 393 | Vector<std::pair<UChar32, Glyph>> bmpMappings; |
| 394 | for (auto& mapping : mappings) { |
| 395 | if (mapping.first < 0x10000) |
| 396 | bmpMappings.append(mapping); |
| 397 | } |
| 398 | overwrite32(format4OffsetLocation, m_result.size() - startingOffset); |
| 399 | appendFormat4CMAPTable(bmpMappings); |
| 400 | } |
| 401 | |
| 402 | void SVGToOTFFontConverter::appendHEADTable() |
| 403 | { |
| 404 | append32(0x00010000); // Version |
| 405 | append32(0x00010000); // Revision |
| 406 | append32(0); // Checksum placeholder; to be overwritten by the caller. |
| 407 | append32(0x5F0F3CF5); // Magic number. |
| 408 | append16((1 << 9) | 1); |
| 409 | |
| 410 | append16(s_outputUnitsPerEm); |
| 411 | append32(0); // First half of creation date |
| 412 | append32(0); // Last half of creation date |
| 413 | append32(0); // First half of modification date |
| 414 | append32(0); // Last half of modification date |
| 415 | append16(clampTo<int16_t>(m_boundingBox.x())); |
| 416 | append16(clampTo<int16_t>(m_boundingBox.y())); |
| 417 | append16(clampTo<int16_t>(m_boundingBox.maxX())); |
| 418 | append16(clampTo<int16_t>(m_boundingBox.maxY())); |
| 419 | append16((m_italic ? 1 << 1 : 0) | (m_weight >= 7 ? 1 : 0)); |
| 420 | append16(3); // Smallest readable size in pixels |
| 421 | append16(0); // Might contain LTR or RTL glyphs |
| 422 | append16(0); // Short offsets in the 'loca' table. However, CFF fonts don't have a 'loca' table so this is irrelevant |
| 423 | append16(0); // Glyph data format |
| 424 | } |
| 425 | |
| 426 | void SVGToOTFFontConverter::appendHHEATable() |
| 427 | { |
| 428 | append32(0x00010000); // Version |
| 429 | append16(clampTo<int16_t>(m_ascent)); |
| 430 | append16(clampTo<int16_t>(-m_descent)); |
| 431 | // WebKit SVG font rendering has hard coded the line gap to be 1/10th of the font size since 2008 (see r29719). |
| 432 | append16(clampTo<int16_t>(m_lineGap)); |
| 433 | append16(clampTo<uint16_t>(m_advanceWidthMax)); |
| 434 | append16(clampTo<int16_t>(m_boundingBox.x())); // Minimum left side bearing |
| 435 | append16(clampTo<int16_t>(m_minRightSideBearing)); // Minimum right side bearing |
| 436 | append16(clampTo<int16_t>(m_boundingBox.maxX())); // X maximum extent |
| 437 | // Since WebKit draws the caret and ignores the following values, it doesn't matter what we set them to. |
| 438 | append16(1); // Vertical caret |
| 439 | append16(0); // Vertical caret |
| 440 | append16(0); // "Set value to 0 for non-slanted fonts" |
| 441 | append32(0); // Reserved |
| 442 | append32(0); // Reserved |
| 443 | append16(0); // Current format |
| 444 | append16(m_glyphs.size()); // Number of advance widths in HMTX table |
| 445 | } |
| 446 | |
| 447 | void SVGToOTFFontConverter::appendHMTXTable() |
| 448 | { |
| 449 | for (auto& glyph : m_glyphs) { |
| 450 | append16(clampTo<uint16_t>(glyph.horizontalAdvance)); |
| 451 | append16(clampTo<int16_t>(glyph.boundingBox.x())); |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | void SVGToOTFFontConverter::appendMAXPTable() |
| 456 | { |
| 457 | append32(0x00010000); // Version |
| 458 | append16(m_glyphs.size()); |
| 459 | append16(0xFFFF); // Maximum number of points in non-compound glyph |
| 460 | append16(0xFFFF); // Maximum number of contours in non-compound glyph |
| 461 | append16(0xFFFF); // Maximum number of points in compound glyph |
| 462 | append16(0xFFFF); // Maximum number of contours in compound glyph |
| 463 | append16(2); // Maximum number of zones |
| 464 | append16(0); // Maximum number of points used in zone 0 |
| 465 | append16(0); // Maximum number of storage area locations |
| 466 | append16(0); // Maximum number of function definitions |
| 467 | append16(0); // Maximum number of instruction definitions |
| 468 | append16(0); // Maximum stack depth |
| 469 | append16(0); // Maximum size of instructions |
| 470 | append16(m_glyphs.size()); // Maximum number of glyphs referenced at top level |
| 471 | append16(0); // No compound glyphs |
| 472 | } |
| 473 | |
| 474 | void SVGToOTFFontConverter::appendNAMETable() |
| 475 | { |
| 476 | append16(0); // Format selector |
| 477 | append16(1); // Number of name records in table |
| 478 | append16(18); // Offset in bytes to the beginning of name character strings |
| 479 | |
| 480 | append16(0); // Unicode |
| 481 | append16(3); // Unicode version 2.0 or later |
| 482 | append16(0); // Language |
| 483 | append16(1); // Name identifier. 1 = Font family |
| 484 | append16(m_fontFamily.length() * 2); |
| 485 | append16(0); // Offset into name data |
| 486 | |
| 487 | for (auto codeUnit : StringView(m_fontFamily).codeUnits()) |
| 488 | append16(codeUnit); |
| 489 | } |
| 490 | |
| 491 | void SVGToOTFFontConverter::appendOS2Table() |
| 492 | { |
| 493 | int16_t averageAdvance = s_outputUnitsPerEm; |
| 494 | bool ok; |
| 495 | int value = m_fontElement.attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr).toInt(&ok); |
| 496 | if (!ok && m_missingGlyphElement) |
| 497 | value = m_missingGlyphElement->attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr).toInt(&ok); |
| 498 | value = scaleUnitsPerEm(value); |
| 499 | if (ok) |
| 500 | averageAdvance = clampTo<int16_t>(value); |
| 501 | |
| 502 | append16(2); // Version |
| 503 | append16(clampTo<int16_t>(averageAdvance)); |
| 504 | append16(m_weight); // Weight class |
| 505 | append16(5); // Width class |
| 506 | append16(0); // Protected font |
| 507 | // WebKit handles these superscripts and subscripts |
| 508 | append16(0); // Subscript X Size |
| 509 | append16(0); // Subscript Y Size |
| 510 | append16(0); // Subscript X Offset |
| 511 | append16(0); // Subscript Y Offset |
| 512 | append16(0); // Superscript X Size |
| 513 | append16(0); // Superscript Y Size |
| 514 | append16(0); // Superscript X Offset |
| 515 | append16(0); // Superscript Y Offset |
| 516 | append16(0); // Strikeout width |
| 517 | append16(0); // Strikeout Position |
| 518 | append16(0); // No classification |
| 519 | |
| 520 | unsigned numPanoseBytes = 0; |
| 521 | const unsigned panoseSize = 10; |
| 522 | char panoseBytes[panoseSize]; |
| 523 | if (m_fontFaceElement) { |
| 524 | Vector<String> segments = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::panose_1Attr).string().split(' '); |
| 525 | if (segments.size() == panoseSize) { |
| 526 | for (auto& segment : segments) { |
| 527 | bool ok; |
| 528 | int value = segment.toInt(&ok); |
| 529 | if (ok && value >= std::numeric_limits<uint8_t>::min() && value <= std::numeric_limits<uint8_t>::max()) |
| 530 | panoseBytes[numPanoseBytes++] = value; |
| 531 | } |
| 532 | } |
| 533 | } |
| 534 | if (numPanoseBytes != panoseSize) |
| 535 | memset(panoseBytes, 0, panoseSize); |
| 536 | m_result.append(panoseBytes, panoseSize); |
| 537 | |
| 538 | for (int i = 0; i < 4; ++i) |
| 539 | append32(0); // "Bit assignments are pending. Set to 0" |
| 540 | append32(0x544B4257); // Font Vendor. "WBKT" |
| 541 | append16((m_weight >= 7 ? 1 << 5 : 0) | (m_italic ? 1 : 0)); // Font Patterns. |
| 542 | append16(0); // First unicode index |
| 543 | append16(0xFFFF); // Last unicode index |
| 544 | append16(clampTo<int16_t>(m_ascent)); // Typographical ascender |
| 545 | append16(clampTo<int16_t>(-m_descent)); // Typographical descender |
| 546 | append16(clampTo<int16_t>(m_lineGap)); // Typographical line gap |
| 547 | append16(clampTo<uint16_t>(m_ascent)); // Windows-specific ascent |
| 548 | append16(clampTo<uint16_t>(m_descent)); // Windows-specific descent |
| 549 | append32(0xFF10FC07); // Bitmask for supported codepages (Part 1). Report all pages as supported. |
| 550 | append32(0x0000FFFF); // Bitmask for supported codepages (Part 2). Report all pages as supported. |
| 551 | append16(clampTo<int16_t>(m_xHeight)); // x-height |
| 552 | append16(clampTo<int16_t>(m_capHeight)); // Cap-height |
| 553 | append16(0); // Default char |
| 554 | append16(' '); // Break character |
| 555 | append16(3); // Maximum context needed to perform font features |
| 556 | append16(3); // Smallest optical point size |
| 557 | append16(0xFFFF); // Largest optical point size |
| 558 | } |
| 559 | |
| 560 | void SVGToOTFFontConverter::appendPOSTTable() |
| 561 | { |
| 562 | append32(0x00030000); // Format. Printing is undefined |
| 563 | append32(0); // Italic angle in degrees |
| 564 | append16(0); // Underline position |
| 565 | append16(0); // Underline thickness |
| 566 | append32(0); // Monospaced |
| 567 | append32(0); // "Minimum memory usage when a TrueType font is downloaded as a Type 42 font" |
| 568 | append32(0); // "Maximum memory usage when a TrueType font is downloaded as a Type 42 font" |
| 569 | append32(0); // "Minimum memory usage when a TrueType font is downloaded as a Type 1 font" |
| 570 | append32(0); // "Maximum memory usage when a TrueType font is downloaded as a Type 1 font" |
| 571 | } |
| 572 | |
| 573 | static bool isValidStringForCFF(const String& string) |
| 574 | { |
| 575 | for (auto c : StringView(string).codeUnits()) { |
| 576 | if (c < 33 || c > 126) |
| 577 | return false; |
| 578 | } |
| 579 | return true; |
| 580 | } |
| 581 | |
| 582 | void SVGToOTFFontConverter::appendValidCFFString(const String& string) |
| 583 | { |
| 584 | ASSERT(isValidStringForCFF(string)); |
| 585 | for (auto c : StringView(string).codeUnits()) |
| 586 | m_result.append(c); |
| 587 | } |
| 588 | |
| 589 | void SVGToOTFFontConverter::appendCFFTable() |
| 590 | { |
| 591 | auto startingOffset = m_result.size(); |
| 592 | |
| 593 | // Header |
| 594 | m_result.append(1); // Major version |
| 595 | m_result.append(0); // Minor version |
| 596 | m_result.append(4); // Header size |
| 597 | m_result.append(4); // Offsets within CFF table are 4 bytes long |
| 598 | |
| 599 | // Name INDEX |
| 600 | String fontName; |
| 601 | if (m_fontFaceElement) { |
| 602 | // FIXME: fontFamily() here might not be quite what we want. |
| 603 | String potentialFontName = m_fontFamily; |
| 604 | if (isValidStringForCFF(potentialFontName)) |
| 605 | fontName = potentialFontName; |
| 606 | } |
| 607 | append16(1); // INDEX contains 1 element |
| 608 | m_result.append(4); // Offsets in this INDEX are 4 bytes long |
| 609 | append32(1); // 1-index offset of name data |
| 610 | append32(fontName.length() + 1); // 1-index offset just past end of name data |
| 611 | appendValidCFFString(fontName); |
| 612 | |
| 613 | String weight; |
| 614 | if (m_fontFaceElement) { |
| 615 | auto& potentialWeight = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_weightAttr); |
| 616 | if (isValidStringForCFF(potentialWeight)) |
| 617 | weight = potentialWeight; |
| 618 | } |
| 619 | |
| 620 | bool hasWeight = !weight.isNull(); |
| 621 | |
| 622 | const char operand32Bit = 29; |
| 623 | const char fullNameKey = 2; |
| 624 | const char familyNameKey = 3; |
| 625 | const char weightKey = 4; |
| 626 | const char fontBBoxKey = 5; |
| 627 | const char charsetIndexKey = 15; |
| 628 | const char charstringsIndexKey = 17; |
| 629 | const char privateDictIndexKey = 18; |
| 630 | const uint32_t userDefinedStringStartIndex = 391; |
| 631 | const unsigned sizeOfTopIndex = 56 + (hasWeight ? 6 : 0); |
| 632 | |
| 633 | // Top DICT INDEX. |
| 634 | append16(1); // INDEX contains 1 element |
| 635 | m_result.append(4); // Offsets in this INDEX are 4 bytes long |
| 636 | append32(1); // 1-index offset of DICT data |
| 637 | append32(1 + sizeOfTopIndex); // 1-index offset just past end of DICT data |
| 638 | |
| 639 | // DICT information |
| 640 | #if !ASSERT_DISABLED |
| 641 | unsigned topDictStart = m_result.size(); |
| 642 | #endif |
| 643 | m_result.append(operand32Bit); |
| 644 | append32(userDefinedStringStartIndex); |
| 645 | m_result.append(fullNameKey); |
| 646 | m_result.append(operand32Bit); |
| 647 | append32(userDefinedStringStartIndex); |
| 648 | m_result.append(familyNameKey); |
| 649 | if (hasWeight) { |
| 650 | m_result.append(operand32Bit); |
| 651 | append32(userDefinedStringStartIndex + 2); |
| 652 | m_result.append(weightKey); |
| 653 | } |
| 654 | m_result.append(operand32Bit); |
| 655 | append32(clampTo<int32_t>(m_boundingBox.x())); |
| 656 | m_result.append(operand32Bit); |
| 657 | append32(clampTo<int32_t>(m_boundingBox.y())); |
| 658 | m_result.append(operand32Bit); |
| 659 | append32(clampTo<int32_t>(m_boundingBox.width())); |
| 660 | m_result.append(operand32Bit); |
| 661 | append32(clampTo<int32_t>(m_boundingBox.height())); |
| 662 | m_result.append(fontBBoxKey); |
| 663 | m_result.append(operand32Bit); |
| 664 | unsigned charsetOffsetLocation = m_result.size(); |
| 665 | append32(0); // Offset of Charset info. Will be overwritten later. |
| 666 | m_result.append(charsetIndexKey); |
| 667 | m_result.append(operand32Bit); |
| 668 | unsigned charstringsOffsetLocation = m_result.size(); |
| 669 | append32(0); // Offset of CharStrings INDEX. Will be overwritten later. |
| 670 | m_result.append(charstringsIndexKey); |
| 671 | m_result.append(operand32Bit); |
| 672 | append32(0); // 0-sized private dict |
| 673 | m_result.append(operand32Bit); |
| 674 | append32(0); // no location for private dict |
| 675 | m_result.append(privateDictIndexKey); // Private dict size and offset |
| 676 | ASSERT(m_result.size() == topDictStart + sizeOfTopIndex); |
| 677 | |
| 678 | // String INDEX |
| 679 | String unknownCharacter = "UnknownChar"_s ; |
| 680 | append16(2 + (hasWeight ? 1 : 0)); // Number of elements in INDEX |
| 681 | m_result.append(4); // Offsets in this INDEX are 4 bytes long |
| 682 | uint32_t offset = 1; |
| 683 | append32(offset); |
| 684 | offset += fontName.length(); |
| 685 | append32(offset); |
| 686 | offset += unknownCharacter.length(); |
| 687 | append32(offset); |
| 688 | if (hasWeight) { |
| 689 | offset += weight.length(); |
| 690 | append32(offset); |
| 691 | } |
| 692 | appendValidCFFString(fontName); |
| 693 | appendValidCFFString(unknownCharacter); |
| 694 | appendValidCFFString(weight); |
| 695 | |
| 696 | append16(0); // Empty subroutine INDEX |
| 697 | |
| 698 | // Charset info |
| 699 | overwrite32(charsetOffsetLocation, m_result.size() - startingOffset); |
| 700 | m_result.append(0); |
| 701 | for (Glyph i = 1; i < m_glyphs.size(); ++i) |
| 702 | append16(userDefinedStringStartIndex + 1); |
| 703 | |
| 704 | // CharStrings INDEX |
| 705 | overwrite32(charstringsOffsetLocation, m_result.size() - startingOffset); |
| 706 | append16(m_glyphs.size()); |
| 707 | m_result.append(4); // Offsets in this INDEX are 4 bytes long |
| 708 | offset = 1; |
| 709 | append32(offset); |
| 710 | for (auto& glyph : m_glyphs) { |
| 711 | offset += glyph.charString.size(); |
| 712 | append32(offset); |
| 713 | } |
| 714 | for (auto& glyph : m_glyphs) |
| 715 | m_result.appendVector(glyph.charString); |
| 716 | } |
| 717 | |
| 718 | Glyph SVGToOTFFontConverter::firstGlyph(const Vector<Glyph, 1>& v, UChar32 codepoint) const |
| 719 | { |
| 720 | #if ASSERT_DISABLED |
| 721 | UNUSED_PARAM(codepoint); |
| 722 | #endif |
| 723 | ASSERT(!v.isEmpty()); |
| 724 | if (v.isEmpty()) |
| 725 | return 0; |
| 726 | #if !ASSERT_DISABLED |
| 727 | auto codePoints = StringView(m_glyphs[v[0]].codepoints).codePoints(); |
| 728 | auto codePointsIterator = codePoints.begin(); |
| 729 | ASSERT(codePointsIterator != codePoints.end()); |
| 730 | ASSERT(codepoint == *codePointsIterator); |
| 731 | #endif |
| 732 | return v[0]; |
| 733 | } |
| 734 | |
| 735 | void SVGToOTFFontConverter::appendLigatureSubtable(size_t subtableRecordLocation) |
| 736 | { |
| 737 | typedef std::pair<Vector<Glyph, 3>, Glyph> LigaturePair; |
| 738 | Vector<LigaturePair> ligaturePairs; |
| 739 | for (Glyph glyphIndex = 0; glyphIndex < m_glyphs.size(); ++glyphIndex) { |
| 740 | ligaturePairs.append(LigaturePair(Vector<Glyph, 3>(), glyphIndex)); |
| 741 | Vector<Glyph, 3>& ligatureGlyphs = ligaturePairs.last().first; |
| 742 | auto codePoints = StringView(m_glyphs[glyphIndex].codepoints).codePoints(); |
| 743 | // FIXME: https://bugs.webkit.org/show_bug.cgi?id=138592 This needs to be done in codepoint space, not glyph space |
| 744 | for (auto codePoint : codePoints) |
| 745 | ligatureGlyphs.append(firstGlyph(glyphsForCodepoint(codePoint), codePoint)); |
| 746 | if (ligatureGlyphs.size() < 2) |
| 747 | ligaturePairs.removeLast(); |
| 748 | } |
| 749 | if (ligaturePairs.size() > std::numeric_limits<uint16_t>::max()) |
| 750 | ligaturePairs.clear(); |
| 751 | std::sort(ligaturePairs.begin(), ligaturePairs.end(), [](auto& lhs, auto& rhs) { |
| 752 | return lhs.first[0] < rhs.first[0]; |
| 753 | }); |
| 754 | Vector<size_t> overlappingFirstGlyphSegmentLengths; |
| 755 | if (!ligaturePairs.isEmpty()) { |
| 756 | Glyph previousFirstGlyph = ligaturePairs[0].first[0]; |
| 757 | size_t segmentStart = 0; |
| 758 | for (size_t i = 0; i < ligaturePairs.size(); ++i) { |
| 759 | auto& ligaturePair = ligaturePairs[i]; |
| 760 | if (ligaturePair.first[0] != previousFirstGlyph) { |
| 761 | overlappingFirstGlyphSegmentLengths.append(i - segmentStart); |
| 762 | segmentStart = i; |
| 763 | previousFirstGlyph = ligaturePairs[0].first[0]; |
| 764 | } |
| 765 | } |
| 766 | overlappingFirstGlyphSegmentLengths.append(ligaturePairs.size() - segmentStart); |
| 767 | } |
| 768 | |
| 769 | overwrite16(subtableRecordLocation + 6, m_result.size() - subtableRecordLocation); |
| 770 | auto subtableLocation = m_result.size(); |
| 771 | append16(1); // Format 1 |
| 772 | append16(0); // Placeholder for offset to coverage table, relative to beginning of substitution table |
| 773 | append16(ligaturePairs.size()); // Number of LigatureSet tables |
| 774 | grow(overlappingFirstGlyphSegmentLengths.size() * 2); // Placeholder for offset to LigatureSet table |
| 775 | |
| 776 | Vector<size_t> ligatureSetTableLocations; |
| 777 | for (size_t i = 0; i < overlappingFirstGlyphSegmentLengths.size(); ++i) { |
| 778 | overwrite16(subtableLocation + 6 + 2 * i, m_result.size() - subtableLocation); |
| 779 | ligatureSetTableLocations.append(m_result.size()); |
| 780 | append16(overlappingFirstGlyphSegmentLengths[i]); // LigatureCount |
| 781 | grow(overlappingFirstGlyphSegmentLengths[i] * 2); // Placeholder for offset to Ligature table |
| 782 | } |
| 783 | ASSERT(ligatureSetTableLocations.size() == overlappingFirstGlyphSegmentLengths.size()); |
| 784 | |
| 785 | size_t ligaturePairIndex = 0; |
| 786 | for (size_t i = 0; i < overlappingFirstGlyphSegmentLengths.size(); ++i) { |
| 787 | for (size_t j = 0; j < overlappingFirstGlyphSegmentLengths[i]; ++j) { |
| 788 | overwrite16(ligatureSetTableLocations[i] + 2 + 2 * j, m_result.size() - ligatureSetTableLocations[i]); |
| 789 | auto& ligaturePair = ligaturePairs[ligaturePairIndex]; |
| 790 | append16(ligaturePair.second); |
| 791 | append16(ligaturePair.first.size()); |
| 792 | for (size_t k = 1; k < ligaturePair.first.size(); ++k) |
| 793 | append16(ligaturePair.first[k]); |
| 794 | ++ligaturePairIndex; |
| 795 | } |
| 796 | } |
| 797 | ASSERT(ligaturePairIndex == ligaturePairs.size()); |
| 798 | |
| 799 | // Coverage table |
| 800 | overwrite16(subtableLocation + 2, m_result.size() - subtableLocation); |
| 801 | append16(1); // CoverageFormat |
| 802 | append16(ligatureSetTableLocations.size()); // GlyphCount |
| 803 | ligaturePairIndex = 0; |
| 804 | for (auto segmentLength : overlappingFirstGlyphSegmentLengths) { |
| 805 | auto& ligaturePair = ligaturePairs[ligaturePairIndex]; |
| 806 | ASSERT(ligaturePair.first.size() > 1); |
| 807 | append16(ligaturePair.first[0]); |
| 808 | ligaturePairIndex += segmentLength; |
| 809 | } |
| 810 | } |
| 811 | |
| 812 | void SVGToOTFFontConverter::appendArabicReplacementSubtable(size_t subtableRecordLocation, const char arabicForm[]) |
| 813 | { |
| 814 | Vector<std::pair<Glyph, Glyph>> arabicFinalReplacements; |
| 815 | for (auto& pair : m_codepointsToIndicesMap) { |
| 816 | for (auto glyphIndex : pair.value) { |
| 817 | auto& glyph = m_glyphs[glyphIndex]; |
| 818 | if (glyph.glyphElement && equalIgnoringASCIICase(glyph.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), arabicForm)) |
| 819 | arabicFinalReplacements.append(std::make_pair(pair.value[0], glyphIndex)); |
| 820 | } |
| 821 | } |
| 822 | if (arabicFinalReplacements.size() > std::numeric_limits<uint16_t>::max()) |
| 823 | arabicFinalReplacements.clear(); |
| 824 | |
| 825 | overwrite16(subtableRecordLocation + 6, m_result.size() - subtableRecordLocation); |
| 826 | auto subtableLocation = m_result.size(); |
| 827 | append16(2); // Format 2 |
| 828 | Placeholder toCoverageTable = placeholder(subtableLocation); |
| 829 | append16(arabicFinalReplacements.size()); // GlyphCount |
| 830 | for (auto& pair : arabicFinalReplacements) |
| 831 | append16(pair.second); |
| 832 | |
| 833 | toCoverageTable.populate(); |
| 834 | append16(1); // CoverageFormat |
| 835 | append16(arabicFinalReplacements.size()); // GlyphCount |
| 836 | for (auto& pair : arabicFinalReplacements) |
| 837 | append16(pair.first); |
| 838 | } |
| 839 | |
| 840 | void SVGToOTFFontConverter::appendScriptSubtable(unsigned featureCount) |
| 841 | { |
| 842 | auto dfltScriptTableLocation = m_result.size(); |
| 843 | append16(0); // Placeholder for offset of default language system table, relative to beginning of Script table |
| 844 | append16(0); // Number of following language system tables |
| 845 | |
| 846 | // LangSys table |
| 847 | overwrite16(dfltScriptTableLocation, m_result.size() - dfltScriptTableLocation); |
| 848 | append16(0); // LookupOrder "= NULL ... reserved" |
| 849 | append16(0xFFFF); // No features are required |
| 850 | append16(featureCount); // Number of FeatureIndex values |
| 851 | for (uint16_t i = 0; i < featureCount; ++i) |
| 852 | append16(m_featureCountGSUB++); // Features indices |
| 853 | } |
| 854 | |
| 855 | void SVGToOTFFontConverter::appendGSUBTable() |
| 856 | { |
| 857 | auto tableLocation = m_result.size(); |
| 858 | auto = 10; |
| 859 | |
| 860 | append32(0x00010000); // Version |
| 861 | append16(headerSize); // Offset to ScriptList |
| 862 | Placeholder toFeatureList = placeholder(tableLocation); |
| 863 | Placeholder toLookupList = placeholder(tableLocation); |
| 864 | ASSERT(tableLocation + headerSize == m_result.size()); |
| 865 | |
| 866 | // ScriptList |
| 867 | auto scriptListLocation = m_result.size(); |
| 868 | append16(2); // Number of ScriptRecords |
| 869 | append32BitCode("DFLT" ); |
| 870 | append16(0); // Placeholder for offset of Script table, relative to beginning of ScriptList |
| 871 | append32BitCode("arab" ); |
| 872 | append16(0); // Placeholder for offset of Script table, relative to beginning of ScriptList |
| 873 | |
| 874 | overwrite16(scriptListLocation + 6, m_result.size() - scriptListLocation); |
| 875 | appendScriptSubtable(1); |
| 876 | overwrite16(scriptListLocation + 12, m_result.size() - scriptListLocation); |
| 877 | appendScriptSubtable(4); |
| 878 | |
| 879 | const unsigned featureCount = 5; |
| 880 | |
| 881 | // FeatureList |
| 882 | toFeatureList.populate(); |
| 883 | auto featureListLocation = m_result.size(); |
| 884 | size_t featureListSize = 2 + 6 * featureCount; |
| 885 | size_t featureTableSize = 6; |
| 886 | append16(featureCount); // FeatureCount |
| 887 | append32BitCode("liga" ); |
| 888 | append16(featureListSize + featureTableSize * 0); // Offset of feature table, relative to beginning of FeatureList table |
| 889 | append32BitCode("fina" ); |
| 890 | append16(featureListSize + featureTableSize * 1); // Offset of feature table, relative to beginning of FeatureList table |
| 891 | append32BitCode("medi" ); |
| 892 | append16(featureListSize + featureTableSize * 2); // Offset of feature table, relative to beginning of FeatureList table |
| 893 | append32BitCode("init" ); |
| 894 | append16(featureListSize + featureTableSize * 3); // Offset of feature table, relative to beginning of FeatureList table |
| 895 | append32BitCode("rlig" ); |
| 896 | append16(featureListSize + featureTableSize * 4); // Offset of feature table, relative to beginning of FeatureList table |
| 897 | ASSERT_UNUSED(featureListLocation, featureListLocation + featureListSize == m_result.size()); |
| 898 | |
| 899 | for (unsigned i = 0; i < featureCount; ++i) { |
| 900 | auto featureTableStart = m_result.size(); |
| 901 | append16(0); // FeatureParams "= NULL ... reserved" |
| 902 | append16(1); // LookupCount |
| 903 | append16(i); // LookupListIndex |
| 904 | ASSERT_UNUSED(featureTableStart, featureTableStart + featureTableSize == m_result.size()); |
| 905 | } |
| 906 | |
| 907 | // LookupList |
| 908 | toLookupList.populate(); |
| 909 | auto lookupListLocation = m_result.size(); |
| 910 | append16(featureCount); // LookupCount |
| 911 | for (unsigned i = 0; i < featureCount; ++i) |
| 912 | append16(0); // Placeholder for offset to feature table, relative to beginning of LookupList |
| 913 | size_t subtableRecordLocations[featureCount]; |
| 914 | for (unsigned i = 0; i < featureCount; ++i) { |
| 915 | subtableRecordLocations[i] = m_result.size(); |
| 916 | overwrite16(lookupListLocation + 2 + 2 * i, m_result.size() - lookupListLocation); |
| 917 | switch (i) { |
| 918 | case 4: |
| 919 | append16(3); // Type 3: "Replace one glyph with one of many glyphs" |
| 920 | break; |
| 921 | case 0: |
| 922 | append16(4); // Type 4: "Replace multiple glyphs with one glyph" |
| 923 | break; |
| 924 | default: |
| 925 | append16(1); // Type 1: "Replace one glyph with one glyph" |
| 926 | break; |
| 927 | } |
| 928 | append16(0); // LookupFlag |
| 929 | append16(1); // SubTableCount |
| 930 | append16(0); // Placeholder for offset to subtable, relative to beginning of Lookup table |
| 931 | } |
| 932 | |
| 933 | appendLigatureSubtable(subtableRecordLocations[0]); |
| 934 | appendArabicReplacementSubtable(subtableRecordLocations[1], "terminal" ); |
| 935 | appendArabicReplacementSubtable(subtableRecordLocations[2], "medial" ); |
| 936 | appendArabicReplacementSubtable(subtableRecordLocations[3], "initial" ); |
| 937 | |
| 938 | // Manually append empty "rlig" subtable |
| 939 | overwrite16(subtableRecordLocations[4] + 6, m_result.size() - subtableRecordLocations[4]); |
| 940 | append16(1); // Format 1 |
| 941 | append16(6); // offset to coverage table, relative to beginning of substitution table |
| 942 | append16(0); // AlternateSetCount |
| 943 | append16(1); // CoverageFormat |
| 944 | append16(0); // GlyphCount |
| 945 | } |
| 946 | |
| 947 | void SVGToOTFFontConverter::appendVORGTable() |
| 948 | { |
| 949 | append16(1); // Major version |
| 950 | append16(0); // Minor version |
| 951 | |
| 952 | bool ok; |
| 953 | int defaultVerticalOriginY = m_fontElement.attributeWithoutSynchronization(SVGNames::vert_origin_yAttr).toInt(&ok); |
| 954 | if (!ok && m_missingGlyphElement) |
| 955 | defaultVerticalOriginY = m_missingGlyphElement->attributeWithoutSynchronization(SVGNames::vert_origin_yAttr).toInt(); |
| 956 | defaultVerticalOriginY = scaleUnitsPerEm(defaultVerticalOriginY); |
| 957 | append16(clampTo<int16_t>(defaultVerticalOriginY)); |
| 958 | |
| 959 | auto tableSizeOffset = m_result.size(); |
| 960 | append16(0); // Place to write table size. |
| 961 | for (Glyph i = 0; i < m_glyphs.size(); ++i) { |
| 962 | if (auto* glyph = m_glyphs[i].glyphElement) { |
| 963 | if (int verticalOriginY = glyph->attributeWithoutSynchronization(SVGNames::vert_origin_yAttr).toInt()) { |
| 964 | append16(i); |
| 965 | append16(clampTo<int16_t>(scaleUnitsPerEm(verticalOriginY))); |
| 966 | } |
| 967 | } |
| 968 | } |
| 969 | ASSERT(!((m_result.size() - tableSizeOffset - 2) % 4)); |
| 970 | overwrite16(tableSizeOffset, (m_result.size() - tableSizeOffset - 2) / 4); |
| 971 | } |
| 972 | |
| 973 | void SVGToOTFFontConverter::appendVHEATable() |
| 974 | { |
| 975 | float height = m_ascent + m_descent; |
| 976 | append32(0x00011000); // Version |
| 977 | append16(clampTo<int16_t>(height / 2)); // Vertical typographic ascender (vertical baseline to the right) |
| 978 | append16(clampTo<int16_t>(-static_cast<int>(height / 2))); // Vertical typographic descender |
| 979 | append16(clampTo<int16_t>(s_outputUnitsPerEm / 10)); // Vertical typographic line gap |
| 980 | // FIXME: m_unitsPerEm is almost certainly not correct |
| 981 | append16(clampTo<int16_t>(m_advanceHeightMax)); |
| 982 | append16(clampTo<int16_t>(s_outputUnitsPerEm - m_boundingBox.maxY())); // Minimum top side bearing |
| 983 | append16(clampTo<int16_t>(m_boundingBox.y())); // Minimum bottom side bearing |
| 984 | append16(clampTo<int16_t>(s_outputUnitsPerEm - m_boundingBox.y())); // Y maximum extent |
| 985 | // Since WebKit draws the caret and ignores the following values, it doesn't matter what we set them to. |
| 986 | append16(1); // Vertical caret |
| 987 | append16(0); // Vertical caret |
| 988 | append16(0); // "Set value to 0 for non-slanted fonts" |
| 989 | append32(0); // Reserved |
| 990 | append32(0); // Reserved |
| 991 | append16(0); // "Set to 0" |
| 992 | append16(m_glyphs.size()); // Number of advance heights in VMTX table |
| 993 | } |
| 994 | |
| 995 | void SVGToOTFFontConverter::appendVMTXTable() |
| 996 | { |
| 997 | for (auto& glyph : m_glyphs) { |
| 998 | append16(clampTo<uint16_t>(glyph.verticalAdvance)); |
| 999 | append16(clampTo<int16_t>(s_outputUnitsPerEm - glyph.boundingBox.maxY())); // top side bearing |
| 1000 | } |
| 1001 | } |
| 1002 | |
| 1003 | static String codepointToString(UChar32 codepoint) |
| 1004 | { |
| 1005 | UChar buffer[2]; |
| 1006 | uint8_t length = 0; |
| 1007 | UBool error = false; |
| 1008 | U16_APPEND(buffer, length, 2, codepoint, error); |
| 1009 | return error ? String() : String(buffer, length); |
| 1010 | } |
| 1011 | |
| 1012 | Vector<Glyph, 1> SVGToOTFFontConverter::glyphsForCodepoint(UChar32 codepoint) const |
| 1013 | { |
| 1014 | return m_codepointsToIndicesMap.get(codepointToString(codepoint)); |
| 1015 | } |
| 1016 | |
| 1017 | void SVGToOTFFontConverter::addCodepointRanges(const UnicodeRanges& unicodeRanges, HashSet<Glyph>& glyphSet) const |
| 1018 | { |
| 1019 | for (auto& unicodeRange : unicodeRanges) { |
| 1020 | for (auto codepoint = unicodeRange.first; codepoint <= unicodeRange.second; ++codepoint) { |
| 1021 | for (auto index : glyphsForCodepoint(codepoint)) |
| 1022 | glyphSet.add(index); |
| 1023 | } |
| 1024 | } |
| 1025 | } |
| 1026 | |
| 1027 | void SVGToOTFFontConverter::addCodepoints(const HashSet<String>& codepoints, HashSet<Glyph>& glyphSet) const |
| 1028 | { |
| 1029 | for (auto& codepointString : codepoints) { |
| 1030 | for (auto index : m_codepointsToIndicesMap.get(codepointString)) |
| 1031 | glyphSet.add(index); |
| 1032 | } |
| 1033 | } |
| 1034 | |
| 1035 | void SVGToOTFFontConverter::addGlyphNames(const HashSet<String>& glyphNames, HashSet<Glyph>& glyphSet) const |
| 1036 | { |
| 1037 | for (auto& glyphName : glyphNames) { |
| 1038 | if (Glyph glyph = m_glyphNameToIndexMap.get(glyphName)) |
| 1039 | glyphSet.add(glyph); |
| 1040 | } |
| 1041 | } |
| 1042 | |
| 1043 | void SVGToOTFFontConverter::addKerningPair(Vector<KerningData>& data, const SVGKerningPair& kerningPair) const |
| 1044 | { |
| 1045 | HashSet<Glyph> glyphSet1; |
| 1046 | HashSet<Glyph> glyphSet2; |
| 1047 | |
| 1048 | addCodepointRanges(kerningPair.unicodeRange1, glyphSet1); |
| 1049 | addCodepointRanges(kerningPair.unicodeRange2, glyphSet2); |
| 1050 | addGlyphNames(kerningPair.glyphName1, glyphSet1); |
| 1051 | addGlyphNames(kerningPair.glyphName2, glyphSet2); |
| 1052 | addCodepoints(kerningPair.unicodeName1, glyphSet1); |
| 1053 | addCodepoints(kerningPair.unicodeName2, glyphSet2); |
| 1054 | |
| 1055 | // FIXME: Use table format 2 so we don't have to append each of these one by one. |
| 1056 | for (auto& glyph1 : glyphSet1) { |
| 1057 | for (auto& glyph2 : glyphSet2) |
| 1058 | data.append(KerningData(glyph1, glyph2, clampTo<int16_t>(-scaleUnitsPerEm(kerningPair.kerning)))); |
| 1059 | } |
| 1060 | } |
| 1061 | |
| 1062 | template<typename T> inline size_t SVGToOTFFontConverter::appendKERNSubtable(bool (T::*buildKerningPair)(SVGKerningPair&) const, uint16_t coverage) |
| 1063 | { |
| 1064 | Vector<KerningData> kerningData; |
| 1065 | for (auto& element : childrenOfType<T>(m_fontElement)) { |
| 1066 | SVGKerningPair kerningPair; |
| 1067 | if ((element.*buildKerningPair)(kerningPair)) |
| 1068 | addKerningPair(kerningData, kerningPair); |
| 1069 | } |
| 1070 | return finishAppendingKERNSubtable(WTFMove(kerningData), coverage); |
| 1071 | } |
| 1072 | |
| 1073 | size_t SVGToOTFFontConverter::finishAppendingKERNSubtable(Vector<KerningData> kerningData, uint16_t coverage) |
| 1074 | { |
| 1075 | std::sort(kerningData.begin(), kerningData.end(), [](auto& a, auto& b) { |
| 1076 | return a.glyph1 < b.glyph1 || (a.glyph1 == b.glyph1 && a.glyph2 < b.glyph2); |
| 1077 | }); |
| 1078 | |
| 1079 | size_t sizeOfKerningDataTable = 14 + 6 * kerningData.size(); |
| 1080 | if (sizeOfKerningDataTable > std::numeric_limits<uint16_t>::max()) { |
| 1081 | kerningData.clear(); |
| 1082 | sizeOfKerningDataTable = 14; |
| 1083 | } |
| 1084 | |
| 1085 | append16(0); // Version of subtable |
| 1086 | append16(sizeOfKerningDataTable); // Length of this subtable |
| 1087 | append16(coverage); // Table coverage bitfield |
| 1088 | |
| 1089 | uint16_t roundedNumKerningPairs = roundDownToPowerOfTwo(kerningData.size()); |
| 1090 | |
| 1091 | append16(kerningData.size()); |
| 1092 | 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." |
| 1093 | append16(integralLog2(roundedNumKerningPairs)); // entrySelector: "log2 of the largest power of two less than or equal to the value of nPairs." |
| 1094 | append16((kerningData.size() - roundedNumKerningPairs) * 6); // rangeShift: "The value of nPairs minus the largest power of two less than or equal to nPairs, |
| 1095 | // and then multiplied by the size in bytes of an entry in the table." |
| 1096 | |
| 1097 | for (auto& kerningDataElement : kerningData) { |
| 1098 | append16(kerningDataElement.glyph1); |
| 1099 | append16(kerningDataElement.glyph2); |
| 1100 | append16(kerningDataElement.adjustment); |
| 1101 | } |
| 1102 | |
| 1103 | return sizeOfKerningDataTable; |
| 1104 | } |
| 1105 | |
| 1106 | void SVGToOTFFontConverter::appendKERNTable() |
| 1107 | { |
| 1108 | append16(0); // Version |
| 1109 | append16(2); // Number of subtables |
| 1110 | |
| 1111 | #if !ASSERT_DISABLED |
| 1112 | auto subtablesOffset = m_result.size(); |
| 1113 | #endif |
| 1114 | |
| 1115 | size_t sizeOfHorizontalSubtable = appendKERNSubtable<SVGHKernElement>(&SVGHKernElement::buildHorizontalKerningPair, 1); |
| 1116 | ASSERT_UNUSED(sizeOfHorizontalSubtable, subtablesOffset + sizeOfHorizontalSubtable == m_result.size()); |
| 1117 | size_t sizeOfVerticalSubtable = appendKERNSubtable<SVGVKernElement>(&SVGVKernElement::buildVerticalKerningPair, 0); |
| 1118 | ASSERT_UNUSED(sizeOfVerticalSubtable, subtablesOffset + sizeOfHorizontalSubtable + sizeOfVerticalSubtable == m_result.size()); |
| 1119 | } |
| 1120 | |
| 1121 | template <typename V> |
| 1122 | static void writeCFFEncodedNumber(V& vector, float number) |
| 1123 | { |
| 1124 | vector.append(0xFF); |
| 1125 | // Convert to 16.16 fixed-point |
| 1126 | append32(vector, clampTo<int32_t>(number * 0x10000)); |
| 1127 | } |
| 1128 | |
| 1129 | static const char rLineTo = 0x05; |
| 1130 | static const char rrCurveTo = 0x08; |
| 1131 | static const char endChar = 0x0e; |
| 1132 | static const char rMoveTo = 0x15; |
| 1133 | |
| 1134 | class CFFBuilder final : public SVGPathConsumer { |
| 1135 | public: |
| 1136 | CFFBuilder(Vector<char>& cffData, float width, FloatPoint origin, float unitsPerEmScalar) |
| 1137 | : m_cffData(cffData) |
| 1138 | , m_unitsPerEmScalar(unitsPerEmScalar) |
| 1139 | { |
| 1140 | writeCFFEncodedNumber(m_cffData, std::floor(width)); // hmtx table can't encode fractional FUnit values, and the CFF table needs to agree with hmtx. |
| 1141 | writeCFFEncodedNumber(m_cffData, origin.x()); |
| 1142 | writeCFFEncodedNumber(m_cffData, origin.y()); |
| 1143 | m_cffData.append(rMoveTo); |
| 1144 | } |
| 1145 | |
| 1146 | Optional<FloatRect> boundingBox() const |
| 1147 | { |
| 1148 | return m_boundingBox; |
| 1149 | } |
| 1150 | |
| 1151 | private: |
| 1152 | void updateBoundingBox(FloatPoint point) |
| 1153 | { |
| 1154 | if (!m_boundingBox) { |
| 1155 | m_boundingBox = FloatRect(point, FloatSize()); |
| 1156 | return; |
| 1157 | } |
| 1158 | m_boundingBox.value().extend(point); |
| 1159 | } |
| 1160 | |
| 1161 | void writePoint(FloatPoint destination) |
| 1162 | { |
| 1163 | updateBoundingBox(destination); |
| 1164 | |
| 1165 | FloatSize delta = destination - m_current; |
| 1166 | writeCFFEncodedNumber(m_cffData, delta.width()); |
| 1167 | writeCFFEncodedNumber(m_cffData, delta.height()); |
| 1168 | |
| 1169 | m_current = destination; |
| 1170 | } |
| 1171 | |
| 1172 | void moveTo(const FloatPoint& targetPoint, bool closed, PathCoordinateMode mode) final |
| 1173 | { |
| 1174 | if (closed && !m_cffData.isEmpty()) |
| 1175 | closePath(); |
| 1176 | |
| 1177 | FloatPoint scaledTargetPoint = FloatPoint(targetPoint.x() * m_unitsPerEmScalar, targetPoint.y() * m_unitsPerEmScalar); |
| 1178 | FloatPoint destination = mode == AbsoluteCoordinates ? scaledTargetPoint : m_current + scaledTargetPoint; |
| 1179 | |
| 1180 | writePoint(destination); |
| 1181 | m_cffData.append(rMoveTo); |
| 1182 | |
| 1183 | m_startingPoint = m_current; |
| 1184 | } |
| 1185 | |
| 1186 | void unscaledLineTo(const FloatPoint& targetPoint) |
| 1187 | { |
| 1188 | writePoint(targetPoint); |
| 1189 | m_cffData.append(rLineTo); |
| 1190 | } |
| 1191 | |
| 1192 | void lineTo(const FloatPoint& targetPoint, PathCoordinateMode mode) final |
| 1193 | { |
| 1194 | FloatPoint scaledTargetPoint = FloatPoint(targetPoint.x() * m_unitsPerEmScalar, targetPoint.y() * m_unitsPerEmScalar); |
| 1195 | FloatPoint destination = mode == AbsoluteCoordinates ? scaledTargetPoint : m_current + scaledTargetPoint; |
| 1196 | |
| 1197 | unscaledLineTo(destination); |
| 1198 | } |
| 1199 | |
| 1200 | void curveToCubic(const FloatPoint& point1, const FloatPoint& point2, const FloatPoint& point3, PathCoordinateMode mode) final |
| 1201 | { |
| 1202 | FloatPoint scaledPoint1 = FloatPoint(point1.x() * m_unitsPerEmScalar, point1.y() * m_unitsPerEmScalar); |
| 1203 | FloatPoint scaledPoint2 = FloatPoint(point2.x() * m_unitsPerEmScalar, point2.y() * m_unitsPerEmScalar); |
| 1204 | FloatPoint scaledPoint3 = FloatPoint(point3.x() * m_unitsPerEmScalar, point3.y() * m_unitsPerEmScalar); |
| 1205 | |
| 1206 | if (mode == RelativeCoordinates) { |
| 1207 | scaledPoint1 += m_current; |
| 1208 | scaledPoint2 += m_current; |
| 1209 | scaledPoint3 += m_current; |
| 1210 | } |
| 1211 | |
| 1212 | writePoint(scaledPoint1); |
| 1213 | writePoint(scaledPoint2); |
| 1214 | writePoint(scaledPoint3); |
| 1215 | m_cffData.append(rrCurveTo); |
| 1216 | } |
| 1217 | |
| 1218 | void closePath() final |
| 1219 | { |
| 1220 | if (m_current != m_startingPoint) |
| 1221 | unscaledLineTo(m_startingPoint); |
| 1222 | } |
| 1223 | |
| 1224 | void incrementPathSegmentCount() final { } |
| 1225 | bool continueConsuming() final { return true; } |
| 1226 | |
| 1227 | void lineToHorizontal(float, PathCoordinateMode) final { ASSERT_NOT_REACHED(); } |
| 1228 | void lineToVertical(float, PathCoordinateMode) final { ASSERT_NOT_REACHED(); } |
| 1229 | void curveToCubicSmooth(const FloatPoint&, const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); } |
| 1230 | void curveToQuadratic(const FloatPoint&, const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); } |
| 1231 | void curveToQuadraticSmooth(const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); } |
| 1232 | void arcTo(float, float, float, bool, bool, const FloatPoint&, PathCoordinateMode) final { ASSERT_NOT_REACHED(); } |
| 1233 | |
| 1234 | Vector<char>& m_cffData; |
| 1235 | FloatPoint m_startingPoint; |
| 1236 | FloatPoint m_current; |
| 1237 | Optional<FloatRect> m_boundingBox; |
| 1238 | float m_unitsPerEmScalar; |
| 1239 | }; |
| 1240 | |
| 1241 | Vector<char> SVGToOTFFontConverter::transcodeGlyphPaths(float width, const SVGElement& glyphOrMissingGlyphElement, Optional<FloatRect>& boundingBox) const |
| 1242 | { |
| 1243 | Vector<char> result; |
| 1244 | |
| 1245 | auto& dAttribute = glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::dAttr); |
| 1246 | if (dAttribute.isEmpty()) { |
| 1247 | writeCFFEncodedNumber(result, width); |
| 1248 | writeCFFEncodedNumber(result, 0); |
| 1249 | writeCFFEncodedNumber(result, 0); |
| 1250 | result.append(rMoveTo); |
| 1251 | result.append(endChar); |
| 1252 | return result; |
| 1253 | } |
| 1254 | |
| 1255 | // FIXME: If we are vertical, use vert_origin_x and vert_origin_y |
| 1256 | bool ok; |
| 1257 | float horizontalOriginX = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::horiz_origin_xAttr).toFloat(&ok)); |
| 1258 | if (!ok && m_fontFaceElement) |
| 1259 | horizontalOriginX = scaleUnitsPerEm(m_fontFaceElement->horizontalOriginX()); |
| 1260 | float horizontalOriginY = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::horiz_origin_yAttr).toFloat(&ok)); |
| 1261 | if (!ok && m_fontFaceElement) |
| 1262 | horizontalOriginY = scaleUnitsPerEm(m_fontFaceElement->horizontalOriginY()); |
| 1263 | |
| 1264 | CFFBuilder builder(result, width, FloatPoint(horizontalOriginX, horizontalOriginY), static_cast<float>(s_outputUnitsPerEm) / m_inputUnitsPerEm); |
| 1265 | SVGPathStringSource source(dAttribute); |
| 1266 | |
| 1267 | ok = SVGPathParser::parse(source, builder); |
| 1268 | if (!ok) |
| 1269 | return { }; |
| 1270 | |
| 1271 | boundingBox = builder.boundingBox(); |
| 1272 | |
| 1273 | result.append(endChar); |
| 1274 | return result; |
| 1275 | } |
| 1276 | |
| 1277 | void SVGToOTFFontConverter::processGlyphElement(const SVGElement& glyphOrMissingGlyphElement, const SVGGlyphElement* glyphElement, float defaultHorizontalAdvance, float defaultVerticalAdvance, const String& codepoints, Optional<FloatRect>& boundingBox) |
| 1278 | { |
| 1279 | bool ok; |
| 1280 | float horizontalAdvance = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::horiz_adv_xAttr).toFloat(&ok)); |
| 1281 | if (!ok) |
| 1282 | horizontalAdvance = defaultHorizontalAdvance; |
| 1283 | m_advanceWidthMax = std::max(m_advanceWidthMax, horizontalAdvance); |
| 1284 | float verticalAdvance = scaleUnitsPerEm(glyphOrMissingGlyphElement.attributeWithoutSynchronization(SVGNames::vert_adv_yAttr).toFloat(&ok)); |
| 1285 | if (!ok) |
| 1286 | verticalAdvance = defaultVerticalAdvance; |
| 1287 | m_advanceHeightMax = std::max(m_advanceHeightMax, verticalAdvance); |
| 1288 | |
| 1289 | Optional<FloatRect> glyphBoundingBox; |
| 1290 | auto path = transcodeGlyphPaths(horizontalAdvance, glyphOrMissingGlyphElement, glyphBoundingBox); |
| 1291 | if (!path.size()) { |
| 1292 | // It's better to use a fallback font rather than use a font without all its glyphs. |
| 1293 | m_error = true; |
| 1294 | } |
| 1295 | if (!boundingBox) |
| 1296 | boundingBox = glyphBoundingBox; |
| 1297 | else if (glyphBoundingBox) |
| 1298 | boundingBox.value().unite(glyphBoundingBox.value()); |
| 1299 | if (glyphBoundingBox) |
| 1300 | m_minRightSideBearing = std::min(m_minRightSideBearing, horizontalAdvance - glyphBoundingBox.value().maxX()); |
| 1301 | |
| 1302 | m_glyphs.append(GlyphData(WTFMove(path), glyphElement, horizontalAdvance, verticalAdvance, glyphBoundingBox.valueOr(FloatRect()), codepoints)); |
| 1303 | } |
| 1304 | |
| 1305 | void SVGToOTFFontConverter::appendLigatureGlyphs() |
| 1306 | { |
| 1307 | HashSet<UChar32> ligatureCodepoints; |
| 1308 | HashSet<UChar32> nonLigatureCodepoints; |
| 1309 | for (auto& glyph : m_glyphs) { |
| 1310 | auto codePoints = StringView(glyph.codepoints).codePoints(); |
| 1311 | auto codePointsIterator = codePoints.begin(); |
| 1312 | if (codePointsIterator == codePoints.end()) |
| 1313 | continue; |
| 1314 | UChar32 codepoint = *codePointsIterator; |
| 1315 | ++codePointsIterator; |
| 1316 | if (codePointsIterator == codePoints.end()) |
| 1317 | nonLigatureCodepoints.add(codepoint); |
| 1318 | else { |
| 1319 | ligatureCodepoints.add(codepoint); |
| 1320 | for (; codePointsIterator != codePoints.end(); ++codePointsIterator) |
| 1321 | ligatureCodepoints.add(*codePointsIterator); |
| 1322 | } |
| 1323 | } |
| 1324 | |
| 1325 | for (auto codepoint : nonLigatureCodepoints) |
| 1326 | ligatureCodepoints.remove(codepoint); |
| 1327 | for (auto codepoint : ligatureCodepoints) { |
| 1328 | auto codepoints = codepointToString(codepoint); |
| 1329 | if (!codepoints.isNull()) |
| 1330 | m_glyphs.append(GlyphData(Vector<char>(m_emptyGlyphCharString), nullptr, s_outputUnitsPerEm, s_outputUnitsPerEm, FloatRect(), codepoints)); |
| 1331 | } |
| 1332 | } |
| 1333 | |
| 1334 | bool SVGToOTFFontConverter::compareCodepointsLexicographically(const GlyphData& data1, const GlyphData& data2) |
| 1335 | { |
| 1336 | auto codePoints1 = StringView(data1.codepoints).codePoints(); |
| 1337 | auto codePoints2 = StringView(data2.codepoints).codePoints(); |
| 1338 | auto iterator1 = codePoints1.begin(); |
| 1339 | auto iterator2 = codePoints2.begin(); |
| 1340 | while (iterator1 != codePoints1.end() && iterator2 != codePoints2.end()) { |
| 1341 | UChar32 codepoint1, codepoint2; |
| 1342 | codepoint1 = *iterator1; |
| 1343 | codepoint2 = *iterator2; |
| 1344 | |
| 1345 | if (codepoint1 < codepoint2) |
| 1346 | return true; |
| 1347 | if (codepoint1 > codepoint2) |
| 1348 | return false; |
| 1349 | |
| 1350 | ++iterator1; |
| 1351 | ++iterator2; |
| 1352 | } |
| 1353 | |
| 1354 | if (iterator1 == codePoints1.end() && iterator2 == codePoints2.end()) { |
| 1355 | bool firstIsIsolated = data1.glyphElement && equalLettersIgnoringASCIICase(data1.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), "isolated" ); |
| 1356 | bool secondIsIsolated = data2.glyphElement && equalLettersIgnoringASCIICase(data2.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), "isolated" ); |
| 1357 | return firstIsIsolated && !secondIsIsolated; |
| 1358 | } |
| 1359 | return iterator1 == codePoints1.end(); |
| 1360 | } |
| 1361 | |
| 1362 | static void populateEmptyGlyphCharString(Vector<char, 17>& o, unsigned unitsPerEm) |
| 1363 | { |
| 1364 | writeCFFEncodedNumber(o, unitsPerEm); |
| 1365 | writeCFFEncodedNumber(o, 0); |
| 1366 | writeCFFEncodedNumber(o, 0); |
| 1367 | o.append(rMoveTo); |
| 1368 | o.append(endChar); |
| 1369 | } |
| 1370 | |
| 1371 | SVGToOTFFontConverter::SVGToOTFFontConverter(const SVGFontElement& fontElement) |
| 1372 | : m_fontElement(fontElement) |
| 1373 | , m_fontFaceElement(childrenOfType<SVGFontFaceElement>(m_fontElement).first()) |
| 1374 | , m_missingGlyphElement(childrenOfType<SVGMissingGlyphElement>(m_fontElement).first()) |
| 1375 | , m_advanceWidthMax(0) |
| 1376 | , m_advanceHeightMax(0) |
| 1377 | , m_minRightSideBearing(std::numeric_limits<float>::max()) |
| 1378 | , m_featureCountGSUB(0) |
| 1379 | , m_tablesAppendedCount(0) |
| 1380 | , m_weight(5) |
| 1381 | , m_italic(false) |
| 1382 | { |
| 1383 | if (!m_fontFaceElement) { |
| 1384 | m_inputUnitsPerEm = 1; |
| 1385 | m_ascent = s_outputUnitsPerEm; |
| 1386 | m_descent = 1; |
| 1387 | m_xHeight = s_outputUnitsPerEm; |
| 1388 | m_capHeight = m_ascent; |
| 1389 | } else { |
| 1390 | m_inputUnitsPerEm = m_fontFaceElement->unitsPerEm(); |
| 1391 | m_ascent = scaleUnitsPerEm(m_fontFaceElement->ascent()); |
| 1392 | m_descent = scaleUnitsPerEm(m_fontFaceElement->descent()); |
| 1393 | m_xHeight = scaleUnitsPerEm(m_fontFaceElement->xHeight()); |
| 1394 | m_capHeight = scaleUnitsPerEm(m_fontFaceElement->capHeight()); |
| 1395 | |
| 1396 | // Some platforms, including OS X, use 0 ascent and descent to mean that the platform should synthesize |
| 1397 | // a value based on a heuristic. However, SVG fonts can legitimately have 0 for ascent or descent. |
| 1398 | // Specifing a single FUnit gets us as close to 0 as we can without triggering the synthesis. |
| 1399 | if (!m_ascent) |
| 1400 | m_ascent = 1; |
| 1401 | if (!m_descent) |
| 1402 | m_descent = 1; |
| 1403 | } |
| 1404 | |
| 1405 | float defaultHorizontalAdvance = m_fontFaceElement ? scaleUnitsPerEm(m_fontFaceElement->horizontalAdvanceX()) : 0; |
| 1406 | float defaultVerticalAdvance = m_fontFaceElement ? scaleUnitsPerEm(m_fontFaceElement->verticalAdvanceY()) : 0; |
| 1407 | |
| 1408 | m_lineGap = s_outputUnitsPerEm / 10; |
| 1409 | |
| 1410 | populateEmptyGlyphCharString(m_emptyGlyphCharString, s_outputUnitsPerEm); |
| 1411 | |
| 1412 | Optional<FloatRect> boundingBox; |
| 1413 | if (m_missingGlyphElement) |
| 1414 | processGlyphElement(*m_missingGlyphElement, nullptr, defaultHorizontalAdvance, defaultVerticalAdvance, String(), boundingBox); |
| 1415 | else { |
| 1416 | m_glyphs.append(GlyphData(Vector<char>(m_emptyGlyphCharString), nullptr, s_outputUnitsPerEm, s_outputUnitsPerEm, FloatRect(), String())); |
| 1417 | boundingBox = FloatRect(0, 0, s_outputUnitsPerEm, s_outputUnitsPerEm); |
| 1418 | } |
| 1419 | |
| 1420 | for (auto& glyphElement : childrenOfType<SVGGlyphElement>(m_fontElement)) { |
| 1421 | auto& unicodeAttribute = glyphElement.attributeWithoutSynchronization(SVGNames::unicodeAttr); |
| 1422 | if (!unicodeAttribute.isEmpty()) // If we can never actually trigger this glyph, ignore it completely |
| 1423 | processGlyphElement(glyphElement, &glyphElement, defaultHorizontalAdvance, defaultVerticalAdvance, unicodeAttribute, boundingBox); |
| 1424 | } |
| 1425 | |
| 1426 | m_boundingBox = boundingBox.valueOr(FloatRect()); |
| 1427 | |
| 1428 | appendLigatureGlyphs(); |
| 1429 | |
| 1430 | if (m_glyphs.size() > std::numeric_limits<Glyph>::max()) { |
| 1431 | m_glyphs.clear(); |
| 1432 | return; |
| 1433 | } |
| 1434 | |
| 1435 | std::sort(m_glyphs.begin(), m_glyphs.end(), &compareCodepointsLexicographically); |
| 1436 | |
| 1437 | for (Glyph i = 0; i < m_glyphs.size(); ++i) { |
| 1438 | GlyphData& glyph = m_glyphs[i]; |
| 1439 | if (glyph.glyphElement) { |
| 1440 | auto& glyphName = glyph.glyphElement->attributeWithoutSynchronization(SVGNames::glyph_nameAttr); |
| 1441 | if (!glyphName.isNull()) |
| 1442 | m_glyphNameToIndexMap.add(glyphName, i); |
| 1443 | } |
| 1444 | if (m_codepointsToIndicesMap.isValidKey(glyph.codepoints)) { |
| 1445 | auto& glyphVector = m_codepointsToIndicesMap.add(glyph.codepoints, Vector<Glyph>()).iterator->value; |
| 1446 | // Prefer isolated arabic forms |
| 1447 | if (glyph.glyphElement && equalLettersIgnoringASCIICase(glyph.glyphElement->attributeWithoutSynchronization(SVGNames::arabic_formAttr), "isolated" )) |
| 1448 | glyphVector.insert(0, i); |
| 1449 | else |
| 1450 | glyphVector.append(i); |
| 1451 | } |
| 1452 | } |
| 1453 | |
| 1454 | // FIXME: Handle commas. |
| 1455 | if (m_fontFaceElement) { |
| 1456 | auto& fontWeightAttribute = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_weightAttr); |
| 1457 | for (auto segment : StringView(fontWeightAttribute).split(' ')) { |
| 1458 | if (equalLettersIgnoringASCIICase(segment, "bold" )) { |
| 1459 | m_weight = 7; |
| 1460 | break; |
| 1461 | } |
| 1462 | bool ok; |
| 1463 | int value = segment.toInt(ok); |
| 1464 | if (ok && value >= 0 && value < 1000) { |
| 1465 | m_weight = std::max(std::min((value + 50) / 100, static_cast<int>(std::numeric_limits<uint8_t>::max())), static_cast<int>(std::numeric_limits<uint8_t>::min())); |
| 1466 | break; |
| 1467 | } |
| 1468 | } |
| 1469 | auto& fontStyleAttribute = m_fontFaceElement->attributeWithoutSynchronization(SVGNames::font_styleAttr); |
| 1470 | for (auto segment : StringView(fontStyleAttribute).split(' ')) { |
| 1471 | if (equalLettersIgnoringASCIICase(segment, "italic" ) || equalLettersIgnoringASCIICase(segment, "oblique" )) { |
| 1472 | m_italic = true; |
| 1473 | break; |
| 1474 | } |
| 1475 | } |
| 1476 | } |
| 1477 | |
| 1478 | if (m_fontFaceElement) |
| 1479 | m_fontFamily = m_fontFaceElement->fontFamily(); |
| 1480 | } |
| 1481 | |
| 1482 | static inline bool isFourByteAligned(size_t x) |
| 1483 | { |
| 1484 | return !(x & 3); |
| 1485 | } |
| 1486 | |
| 1487 | uint32_t SVGToOTFFontConverter::calculateChecksum(size_t startingOffset, size_t endingOffset) const |
| 1488 | { |
| 1489 | ASSERT(isFourByteAligned(endingOffset - startingOffset)); |
| 1490 | uint32_t sum = 0; |
| 1491 | for (size_t offset = startingOffset; offset < endingOffset; offset += 4) { |
| 1492 | sum += static_cast<unsigned char>(m_result[offset + 3]) |
| 1493 | | (static_cast<unsigned char>(m_result[offset + 2]) << 8) |
| 1494 | | (static_cast<unsigned char>(m_result[offset + 1]) << 16) |
| 1495 | | (static_cast<unsigned char>(m_result[offset]) << 24); |
| 1496 | } |
| 1497 | return sum; |
| 1498 | } |
| 1499 | |
| 1500 | void SVGToOTFFontConverter::appendTable(const char identifier[4], FontAppendingFunction appendingFunction) |
| 1501 | { |
| 1502 | size_t offset = m_result.size(); |
| 1503 | ASSERT(isFourByteAligned(offset)); |
| 1504 | (this->*appendingFunction)(); |
| 1505 | size_t unpaddedSize = m_result.size() - offset; |
| 1506 | while (!isFourByteAligned(m_result.size())) |
| 1507 | m_result.append(0); |
| 1508 | ASSERT(isFourByteAligned(m_result.size())); |
| 1509 | size_t directoryEntryOffset = headerSize + m_tablesAppendedCount * directoryEntrySize; |
| 1510 | m_result[directoryEntryOffset] = identifier[0]; |
| 1511 | m_result[directoryEntryOffset + 1] = identifier[1]; |
| 1512 | m_result[directoryEntryOffset + 2] = identifier[2]; |
| 1513 | m_result[directoryEntryOffset + 3] = identifier[3]; |
| 1514 | overwrite32(directoryEntryOffset + 4, calculateChecksum(offset, m_result.size())); |
| 1515 | overwrite32(directoryEntryOffset + 8, offset); |
| 1516 | overwrite32(directoryEntryOffset + 12, unpaddedSize); |
| 1517 | ++m_tablesAppendedCount; |
| 1518 | } |
| 1519 | |
| 1520 | bool SVGToOTFFontConverter::convertSVGToOTFFont() |
| 1521 | { |
| 1522 | if (m_glyphs.isEmpty()) |
| 1523 | return false; |
| 1524 | |
| 1525 | uint16_t numTables = 14; |
| 1526 | uint16_t roundedNumTables = roundDownToPowerOfTwo(numTables); |
| 1527 | uint16_t searchRange = roundedNumTables * 16; // searchRange: "(Maximum power of 2 <= numTables) x 16." |
| 1528 | |
| 1529 | m_result.append('O'); |
| 1530 | m_result.append('T'); |
| 1531 | m_result.append('T'); |
| 1532 | m_result.append('O'); |
| 1533 | append16(numTables); |
| 1534 | append16(searchRange); |
| 1535 | append16(integralLog2(roundedNumTables)); // entrySelector: "Log2(maximum power of 2 <= numTables)." |
| 1536 | append16(numTables * 16 - searchRange); // rangeShift: "NumTables x 16-searchRange." |
| 1537 | |
| 1538 | ASSERT(m_result.size() == headerSize); |
| 1539 | |
| 1540 | // Leave space for the directory entries. |
| 1541 | for (size_t i = 0; i < directoryEntrySize * numTables; ++i) |
| 1542 | m_result.append(0); |
| 1543 | |
| 1544 | appendTable("CFF " , &SVGToOTFFontConverter::appendCFFTable); |
| 1545 | appendTable("GSUB" , &SVGToOTFFontConverter::appendGSUBTable); |
| 1546 | appendTable("OS/2" , &SVGToOTFFontConverter::appendOS2Table); |
| 1547 | appendTable("VORG" , &SVGToOTFFontConverter::appendVORGTable); |
| 1548 | appendTable("cmap" , &SVGToOTFFontConverter::appendCMAPTable); |
| 1549 | auto headTableOffset = m_result.size(); |
| 1550 | appendTable("head" , &SVGToOTFFontConverter::appendHEADTable); |
| 1551 | appendTable("hhea" , &SVGToOTFFontConverter::appendHHEATable); |
| 1552 | appendTable("hmtx" , &SVGToOTFFontConverter::appendHMTXTable); |
| 1553 | appendTable("kern" , &SVGToOTFFontConverter::appendKERNTable); |
| 1554 | appendTable("maxp" , &SVGToOTFFontConverter::appendMAXPTable); |
| 1555 | appendTable("name" , &SVGToOTFFontConverter::appendNAMETable); |
| 1556 | appendTable("post" , &SVGToOTFFontConverter::appendPOSTTable); |
| 1557 | appendTable("vhea" , &SVGToOTFFontConverter::appendVHEATable); |
| 1558 | appendTable("vmtx" , &SVGToOTFFontConverter::appendVMTXTable); |
| 1559 | |
| 1560 | ASSERT(numTables == m_tablesAppendedCount); |
| 1561 | |
| 1562 | // checksumAdjustment: "To compute: set it to 0, calculate the checksum for the 'head' table and put it in the table directory, |
| 1563 | // sum the entire font as uint32, then store B1B0AFBA - sum. The checksum for the 'head' table will now be wrong. That is OK." |
| 1564 | overwrite32(headTableOffset + 8, 0xB1B0AFBAU - calculateChecksum(0, m_result.size())); |
| 1565 | return true; |
| 1566 | } |
| 1567 | |
| 1568 | Optional<Vector<char>> convertSVGToOTFFont(const SVGFontElement& element) |
| 1569 | { |
| 1570 | SVGToOTFFontConverter converter(element); |
| 1571 | if (converter.error()) |
| 1572 | return WTF::nullopt; |
| 1573 | if (!converter.convertSVGToOTFFont()) |
| 1574 | return WTF::nullopt; |
| 1575 | return converter.releaseResult(); |
| 1576 | } |
| 1577 | |
| 1578 | } |
| 1579 | |
| 1580 | #endif // ENABLE(SVG_FONTS) |
| 1581 | |