| /* |
| * Copyright (C) 2015-2021 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 "FontCache.h" |
| |
| #include "Color.h" |
| #include "Font.h" |
| #include "FontCascadeDescription.h" |
| #include "FontCreationContext.h" |
| #include "FontFamilySpecificationCoreText.h" |
| #include "FontPaletteValues.h" |
| #include "RenderThemeCocoa.h" |
| #include "SystemFontDatabaseCoreText.h" |
| #include "VersionChecks.h" |
| #include <CoreText/SFNTLayoutTypes.h> |
| #include <pal/spi/cf/CoreTextSPI.h> |
| #include <wtf/HashSet.h> |
| #include <wtf/Lock.h> |
| #include <wtf/MainThread.h> |
| #include <wtf/MemoryPressureHandler.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/cf/TypeCastsCF.h> |
| |
| // FIXME: This seems like it should be in PlatformHave.h. |
| // FIXME: Likely we can remove this special case for watchOS and tvOS. |
| #define HAS_CORE_TEXT_WIDTH_ATTRIBUTE (PLATFORM(COCOA) && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)) |
| |
| namespace WebCore { |
| |
| static inline void appendTrueTypeFeature(CFMutableArrayRef features, int type, int selector) |
| { |
| auto typeNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &type)); |
| auto selectorNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &selector)); |
| CFTypeRef featureKeys[] = { kCTFontFeatureTypeIdentifierKey, kCTFontFeatureSelectorIdentifierKey }; |
| CFTypeRef featureValues[] = { typeNumber.get(), selectorNumber.get() }; |
| auto feature = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, featureKeys, featureValues, WTF_ARRAY_LENGTH(featureKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| CFArrayAppendValue(features, feature.get()); |
| } |
| |
| static inline bool tagEquals(FontTag tag, const char comparison[4]) |
| { |
| return equalIgnoringASCIICase(tag.data(), comparison, 4); |
| } |
| |
| static inline void appendOpenTypeFeature(CFMutableArrayRef features, const FontFeature& feature) |
| { |
| auto featureKey = adoptCF(CFStringCreateWithBytes(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(feature.tag().data()), feature.tag().size() * sizeof(FontTag::value_type), kCFStringEncodingASCII, false)); |
| int rawFeatureValue = feature.value(); |
| auto featureValue = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &rawFeatureValue)); |
| CFTypeRef featureDictionaryKeys[] = { kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureValue }; |
| CFTypeRef featureDictionaryValues[] = { featureKey.get(), featureValue.get() }; |
| auto featureDictionary = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, featureDictionaryKeys, featureDictionaryValues, WTF_ARRAY_LENGTH(featureDictionaryValues), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| CFArrayAppendValue(features, featureDictionary.get()); |
| } |
| |
| typedef HashMap<FontTag, int, FourCharacterTagHash, FourCharacterTagHashTraits> FeaturesMap; |
| typedef HashMap<FontTag, float, FourCharacterTagHash, FourCharacterTagHashTraits> VariationsMap; |
| |
| static FeaturesMap computeFeatureSettingsFromVariants(const FontVariantSettings& variantSettings) |
| { |
| FeaturesMap result; |
| |
| switch (variantSettings.commonLigatures) { |
| case FontVariantLigatures::Normal: |
| break; |
| case FontVariantLigatures::Yes: |
| result.add(fontFeatureTag("liga"), 1); |
| result.add(fontFeatureTag("clig"), 1); |
| break; |
| case FontVariantLigatures::No: |
| result.add(fontFeatureTag("liga"), 0); |
| result.add(fontFeatureTag("clig"), 0); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.discretionaryLigatures) { |
| case FontVariantLigatures::Normal: |
| break; |
| case FontVariantLigatures::Yes: |
| result.add(fontFeatureTag("dlig"), 1); |
| break; |
| case FontVariantLigatures::No: |
| result.add(fontFeatureTag("dlig"), 0); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.historicalLigatures) { |
| case FontVariantLigatures::Normal: |
| break; |
| case FontVariantLigatures::Yes: |
| result.add(fontFeatureTag("hlig"), 1); |
| break; |
| case FontVariantLigatures::No: |
| result.add(fontFeatureTag("hlig"), 0); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.contextualAlternates) { |
| case FontVariantLigatures::Normal: |
| break; |
| case FontVariantLigatures::Yes: |
| result.add(fontFeatureTag("calt"), 1); |
| break; |
| case FontVariantLigatures::No: |
| result.add(fontFeatureTag("calt"), 0); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.position) { |
| case FontVariantPosition::Normal: |
| break; |
| case FontVariantPosition::Subscript: |
| result.add(fontFeatureTag("subs"), 1); |
| break; |
| case FontVariantPosition::Superscript: |
| result.add(fontFeatureTag("sups"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.caps) { |
| case FontVariantCaps::Normal: |
| break; |
| case FontVariantCaps::AllSmall: |
| result.add(fontFeatureTag("c2sc"), 1); |
| FALLTHROUGH; |
| case FontVariantCaps::Small: |
| result.add(fontFeatureTag("smcp"), 1); |
| break; |
| case FontVariantCaps::AllPetite: |
| result.add(fontFeatureTag("c2pc"), 1); |
| FALLTHROUGH; |
| case FontVariantCaps::Petite: |
| result.add(fontFeatureTag("pcap"), 1); |
| break; |
| case FontVariantCaps::Unicase: |
| result.add(fontFeatureTag("unic"), 1); |
| break; |
| case FontVariantCaps::Titling: |
| result.add(fontFeatureTag("titl"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.numericFigure) { |
| case FontVariantNumericFigure::Normal: |
| break; |
| case FontVariantNumericFigure::LiningNumbers: |
| result.add(fontFeatureTag("lnum"), 1); |
| break; |
| case FontVariantNumericFigure::OldStyleNumbers: |
| result.add(fontFeatureTag("onum"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.numericSpacing) { |
| case FontVariantNumericSpacing::Normal: |
| break; |
| case FontVariantNumericSpacing::ProportionalNumbers: |
| result.add(fontFeatureTag("pnum"), 1); |
| break; |
| case FontVariantNumericSpacing::TabularNumbers: |
| result.add(fontFeatureTag("tnum"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.numericFraction) { |
| case FontVariantNumericFraction::Normal: |
| break; |
| case FontVariantNumericFraction::DiagonalFractions: |
| result.add(fontFeatureTag("frac"), 1); |
| break; |
| case FontVariantNumericFraction::StackedFractions: |
| result.add(fontFeatureTag("afrc"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.numericOrdinal) { |
| case FontVariantNumericOrdinal::Normal: |
| break; |
| case FontVariantNumericOrdinal::Yes: |
| result.add(fontFeatureTag("ordn"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.numericSlashedZero) { |
| case FontVariantNumericSlashedZero::Normal: |
| break; |
| case FontVariantNumericSlashedZero::Yes: |
| result.add(fontFeatureTag("zero"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.alternates) { |
| case FontVariantAlternates::Normal: |
| break; |
| case FontVariantAlternates::HistoricalForms: |
| result.add(fontFeatureTag("hist"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.eastAsianVariant) { |
| case FontVariantEastAsianVariant::Normal: |
| break; |
| case FontVariantEastAsianVariant::Jis78: |
| result.add(fontFeatureTag("jp78"), 1); |
| break; |
| case FontVariantEastAsianVariant::Jis83: |
| result.add(fontFeatureTag("jp83"), 1); |
| break; |
| case FontVariantEastAsianVariant::Jis90: |
| result.add(fontFeatureTag("jp90"), 1); |
| break; |
| case FontVariantEastAsianVariant::Jis04: |
| result.add(fontFeatureTag("jp04"), 1); |
| break; |
| case FontVariantEastAsianVariant::Simplified: |
| result.add(fontFeatureTag("smpl"), 1); |
| break; |
| case FontVariantEastAsianVariant::Traditional: |
| result.add(fontFeatureTag("trad"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.eastAsianWidth) { |
| case FontVariantEastAsianWidth::Normal: |
| break; |
| case FontVariantEastAsianWidth::Full: |
| result.add(fontFeatureTag("fwid"), 1); |
| break; |
| case FontVariantEastAsianWidth::Proportional: |
| result.add(fontFeatureTag("pwid"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (variantSettings.eastAsianRuby) { |
| case FontVariantEastAsianRuby::Normal: |
| break; |
| case FontVariantEastAsianRuby::Yes: |
| result.add(fontFeatureTag("ruby"), 1); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| return result; |
| } |
| |
| static inline bool fontNameIsSystemFont(CFStringRef fontName) |
| { |
| return CFStringGetLength(fontName) > 0 && CFStringGetCharacterAtIndex(fontName, 0) == '.'; |
| } |
| |
| static RetainPtr<CFArrayRef> variationAxes(CTFontRef font, ShouldLocalizeAxisNames shouldLocalizeAxisNames) |
| { |
| #if defined(HAVE_CTFontCopyVariationAxesInternal) // This macro is defined inside CoreText, not WebKit. |
| if (shouldLocalizeAxisNames == ShouldLocalizeAxisNames::Yes) |
| return adoptCF(CTFontCopyVariationAxes(font)); |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wunguarded-availability-new" |
| return adoptCF(CTFontCopyVariationAxesInternal(font)); |
| #pragma clang diagnostic pop |
| #else |
| UNUSED_PARAM(shouldLocalizeAxisNames); |
| return adoptCF(CTFontCopyVariationAxes(font)); |
| #endif |
| } |
| |
| VariationDefaultsMap defaultVariationValues(CTFontRef font, ShouldLocalizeAxisNames shouldLocalizeAxisNames) |
| { |
| VariationDefaultsMap result; |
| auto axes = variationAxes(font, shouldLocalizeAxisNames); |
| if (!axes) |
| return result; |
| auto size = CFArrayGetCount(axes.get()); |
| for (CFIndex i = 0; i < size; ++i) { |
| CFDictionaryRef axis = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(axes.get(), i)); |
| CFNumberRef axisIdentifier = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey)); |
| String axisName = static_cast<CFStringRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisNameKey)); |
| CFNumberRef defaultValue = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey)); |
| CFNumberRef minimumValue = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey)); |
| CFNumberRef maximumValue = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey)); |
| uint32_t rawAxisIdentifier = 0; |
| Boolean success = CFNumberGetValue(axisIdentifier, kCFNumberSInt32Type, &rawAxisIdentifier); |
| ASSERT_UNUSED(success, success); |
| float rawDefaultValue = 0; |
| float rawMinimumValue = 0; |
| float rawMaximumValue = 0; |
| CFNumberGetValue(defaultValue, kCFNumberFloatType, &rawDefaultValue); |
| CFNumberGetValue(minimumValue, kCFNumberFloatType, &rawMinimumValue); |
| CFNumberGetValue(maximumValue, kCFNumberFloatType, &rawMaximumValue); |
| |
| if (rawMinimumValue > rawMaximumValue) |
| std::swap(rawMinimumValue, rawMaximumValue); |
| |
| auto b1 = rawAxisIdentifier >> 24; |
| auto b2 = (rawAxisIdentifier & 0xFF0000) >> 16; |
| auto b3 = (rawAxisIdentifier & 0xFF00) >> 8; |
| auto b4 = rawAxisIdentifier & 0xFF; |
| FontTag resultKey = {{ static_cast<char>(b1), static_cast<char>(b2), static_cast<char>(b3), static_cast<char>(b4) }}; |
| VariationDefaults resultValues = { axisName, rawDefaultValue, rawMinimumValue, rawMaximumValue }; |
| result.set(resultKey, resultValues); |
| } |
| return result; |
| } |
| |
| #if USE(NON_VARIABLE_SYSTEM_FONT) |
| static inline bool fontIsSystemFont(CTFontRef font) |
| { |
| if (isSystemFont(font)) |
| return true; |
| |
| auto name = adoptCF(CTFontCopyPostScriptName(font)); |
| return fontNameIsSystemFont(name.get()); |
| } |
| #endif |
| |
| // These values were calculated by performing a linear regression on the CSS weights/widths/slopes and Core Text weights/widths/slopes of San Francisco. |
| // FIXME: <rdar://problem/31312602> Get the real values from Core Text. |
| static inline float normalizeWeight(float value) |
| { |
| return 523.7 * value - 109.3; |
| } |
| |
| static inline float normalizeSlope(float value) |
| { |
| return value * 300; |
| } |
| |
| static inline float denormalizeWeight(float value) |
| { |
| return (value + 109.3) / 523.7; |
| } |
| |
| static inline float denormalizeSlope(float value) |
| { |
| return value / 300; |
| } |
| |
| static inline float denormalizeVariationWidth(float value) |
| { |
| if (value <= 125) |
| return value / 100; |
| if (value <= 150) |
| return (value + 125) / 200; |
| return (value + 400) / 400; |
| } |
| |
| static inline float normalizeVariationWidth(float value) |
| { |
| if (value <= 1.25) |
| return value * 100; |
| if (value <= 1.375) |
| return value * 200 - 125; |
| return value * 400 - 400; |
| } |
| |
| #if !HAS_CORE_TEXT_WIDTH_ATTRIBUTE |
| static inline float normalizeWidth(float value) |
| { |
| return normalizeVariationWidth(value + 1); |
| } |
| #endif |
| |
| struct FontType { |
| FontType(CTFontRef font) |
| { |
| bool foundStat = false; |
| bool foundTrak = false; |
| auto tables = adoptCF(CTFontCopyAvailableTables(font, kCTFontTableOptionNoOptions)); |
| if (!tables) |
| return; |
| auto size = CFArrayGetCount(tables.get()); |
| for (CFIndex i = 0; i < size; ++i) { |
| auto tableTag = static_cast<CTFontTableTag>(reinterpret_cast<uintptr_t>(CFArrayGetValueAtIndex(tables.get(), i))); |
| switch (tableTag) { |
| case kCTFontTableFvar: |
| if (variationType == VariationType::NotVariable) |
| variationType = VariationType::TrueTypeGX; |
| break; |
| case kCTFontTableSTAT: |
| foundStat = true; |
| variationType = VariationType::OpenType18; |
| break; |
| case kCTFontTableMorx: |
| case kCTFontTableMort: |
| aatShaping = true; |
| break; |
| case kCTFontTableGPOS: |
| case kCTFontTableGSUB: |
| openTypeShaping = true; |
| break; |
| case kCTFontTableTrak: |
| foundTrak = true; |
| break; |
| } |
| } |
| if (foundStat && foundTrak) |
| trackingType = TrackingType::Automatic; |
| else if (foundTrak) |
| trackingType = TrackingType::Manual; |
| } |
| |
| enum class VariationType : uint8_t { NotVariable, TrueTypeGX, OpenType18, }; |
| VariationType variationType { VariationType::NotVariable }; |
| enum class TrackingType : uint8_t { None, Automatic, Manual, }; |
| TrackingType trackingType { TrackingType::None }; |
| bool openTypeShaping { false }; |
| bool aatShaping { false }; |
| }; |
| |
| static void addLightPalette(CFMutableDictionaryRef attributes) |
| { |
| CFIndex light = kCTFontPaletteLight; |
| auto number = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &light)); |
| CFDictionaryAddValue(attributes, kCTFontPaletteAttribute, number.get()); |
| } |
| |
| static void addDarkPalette(CFMutableDictionaryRef attributes) |
| { |
| CFIndex dark = kCTFontPaletteDark; |
| auto number = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &dark)); |
| CFDictionaryAddValue(attributes, kCTFontPaletteAttribute, number.get()); |
| } |
| |
| static void addAttributesForCustomFontPalettes(CFMutableDictionaryRef attributes, std::optional<FontPaletteIndex> basePalette, const Vector<FontPaletteValues::OverriddenColor>& overrideColors) |
| { |
| if (basePalette) { |
| switch (basePalette->type) { |
| case FontPaletteIndex::Type::Light: |
| addLightPalette(attributes); |
| break; |
| case FontPaletteIndex::Type::Dark: |
| addDarkPalette(attributes); |
| break; |
| case FontPaletteIndex::Type::Integer: { |
| int64_t rawIndex = basePalette->integer; // There is no kCFNumberUIntType. |
| auto number = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &rawIndex)); |
| CFDictionaryAddValue(attributes, kCTFontPaletteAttribute, number.get()); |
| break; |
| } |
| } |
| } |
| |
| if (!overrideColors.isEmpty()) { |
| auto overrideDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| for (const auto& pair : overrideColors) { |
| const auto& color = pair.second; |
| int64_t rawIndex = pair.first; // There is no kCFNumberUIntType. |
| auto number = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &rawIndex)); |
| auto colorObject = cachedCGColor(color); |
| CFDictionaryAddValue(overrideDictionary.get(), number.get(), colorObject.get()); |
| } |
| if (CFDictionaryGetCount(overrideDictionary.get())) |
| CFDictionaryAddValue(attributes, kCTFontPaletteColorsAttribute, overrideDictionary.get()); |
| } |
| } |
| |
| static void addAttributesForFontPalettes(CFMutableDictionaryRef attributes, const FontPalette& fontPalette, const FontPaletteValues* fontPaletteValues) |
| { |
| switch (fontPalette.type) { |
| case FontPalette::Type::Normal: |
| break; |
| case FontPalette::Type::Light: |
| addLightPalette(attributes); |
| break; |
| case FontPalette::Type::Dark: |
| addDarkPalette(attributes); |
| break; |
| case FontPalette::Type::Custom: { |
| if (fontPaletteValues) |
| addAttributesForCustomFontPalettes(attributes, fontPaletteValues->basePalette(), fontPaletteValues->overrideColors()); |
| break; |
| } |
| } |
| } |
| |
| RetainPtr<CTFontRef> preparePlatformFont(CTFontRef originalFont, const FontDescription& fontDescription, const FontCreationContext& fontCreationContext, bool applyWeightWidthSlopeVariations) |
| { |
| if (!originalFont) |
| return originalFont; |
| |
| FontType fontType { originalFont }; |
| |
| auto fontOpticalSizing = fontDescription.opticalSizing(); |
| |
| auto defaultValues = defaultVariationValues(originalFont, ShouldLocalizeAxisNames::No); |
| |
| auto fontSelectionRequest = fontDescription.fontSelectionRequest(); |
| auto fontStyleAxis = fontDescription.fontStyleAxis(); |
| |
| bool forceOpticalSizingOn = fontOpticalSizing == FontOpticalSizing::Enabled && fontType.variationType == FontType::VariationType::TrueTypeGX && defaultValues.contains({{'o', 'p', 's', 'z'}}); |
| bool forceVariations = defaultValues.contains({{'w', 'g', 'h', 't'}}) || defaultValues.contains({{'w', 'd', 't', 'h'}}) || (fontStyleAxis == FontStyleAxis::ital && defaultValues.contains({{'i', 't', 'a', 'l'}})) || (fontStyleAxis == FontStyleAxis::slnt && defaultValues.contains({{'s', 'l', 'n', 't'}})); |
| const auto& variations = fontDescription.variationSettings(); |
| |
| const auto& features = fontDescription.featureSettings(); |
| const auto& variantSettings = fontDescription.variantSettings(); |
| auto textRenderingMode = fontDescription.textRenderingMode(); |
| auto shouldDisableLigaturesForSpacing = fontDescription.shouldDisableLigaturesForSpacing(); |
| bool dontNeedToApplyFontPalettes = fontDescription.fontPalette().type == FontPalette::Type::Normal; |
| |
| // We might want to check fontType.trackingType == FontType::TrackingType::Manual here, but in order to maintain compatibility with the rest of the system, we don't. |
| bool noFontFeatureSettings = features.isEmpty(); |
| bool noFontVariationSettings = !forceVariations && variations.isEmpty(); |
| bool textRenderingModeIsAuto = textRenderingMode == TextRenderingMode::AutoTextRendering; |
| bool variantSettingsIsNormal = variantSettings.isAllNormal(); |
| bool dontNeedToApplyOpticalSizing = fontOpticalSizing == FontOpticalSizing::Enabled && !forceOpticalSizingOn; |
| bool fontFaceDoesntSpecifyFeatures = !fontCreationContext.fontFaceFeatures() || fontCreationContext.fontFaceFeatures()->isEmpty(); |
| if (noFontFeatureSettings && noFontVariationSettings && textRenderingModeIsAuto && variantSettingsIsNormal && dontNeedToApplyOpticalSizing && fontFaceDoesntSpecifyFeatures && !shouldDisableLigaturesForSpacing && dontNeedToApplyFontPalettes) { |
| #if HAVE(CTFONTCREATEFORCHARACTERSWITHLANGUAGEANDOPTION) |
| return originalFont; |
| #else |
| return createFontForInstalledFonts(originalFont, fontDescription.shouldAllowUserInstalledFonts()); |
| #endif |
| } |
| |
| // This algorithm is described at https://drafts.csswg.org/css-fonts-4/#feature-variation-precedence |
| FeaturesMap featuresToBeApplied; |
| VariationsMap variationsToBeApplied; |
| |
| bool needsConversion = fontType.variationType == FontType::VariationType::TrueTypeGX; |
| |
| auto applyVariation = [&](const FontTag& tag, float value) { |
| auto iterator = defaultValues.find(tag); |
| if (iterator == defaultValues.end()) |
| return; |
| float valueToApply = clampTo(value, iterator->value.minimumValue, iterator->value.maximumValue); |
| variationsToBeApplied.set(tag, valueToApply); |
| }; |
| |
| auto applyFeature = [&](const FontTag& tag, int value) { |
| // AAT doesn't differentiate between liga and clig. We need to make sure they always agree. |
| featuresToBeApplied.set(tag, value); |
| if (fontType.aatShaping) { |
| if (tag == fontFeatureTag("liga")) |
| featuresToBeApplied.set(fontFeatureTag("clig"), value); |
| else if (tag == fontFeatureTag("clig")) |
| featuresToBeApplied.set(fontFeatureTag("liga"), value); |
| } |
| }; |
| |
| // Step 1: CoreText handles default features (such as required ligatures). |
| |
| // Step 2: font-weight, font-stretch, and font-style |
| // The system font is somewhat magical. Don't mess with its variations. |
| if (applyWeightWidthSlopeVariations |
| #if USE(NON_VARIABLE_SYSTEM_FONT) |
| && !fontIsSystemFont(originalFont) |
| #endif |
| ) { |
| float weight = fontSelectionRequest.weight; |
| float width = fontSelectionRequest.width; |
| float slope = fontSelectionRequest.slope.value_or(normalItalicValue()); |
| if (auto weightValue = fontCreationContext.fontFaceCapabilities().weight) |
| weight = std::max(std::min(weight, static_cast<float>(weightValue->maximum)), static_cast<float>(weightValue->minimum)); |
| if (auto widthValue = fontCreationContext.fontFaceCapabilities().width) |
| width = std::max(std::min(width, static_cast<float>(widthValue->maximum)), static_cast<float>(widthValue->minimum)); |
| if (auto slopeValue = fontCreationContext.fontFaceCapabilities().weight) |
| slope = std::max(std::min(slope, static_cast<float>(slopeValue->maximum)), static_cast<float>(slopeValue->minimum)); |
| if (needsConversion) { |
| weight = denormalizeWeight(weight); |
| width = denormalizeVariationWidth(width); |
| slope = denormalizeSlope(slope); |
| } |
| applyVariation({{'w', 'g', 'h', 't'}}, weight); |
| applyVariation({{'w', 'd', 't', 'h'}}, width); |
| if (fontStyleAxis == FontStyleAxis::ital) |
| applyVariation({{'i', 't', 'a', 'l'}}, 1); |
| else |
| applyVariation({{'s', 'l', 'n', 't'}}, slope); |
| } |
| |
| // FIXME: Implement Step 5: font-named-instance |
| |
| // FIXME: Implement Step 6: the font-variation-settings descriptor inside @font-face |
| |
| // Step 7: Consult with font-feature-settings inside @font-face |
| if (fontCreationContext.fontFaceFeatures() && !fontCreationContext.fontFaceFeatures()->isEmpty()) { |
| for (auto& fontFaceFeature : *fontCreationContext.fontFaceFeatures()) |
| applyFeature(fontFaceFeature.tag(), fontFaceFeature.value()); |
| } |
| |
| // FIXME: Move font-optical-sizing handling here. It should be step 9. |
| |
| // Step 10: Font-variant |
| for (auto& newFeature : computeFeatureSettingsFromVariants(variantSettings)) |
| applyFeature(newFeature.key, newFeature.value); |
| |
| // Step 11: Other properties |
| if (textRenderingMode == TextRenderingMode::OptimizeSpeed) { |
| applyFeature(fontFeatureTag("liga"), 0); |
| applyFeature(fontFeatureTag("clig"), 0); |
| applyFeature(fontFeatureTag("dlig"), 0); |
| applyFeature(fontFeatureTag("hlig"), 0); |
| applyFeature(fontFeatureTag("calt"), 0); |
| } |
| if (shouldDisableLigaturesForSpacing) { |
| applyFeature(fontFeatureTag("liga"), 0); |
| applyFeature(fontFeatureTag("clig"), 0); |
| applyFeature(fontFeatureTag("dlig"), 0); |
| applyFeature(fontFeatureTag("hlig"), 0); |
| // Core Text doesn't disable calt when letter-spacing is applied, so we won't either. |
| } |
| |
| // Step 13: Font-feature-settings |
| for (auto& newFeature : features) |
| applyFeature(newFeature.tag(), newFeature.value()); |
| |
| // Step 12: font-variation-settings |
| for (auto& newVariation : variations) |
| applyVariation(newVariation.tag(), newVariation.value()); |
| |
| auto attributes = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| if (!featuresToBeApplied.isEmpty()) { |
| auto featureArray = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, features.size(), &kCFTypeArrayCallBacks)); |
| for (auto& p : featuresToBeApplied) { |
| auto feature = FontFeature(p.key, p.value); |
| |
| // CoreText does not map hlig and hist for TrueType fonts. |
| if (fontType.aatShaping && (tagEquals(feature.tag(), "hlig") || tagEquals(feature.tag(), "hist"))) { |
| if (feature.enabled()) |
| appendTrueTypeFeature(featureArray.get(), kLigaturesType, kHistoricalLigaturesOnSelector); |
| else if (tagEquals(feature.tag(), "hlig")) |
| appendTrueTypeFeature(featureArray.get(), kLigaturesType, kHistoricalLigaturesOffSelector); |
| continue; |
| } |
| |
| appendOpenTypeFeature(featureArray.get(), feature); |
| } |
| CFDictionaryAddValue(attributes.get(), kCTFontFeatureSettingsAttribute, featureArray.get()); |
| } |
| if (!variationsToBeApplied.isEmpty()) { |
| auto variationDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| for (auto& p : variationsToBeApplied) { |
| long long bitwiseTag = p.key[0] << 24 | p.key[1] << 16 | p.key[2] << 8 | p.key[3]; |
| auto tagNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &bitwiseTag)); |
| auto valueNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &p.value)); |
| CFDictionarySetValue(variationDictionary.get(), tagNumber.get(), valueNumber.get()); |
| } |
| CFDictionaryAddValue(attributes.get(), kCTFontVariationAttribute, variationDictionary.get()); |
| } |
| |
| // Step 9: font-optical-sizing |
| // FIXME: Apply this before font-variation-settings |
| if (forceOpticalSizingOn || textRenderingMode == TextRenderingMode::OptimizeLegibility) { |
| #if HAVE(CORETEXT_AUTO_OPTICAL_SIZING) |
| CFDictionaryAddValue(attributes.get(), kCTFontOpticalSizeAttribute, CFSTR("auto")); |
| #else |
| auto size = CTFontGetSize(originalFont); |
| auto sizeNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &size)); |
| CFDictionaryAddValue(attributes.get(), kCTFontOpticalSizeAttribute, sizeNumber.get()); |
| #endif |
| } else if (fontOpticalSizing == FontOpticalSizing::Disabled) { |
| #if HAVE(CORETEXT_AUTO_OPTICAL_SIZING) |
| CFDictionaryAddValue(attributes.get(), kCTFontOpticalSizeAttribute, CFSTR("none")); |
| #endif |
| } |
| |
| addAttributesForFontPalettes(attributes.get(), fontDescription.fontPalette(), fontCreationContext.fontPaletteValues()); |
| |
| addAttributesForInstalledFonts(attributes.get(), fontDescription.shouldAllowUserInstalledFonts()); |
| |
| auto descriptor = adoptCF(CTFontDescriptorCreateWithAttributes(attributes.get())); |
| return adoptCF(CTFontCreateCopyWithAttributes(originalFont, CTFontGetSize(originalFont), nullptr, descriptor.get())); |
| } |
| |
| RefPtr<Font> FontCache::similarFont(const FontDescription& description, const String& family) |
| { |
| // Attempt to find an appropriate font using a match based on the presence of keywords in |
| // the requested names. For example, we'll match any name that contains "Arabic" to Geeza Pro. |
| if (family.isEmpty()) |
| return nullptr; |
| |
| #if PLATFORM(IOS_FAMILY) |
| // Substitute the default monospace font for well-known monospace fonts. |
| if (equalLettersIgnoringASCIICase(family, "monaco") || equalLettersIgnoringASCIICase(family, "menlo")) |
| return fontForFamily(description, "courier"_s); |
| |
| // Substitute Verdana for Lucida Grande. |
| if (equalLettersIgnoringASCIICase(family, "lucida grande")) |
| return fontForFamily(description, "verdana"_s); |
| #endif |
| |
| static constexpr ASCIILiteral matchWords[] = { "Arabic"_s, "Pashto"_s, "Urdu"_s }; |
| auto familyMatcher = StringView(family); |
| for (auto matchWord : matchWords) { |
| if (equalIgnoringASCIICase(familyMatcher, StringView(matchWord))) |
| return fontForFamily(description, isFontWeightBold(description.weight()) ? "GeezaPro-Bold"_s : "GeezaPro"_s); |
| } |
| return nullptr; |
| } |
| |
| #if !HAS_CORE_TEXT_WIDTH_ATTRIBUTE |
| static float stretchFromCoreTextTraits(CFDictionaryRef traits) |
| { |
| auto widthNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(traits, kCTFontWidthTrait)); |
| if (!widthNumber) |
| return normalStretchValue(); |
| |
| float ctWidth; |
| auto success = CFNumberGetValue(widthNumber, kCFNumberFloatType, &ctWidth); |
| ASSERT_UNUSED(success, success); |
| return normalizeWidth(ctWidth); |
| } |
| #endif |
| |
| static void invalidateFontCache(); |
| |
| static void fontCacheRegisteredFontsChangedNotificationCallback(CFNotificationCenterRef, void* observer, CFStringRef, const void *, CFDictionaryRef) |
| { |
| ASSERT_UNUSED(observer, observer == &FontCache::singleton()); |
| |
| invalidateFontCache(); |
| } |
| |
| void FontCache::platformInit() |
| { |
| CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, &fontCacheRegisteredFontsChangedNotificationCallback, kCTFontManagerRegisteredFontsChangedNotification, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); |
| |
| #if PLATFORM(MAC) |
| CFNotificationCenterRef center = CFNotificationCenterGetLocalCenter(); |
| const CFStringRef notificationName = kCFLocaleCurrentLocaleDidChangeNotification; |
| #else |
| CFNotificationCenterRef center = CFNotificationCenterGetDarwinNotifyCenter(); |
| const CFStringRef notificationName = CFSTR("com.apple.language.changed"); |
| #endif |
| CFNotificationCenterAddObserver(center, this, &fontCacheRegisteredFontsChangedNotificationCallback, notificationName, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); |
| } |
| |
| Vector<String> FontCache::systemFontFamilies() |
| { |
| Vector<String> fontFamilies; |
| |
| auto availableFontFamilies = adoptCF(CTFontManagerCopyAvailableFontFamilyNames()); |
| CFIndex count = CFArrayGetCount(availableFontFamilies.get()); |
| for (CFIndex i = 0; i < count; ++i) { |
| auto fontName = dynamic_cf_cast<CFStringRef>(CFArrayGetValueAtIndex(availableFontFamilies.get(), i)); |
| if (!fontName) { |
| ASSERT_NOT_REACHED(); |
| continue; |
| } |
| |
| if (fontNameIsSystemFont(fontName)) |
| continue; |
| |
| fontFamilies.append(fontName); |
| } |
| |
| return fontFamilies; |
| } |
| |
| static inline bool isSystemFont(const String& family) |
| { |
| // AtomString's operator[] handles out-of-bounds by returning 0. |
| return family[0] == '.'; |
| } |
| |
| bool FontCache::isSystemFontForbiddenForEditing(const String& fontFamily) |
| { |
| return isSystemFont(fontFamily); |
| } |
| |
| static CTFontSymbolicTraits computeTraits(const FontDescription& fontDescription) |
| { |
| CTFontSymbolicTraits traits = 0; |
| if (fontDescription.italic()) |
| traits |= kCTFontTraitItalic; |
| if (isFontWeightBold(fontDescription.weight())) |
| traits |= kCTFontTraitBold; |
| return traits; |
| } |
| |
| SynthesisPair computeNecessarySynthesis(CTFontRef font, const FontDescription& fontDescription, ShouldComputePhysicalTraits shouldComputePhysicalTraits, bool isPlatformFont) |
| { |
| if (CTFontIsAppleColorEmoji(font)) |
| return SynthesisPair(false, false); |
| |
| if (isPlatformFont) |
| return SynthesisPair(false, false); |
| |
| CTFontSymbolicTraits desiredTraits = computeTraits(fontDescription); |
| |
| CTFontSymbolicTraits actualTraits = 0; |
| if (isFontWeightBold(fontDescription.weight()) || isItalic(fontDescription.italic())) { |
| if (shouldComputePhysicalTraits == ShouldComputePhysicalTraits::Yes) |
| actualTraits = CTFontGetPhysicalSymbolicTraits(font); |
| else |
| actualTraits = CTFontGetSymbolicTraits(font); |
| } |
| |
| bool needsSyntheticBold = (fontDescription.fontSynthesis() & FontSynthesisWeight) && (desiredTraits & kCTFontTraitBold) && !(actualTraits & kCTFontTraitBold); |
| bool needsSyntheticOblique = (fontDescription.fontSynthesis() & FontSynthesisStyle) && (desiredTraits & kCTFontTraitItalic) && !(actualTraits & kCTFontTraitItalic); |
| |
| return SynthesisPair(needsSyntheticBold, needsSyntheticOblique); |
| } |
| |
| typedef HashSet<String, ASCIICaseInsensitiveHash> Allowlist; |
| static Allowlist& fontAllowlist() |
| { |
| static NeverDestroyed<Allowlist> allowlist; |
| return allowlist; |
| } |
| |
| void FontCache::setFontAllowlist(const Vector<String>& inputAllowlist) |
| { |
| Allowlist& allowlist = fontAllowlist(); |
| allowlist.clear(); |
| for (auto& item : inputAllowlist) |
| allowlist.add(item); |
| } |
| |
| class FontDatabase { |
| public: |
| static FontDatabase& singletonAllowingUserInstalledFonts() |
| { |
| static NeverDestroyed<FontDatabase> database(AllowUserInstalledFonts::Yes); |
| return database; |
| } |
| |
| static FontDatabase& singletonDisallowingUserInstalledFonts() |
| { |
| static NeverDestroyed<FontDatabase> database(AllowUserInstalledFonts::No); |
| return database; |
| } |
| |
| FontDatabase(const FontDatabase&) = delete; |
| FontDatabase& operator=(const FontDatabase&) = delete; |
| |
| struct InstalledFont { |
| InstalledFont() = default; |
| |
| InstalledFont(CTFontDescriptorRef fontDescriptor, AllowUserInstalledFonts allowUserInstalledFonts) |
| : fontDescriptor(fontDescriptor) |
| , capabilities(capabilitiesForFontDescriptor(fontDescriptor)) |
| { |
| #if HAVE(CTFONTCREATEFORCHARACTERSWITHLANGUAGEANDOPTION) |
| UNUSED_PARAM(allowUserInstalledFonts); |
| #else |
| if (allowUserInstalledFonts != AllowUserInstalledFonts::No) |
| return; |
| auto attributes = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| addAttributesForInstalledFonts(attributes.get(), allowUserInstalledFonts); |
| this->fontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithAttributes(fontDescriptor, attributes.get())); |
| #endif |
| } |
| |
| RetainPtr<CTFontDescriptorRef> fontDescriptor; |
| FontSelectionCapabilities capabilities; |
| }; |
| |
| struct InstalledFontFamily { |
| WTF_MAKE_STRUCT_FAST_ALLOCATED; |
| |
| InstalledFontFamily() = default; |
| |
| explicit InstalledFontFamily(Vector<InstalledFont>&& installedFonts) |
| : installedFonts(WTFMove(installedFonts)) |
| { |
| for (auto& font : this->installedFonts) |
| expand(font); |
| } |
| |
| void expand(const InstalledFont& installedFont) |
| { |
| capabilities.expand(installedFont.capabilities); |
| } |
| |
| bool isEmpty() const |
| { |
| return installedFonts.isEmpty(); |
| } |
| |
| size_t size() const |
| { |
| return installedFonts.size(); |
| } |
| |
| Vector<InstalledFont> installedFonts; |
| FontSelectionCapabilities capabilities; |
| }; |
| |
| const InstalledFontFamily& collectionForFamily(const String& familyName) |
| { |
| auto folded = FontCascadeDescription::foldedFamilyName(familyName); |
| { |
| Locker locker { m_familyNameToFontDescriptorsLock }; |
| auto it = m_familyNameToFontDescriptors.find(folded); |
| if (it != m_familyNameToFontDescriptors.end()) |
| return *it->value; |
| } |
| |
| auto installedFontFamily = [&] { |
| auto familyNameString = folded.createCFString(); |
| auto attributes = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| CFDictionaryAddValue(attributes.get(), kCTFontFamilyNameAttribute, familyNameString.get()); |
| addAttributesForInstalledFonts(attributes.get(), m_allowUserInstalledFonts); |
| auto fontDescriptorToMatch = adoptCF(CTFontDescriptorCreateWithAttributes(attributes.get())); |
| auto mandatoryAttributes = installedFontMandatoryAttributes(m_allowUserInstalledFonts); |
| if (auto matches = adoptCF(CTFontDescriptorCreateMatchingFontDescriptors(fontDescriptorToMatch.get(), mandatoryAttributes.get()))) { |
| auto count = CFArrayGetCount(matches.get()); |
| Vector<InstalledFont> result; |
| result.reserveInitialCapacity(count); |
| for (CFIndex i = 0; i < count; ++i) { |
| InstalledFont installedFont(static_cast<CTFontDescriptorRef>(CFArrayGetValueAtIndex(matches.get(), i)), m_allowUserInstalledFonts); |
| result.uncheckedAppend(WTFMove(installedFont)); |
| } |
| return makeUnique<InstalledFontFamily>(WTFMove(result)); |
| } |
| return makeUnique<InstalledFontFamily>(); |
| }(); |
| |
| Locker locker { m_familyNameToFontDescriptorsLock }; |
| return *m_familyNameToFontDescriptors.add(folded.isolatedCopy(), WTFMove(installedFontFamily)).iterator->value; |
| } |
| |
| const InstalledFont& fontForPostScriptName(const AtomString& postScriptName) |
| { |
| const auto& folded = FontCascadeDescription::foldedFamilyName(postScriptName); |
| return m_postScriptNameToFontDescriptors.ensure(folded, [&] { |
| auto postScriptNameString = folded.createCFString(); |
| CFStringRef nameAttribute = kCTFontPostScriptNameAttribute; |
| auto attributes = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| CFDictionaryAddValue(attributes.get(), kCTFontEnabledAttribute, kCFBooleanTrue); |
| CFDictionaryAddValue(attributes.get(), nameAttribute, postScriptNameString.get()); |
| addAttributesForInstalledFonts(attributes.get(), m_allowUserInstalledFonts); |
| auto fontDescriptorToMatch = adoptCF(CTFontDescriptorCreateWithAttributes(attributes.get())); |
| auto mandatoryAttributes = installedFontMandatoryAttributes(m_allowUserInstalledFonts); |
| auto match = adoptCF(CTFontDescriptorCreateMatchingFontDescriptor(fontDescriptorToMatch.get(), mandatoryAttributes.get())); |
| return InstalledFont(match.get(), m_allowUserInstalledFonts); |
| }).iterator->value; |
| } |
| |
| void clear() |
| { |
| { |
| Locker locker { m_familyNameToFontDescriptorsLock }; |
| m_familyNameToFontDescriptors.clear(); |
| } |
| m_postScriptNameToFontDescriptors.clear(); |
| } |
| |
| private: |
| friend class NeverDestroyed<FontDatabase>; |
| |
| FontDatabase(AllowUserInstalledFonts allowUserInstalledFonts) |
| : m_allowUserInstalledFonts(allowUserInstalledFonts) |
| { |
| } |
| |
| Lock m_familyNameToFontDescriptorsLock; |
| HashMap<String, std::unique_ptr<InstalledFontFamily>> m_familyNameToFontDescriptors WTF_GUARDED_BY_LOCK(m_familyNameToFontDescriptorsLock); |
| HashMap<String, InstalledFont> m_postScriptNameToFontDescriptors; |
| AllowUserInstalledFonts m_allowUserInstalledFonts; |
| }; |
| |
| // Because this struct holds intermediate values which may be in the compressed -1 - 1 GX range, we don't want to use the relatively large |
| // quantization of FontSelectionValue. Instead, do this logic with floats. |
| struct MinMax { |
| float minimum; |
| float maximum; |
| }; |
| |
| struct VariationCapabilities { |
| std::optional<MinMax> weight; |
| std::optional<MinMax> width; |
| std::optional<MinMax> slope; |
| }; |
| |
| static std::optional<MinMax> extractVariationBounds(CFDictionaryRef axis) |
| { |
| CFNumberRef minimumValue = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey)); |
| CFNumberRef maximumValue = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey)); |
| float rawMinimumValue = 0; |
| float rawMaximumValue = 0; |
| CFNumberGetValue(minimumValue, kCFNumberFloatType, &rawMinimumValue); |
| CFNumberGetValue(maximumValue, kCFNumberFloatType, &rawMaximumValue); |
| if (rawMinimumValue < rawMaximumValue) |
| return {{ rawMinimumValue, rawMaximumValue }}; |
| return std::nullopt; |
| } |
| |
| static VariationCapabilities variationCapabilitiesForFontDescriptor(CTFontDescriptorRef fontDescriptor) |
| { |
| VariationCapabilities result; |
| |
| if (!adoptCF(CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontVariationAttribute))) |
| return result; |
| |
| auto font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor, 0, nullptr)); |
| auto variations = variationAxes(font.get(), ShouldLocalizeAxisNames::No); |
| if (!variations) |
| return result; |
| |
| auto axisCount = CFArrayGetCount(variations.get()); |
| if (!axisCount) |
| return result; |
| |
| for (CFIndex i = 0; i < axisCount; ++i) { |
| CFDictionaryRef axis = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(variations.get(), i)); |
| CFNumberRef axisIdentifier = static_cast<CFNumberRef>(CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey)); |
| uint32_t rawAxisIdentifier = 0; |
| Boolean success = CFNumberGetValue(axisIdentifier, kCFNumberSInt32Type, &rawAxisIdentifier); |
| ASSERT_UNUSED(success, success); |
| if (rawAxisIdentifier == 0x77676874) // 'wght' |
| result.weight = extractVariationBounds(axis); |
| else if (rawAxisIdentifier == 0x77647468) // 'wdth' |
| result.width = extractVariationBounds(axis); |
| else if (rawAxisIdentifier == 0x736C6E74) // 'slnt' |
| result.slope = extractVariationBounds(axis); |
| } |
| |
| bool optOutFromGXNormalization = false; |
| // FIXME: Likely we can remove this special case for watchOS and tvOS. |
| #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) |
| optOutFromGXNormalization = CTFontDescriptorIsSystemUIFont(fontDescriptor); |
| #endif |
| |
| if (FontType(font.get()).variationType == FontType::VariationType::TrueTypeGX && !optOutFromGXNormalization) { |
| if (result.weight) |
| result.weight = {{ normalizeWeight(result.weight.value().minimum), normalizeWeight(result.weight.value().maximum) }}; |
| if (result.width) |
| result.width = {{ normalizeVariationWidth(result.width.value().minimum), normalizeVariationWidth(result.width.value().maximum) }}; |
| if (result.slope) |
| result.slope = {{ normalizeSlope(result.slope.value().minimum), normalizeSlope(result.slope.value().maximum) }}; |
| } |
| |
| auto minimum = static_cast<float>(FontSelectionValue::minimumValue()); |
| auto maximum = static_cast<float>(FontSelectionValue::maximumValue()); |
| if (result.weight && (result.weight.value().minimum < minimum || result.weight.value().maximum > maximum)) |
| result.weight = { }; |
| if (result.width && (result.width.value().minimum < minimum || result.width.value().maximum > maximum)) |
| result.width = { }; |
| if (result.slope && (result.slope.value().minimum < minimum || result.slope.value().maximum > maximum)) |
| result.slope = { }; |
| |
| return result; |
| } |
| |
| static float getCSSAttribute(CTFontDescriptorRef fontDescriptor, const CFStringRef attribute, float fallback) |
| { |
| auto number = adoptCF(static_cast<CFNumberRef>(CTFontDescriptorCopyAttribute(fontDescriptor, attribute))); |
| if (!number) |
| return fallback; |
| float cssValue; |
| auto success = CFNumberGetValue(number.get(), kCFNumberFloatType, &cssValue); |
| ASSERT_UNUSED(success, success); |
| return cssValue; |
| } |
| |
| FontSelectionCapabilities capabilitiesForFontDescriptor(CTFontDescriptorRef fontDescriptor) |
| { |
| if (!fontDescriptor) |
| return { }; |
| |
| VariationCapabilities variationCapabilities = variationCapabilitiesForFontDescriptor(fontDescriptor); |
| |
| #if !HAS_CORE_TEXT_WIDTH_ATTRIBUTE |
| bool weightOrWidthComeFromTraits = !variationCapabilities.weight || !variationCapabilities.width; |
| #else |
| bool weightOrWidthComeFromTraits = false; |
| #endif |
| |
| if (!variationCapabilities.slope || weightOrWidthComeFromTraits) { |
| auto traits = adoptCF(static_cast<CFDictionaryRef>(CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontTraitsAttribute))); |
| if (traits) { |
| #if !HAS_CORE_TEXT_WIDTH_ATTRIBUTE |
| if (!variationCapabilities.width) { |
| auto widthValue = stretchFromCoreTextTraits(traits.get()); |
| variationCapabilities.width = {{ widthValue, widthValue }}; |
| } |
| #endif |
| |
| if (!variationCapabilities.slope) { |
| auto symbolicTraitsNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(traits.get(), kCTFontSymbolicTrait)); |
| if (symbolicTraitsNumber) { |
| int32_t symbolicTraits; |
| auto success = CFNumberGetValue(symbolicTraitsNumber, kCFNumberSInt32Type, &symbolicTraits); |
| ASSERT_UNUSED(success, success); |
| auto slopeValue = static_cast<float>(symbolicTraits & kCTFontTraitItalic ? italicValue() : normalItalicValue()); |
| variationCapabilities.slope = {{ slopeValue, slopeValue }}; |
| } else |
| variationCapabilities.slope = {{ static_cast<float>(normalItalicValue()), static_cast<float>(normalItalicValue()) }}; |
| } |
| } |
| } |
| |
| if (!variationCapabilities.weight) { |
| auto value = getCSSAttribute(fontDescriptor, kCTFontCSSWeightAttribute, static_cast<float>(normalWeightValue())); |
| variationCapabilities.weight = {{ value, value }}; |
| } |
| |
| #if HAS_CORE_TEXT_WIDTH_ATTRIBUTE |
| if (!variationCapabilities.width) { |
| auto value = getCSSAttribute(fontDescriptor, kCTFontCSSWidthAttribute, static_cast<float>(normalStretchValue())); |
| variationCapabilities.width = {{ value, value }}; |
| } |
| #endif |
| |
| FontSelectionCapabilities result = {{ FontSelectionValue(variationCapabilities.weight.value().minimum), FontSelectionValue(variationCapabilities.weight.value().maximum) }, |
| { FontSelectionValue(variationCapabilities.width.value().minimum), FontSelectionValue(variationCapabilities.width.value().maximum) }, |
| { FontSelectionValue(variationCapabilities.slope.value().minimum), FontSelectionValue(variationCapabilities.slope.value().maximum) }}; |
| ASSERT(result.weight.isValid()); |
| ASSERT(result.width.isValid()); |
| ASSERT(result.slope.isValid()); |
| return result; |
| } |
| |
| static const FontDatabase::InstalledFont* findClosestFont(const FontDatabase::InstalledFontFamily& familyFonts, FontSelectionRequest fontSelectionRequest) |
| { |
| Vector<FontSelectionCapabilities> capabilities; |
| capabilities.reserveInitialCapacity(familyFonts.size()); |
| for (auto& font : familyFonts.installedFonts) |
| capabilities.uncheckedAppend(font.capabilities); |
| FontSelectionAlgorithm fontSelectionAlgorithm(fontSelectionRequest, capabilities, familyFonts.capabilities); |
| auto index = fontSelectionAlgorithm.indexOfBestCapabilities(); |
| if (index == notFound) |
| return nullptr; |
| |
| return &familyFonts.installedFonts[index]; |
| } |
| |
| Vector<FontSelectionCapabilities> FontCache::getFontSelectionCapabilitiesInFamily(const AtomString& familyName, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| auto& fontDatabase = allowUserInstalledFonts == AllowUserInstalledFonts::Yes ? FontDatabase::singletonAllowingUserInstalledFonts() : FontDatabase::singletonDisallowingUserInstalledFonts(); |
| const auto& fonts = fontDatabase.collectionForFamily(familyName.string()); |
| if (fonts.isEmpty()) |
| return { }; |
| |
| Vector<FontSelectionCapabilities> result; |
| result.reserveInitialCapacity(fonts.size()); |
| for (const auto& font : fonts.installedFonts) |
| result.uncheckedAppend(font.capabilities); |
| return result; |
| } |
| |
| struct FontLookup { |
| RetainPtr<CTFontRef> result; |
| bool createdFromPostScriptName { false }; |
| }; |
| |
| static bool isDotPrefixedForbiddenFont(const AtomString& family) |
| { |
| if (linkedOnOrAfter(SDKVersion::FirstForbiddingDotPrefixedFonts)) |
| return family.startsWith('.'); |
| return equalLettersIgnoringASCIICase(family, ".applesystemuifontserif") |
| || equalLettersIgnoringASCIICase(family, ".sf ns mono") |
| || equalLettersIgnoringASCIICase(family, ".sf ui mono") |
| || equalLettersIgnoringASCIICase(family, ".sf arabic") |
| || equalLettersIgnoringASCIICase(family, ".applesystemuifontrounded"); |
| } |
| |
| static FontLookup platformFontLookupWithFamily(const AtomString& family, FontSelectionRequest request, float size, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| const auto& allowlist = fontAllowlist(); |
| if (!isSystemFont(family.string()) && allowlist.size() && !allowlist.contains(family)) |
| return { nullptr }; |
| |
| if (isDotPrefixedForbiddenFont(family)) { |
| // If you want to use these fonts, use system-ui, ui-serif, ui-monospace, or ui-rounded. |
| return { nullptr }; |
| } |
| |
| auto& fontDatabase = allowUserInstalledFonts == AllowUserInstalledFonts::Yes ? FontDatabase::singletonAllowingUserInstalledFonts() : FontDatabase::singletonDisallowingUserInstalledFonts(); |
| const auto& familyFonts = fontDatabase.collectionForFamily(family.string()); |
| if (familyFonts.isEmpty()) { |
| // The CSS spec states that font-family only accepts a name of an actual font family. However, in WebKit, we claim to also |
| // support supplying a PostScript name instead. However, this creates problems when the other properties (font-weight, |
| // font-style) disagree with the traits of the PostScript-named font. The solution we have come up with is, when the default |
| // values for font-weight and font-style are supplied, honor the PostScript name, but if font-weight specifies bold or |
| // font-style specifies italic, then we run the regular matching algorithm on the family of the PostScript font. This way, |
| // if content simply states "font-family: PostScriptName;" without specifying the other font properties, it will be honored, |
| // but if a <b> appears as a descendent element, it will be honored too. |
| const auto& postScriptFont = fontDatabase.fontForPostScriptName(family); |
| if (!postScriptFont.fontDescriptor) |
| return { nullptr }; |
| if ((isItalic(request.slope) && !isItalic(postScriptFont.capabilities.slope.maximum)) |
| || (isFontWeightBold(request.weight) && !isFontWeightBold(postScriptFont.capabilities.weight.maximum))) { |
| auto postScriptFamilyName = adoptCF(static_cast<CFStringRef>(CTFontDescriptorCopyAttribute(postScriptFont.fontDescriptor.get(), kCTFontFamilyNameAttribute))); |
| if (!postScriptFamilyName) |
| return { nullptr }; |
| const auto& familyFonts = fontDatabase.collectionForFamily(String(postScriptFamilyName.get())); |
| if (familyFonts.isEmpty()) |
| return { nullptr }; |
| if (const auto* installedFont = findClosestFont(familyFonts, request)) { |
| if (!installedFont->fontDescriptor) |
| return { nullptr }; |
| return { adoptCF(CTFontCreateWithFontDescriptor(installedFont->fontDescriptor.get(), size, nullptr)), true }; |
| } |
| return { nullptr }; |
| } |
| return { adoptCF(CTFontCreateWithFontDescriptor(postScriptFont.fontDescriptor.get(), size, nullptr)), true }; |
| } |
| |
| if (const auto* installedFont = findClosestFont(familyFonts, request)) |
| return { adoptCF(CTFontCreateWithFontDescriptor(installedFont->fontDescriptor.get(), size, nullptr)), false }; |
| |
| return { nullptr }; |
| } |
| |
| static void invalidateFontCache() |
| { |
| ensureOnMainThread([] { |
| SystemFontDatabaseCoreText::singleton().clear(); |
| clearFontFamilySpecificationCoreTextCache(); |
| |
| FontDatabase::singletonAllowingUserInstalledFonts().clear(); |
| FontDatabase::singletonDisallowingUserInstalledFonts().clear(); |
| |
| FontCache::singleton().invalidate(); |
| }); |
| } |
| |
| static RetainPtr<CTFontRef> fontWithFamilySpecialCase(const AtomString& family, const FontDescription& fontDescription, float size, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| // FIXME: See comment in FontCascadeDescription::effectiveFamilyAt() in FontDescriptionCocoa.cpp |
| std::optional<SystemFontKind> systemDesign; |
| |
| #if HAVE(DESIGN_SYSTEM_UI_FONTS) |
| if (equalLettersIgnoringASCIICase(family, "ui-serif")) |
| systemDesign = SystemFontKind::UISerif; |
| else if (equalLettersIgnoringASCIICase(family, "ui-monospace")) |
| systemDesign = SystemFontKind::UIMonospace; |
| else if (equalLettersIgnoringASCIICase(family, "ui-rounded")) |
| systemDesign = SystemFontKind::UIRounded; |
| #endif |
| |
| if (equalLettersIgnoringASCIICase(family, "-webkit-system-font") || equalLettersIgnoringASCIICase(family, "-apple-system") || equalLettersIgnoringASCIICase(family, "-apple-system-font") || equalLettersIgnoringASCIICase(family, "system-ui") || equalLettersIgnoringASCIICase(family, "ui-sans-serif")) { |
| ASSERT(!systemDesign); |
| systemDesign = SystemFontKind::SystemUI; |
| } |
| |
| if (systemDesign) { |
| auto cascadeList = SystemFontDatabaseCoreText::singleton().cascadeList(fontDescription, family, *systemDesign, allowUserInstalledFonts); |
| if (cascadeList.isEmpty()) |
| return nullptr; |
| return createFontForInstalledFonts(cascadeList[0].get(), size, allowUserInstalledFonts); |
| } |
| |
| if (family.startsWith("UICTFontTextStyle")) { |
| const auto& request = fontDescription.fontSelectionRequest(); |
| CTFontSymbolicTraits traits = (isFontWeightBold(request.weight) || FontCache::singleton().shouldMockBoldSystemFontForAccessibility() ? kCTFontTraitBold : 0) | (isItalic(request.slope) ? kCTFontTraitItalic : 0); |
| auto descriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(family.string().createCFString().get(), RenderThemeCocoa::singleton().contentSizeCategory(), fontDescription.computedLocale().string().createCFString().get())); |
| if (traits) |
| descriptor = adoptCF(CTFontDescriptorCreateCopyWithSymbolicTraits(descriptor.get(), traits, traits)); |
| return createFontForInstalledFonts(descriptor.get(), size, allowUserInstalledFonts); |
| } |
| |
| if (equalLettersIgnoringASCIICase(family, "-apple-menu")) { |
| auto result = adoptCF(CTFontCreateUIFontForLanguage(kCTFontUIFontMenuItem, size, fontDescription.computedLocale().string().createCFString().get())); |
| return createFontForInstalledFonts(result.get(), allowUserInstalledFonts); |
| } |
| |
| if (equalLettersIgnoringASCIICase(family, "-apple-status-bar")) { |
| auto result = adoptCF(CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, size, fontDescription.computedLocale().string().createCFString().get())); |
| return createFontForInstalledFonts(result.get(), allowUserInstalledFonts); |
| } |
| |
| if (equalLettersIgnoringASCIICase(family, "lastresort")) { |
| static const CTFontDescriptorRef lastResort = CTFontDescriptorCreateLastResort(); |
| return adoptCF(CTFontCreateWithFontDescriptor(lastResort, size, nullptr)); |
| } |
| |
| if (equalLettersIgnoringASCIICase(family, "-apple-system-monospaced-numbers")) { |
| int numberSpacingType = kNumberSpacingType; |
| int monospacedNumbersSelector = kMonospacedNumbersSelector; |
| auto numberSpacingNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &numberSpacingType)); |
| auto monospacedNumbersNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &monospacedNumbersSelector)); |
| auto systemFontDescriptor = adoptCF(CTFontDescriptorCreateForUIType(kCTFontUIFontSystem, size, nullptr)); |
| auto monospaceFontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithFeature(systemFontDescriptor.get(), numberSpacingNumber.get(), monospacedNumbersNumber.get())); |
| return createFontForInstalledFonts(monospaceFontDescriptor.get(), size, allowUserInstalledFonts); |
| } |
| |
| return nullptr; |
| } |
| |
| static RetainPtr<CTFontRef> fontWithFamily(const AtomString& family, const FontDescription& fontDescription, const FontCreationContext& fontCreationContext, float size) |
| { |
| if (family.isEmpty()) |
| return nullptr; |
| |
| FontLookup fontLookup; |
| fontLookup.result = fontWithFamilySpecialCase(family, fontDescription, size, fontDescription.shouldAllowUserInstalledFonts()); |
| if (!fontLookup.result) |
| fontLookup = platformFontLookupWithFamily(family, fontDescription.fontSelectionRequest(), size, fontDescription.shouldAllowUserInstalledFonts()); |
| return preparePlatformFont(fontLookup.result.get(), fontDescription, fontCreationContext, !fontLookup.createdFromPostScriptName); |
| } |
| |
| #if PLATFORM(MAC) |
| static bool shouldAutoActivateFontIfNeeded(const AtomString& family) |
| { |
| #ifndef NDEBUG |
| // This cache is not thread safe so the following assertion is there to |
| // make sure this function is always called from the same thread. |
| static Thread* initThread = &Thread::current(); |
| ASSERT(initThread == &Thread::current()); |
| #endif |
| |
| static NeverDestroyed<HashSet<AtomString>> knownFamilies; |
| static const unsigned maxCacheSize = 128; |
| ASSERT(knownFamilies.get().size() <= maxCacheSize); |
| if (knownFamilies.get().size() == maxCacheSize) |
| knownFamilies.get().remove(knownFamilies.get().random()); |
| |
| // Only attempt to auto-activate fonts once for performance reasons. |
| return knownFamilies.get().add(family).isNewEntry; |
| } |
| |
| static void autoActivateFont(const String& name, CGFloat size) |
| { |
| auto fontName = name.createCFString(); |
| CFTypeRef keys[] = { kCTFontNameAttribute, kCTFontEnabledAttribute }; |
| CFTypeRef values[] = { fontName.get(), kCFBooleanTrue }; |
| auto attributes = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| auto descriptor = adoptCF(CTFontDescriptorCreateWithAttributes(attributes.get())); |
| auto newFont = adoptCF(CTFontCreateWithFontDescriptor(descriptor.get(), size, nullptr)); |
| } |
| #endif |
| |
| std::unique_ptr<FontPlatformData> FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomString& family, const FontCreationContext& fontCreationContext) |
| { |
| float size = fontDescription.computedPixelSize(); |
| |
| auto font = fontWithFamily(family, fontDescription, fontCreationContext, size); |
| |
| #if PLATFORM(MAC) |
| if (!font) { |
| if (!shouldAutoActivateFontIfNeeded(family)) |
| return nullptr; |
| |
| // Auto activate the font before looking for it a second time. |
| // Ignore the result because we want to use our own algorithm to actually find the font. |
| autoActivateFont(family.string(), size); |
| |
| font = fontWithFamily(family, fontDescription, fontCreationContext, size); |
| } |
| #endif |
| |
| if (!font) |
| return nullptr; |
| |
| if (fontDescription.shouldAllowUserInstalledFonts() == AllowUserInstalledFonts::No) |
| m_seenFamiliesForPrewarming.add(FontCascadeDescription::foldedFamilyName(family)); |
| |
| auto [syntheticBold, syntheticOblique] = computeNecessarySynthesis(font.get(), fontDescription).boldObliquePair(); |
| |
| return makeUnique<FontPlatformData>(font.get(), size, syntheticBold, syntheticOblique, fontDescription.orientation(), fontDescription.widthVariant(), fontDescription.textRenderingMode()); |
| } |
| |
| typedef HashSet<RetainPtr<CTFontRef>, WTF::RetainPtrObjectHash<CTFontRef>, WTF::RetainPtrObjectHashTraits<CTFontRef>> FallbackDedupSet; |
| static FallbackDedupSet& fallbackDedupSet() |
| { |
| static NeverDestroyed<FallbackDedupSet> dedupSet; |
| return dedupSet.get(); |
| } |
| |
| void FontCache::platformPurgeInactiveFontData() |
| { |
| Vector<CTFontRef> toRemove; |
| for (auto& font : fallbackDedupSet()) { |
| if (CFGetRetainCount(font.get()) == 1) |
| toRemove.append(font.get()); |
| } |
| for (auto& font : toRemove) |
| fallbackDedupSet().remove(font); |
| |
| FontDatabase::singletonAllowingUserInstalledFonts().clear(); |
| FontDatabase::singletonDisallowingUserInstalledFonts().clear(); |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| static inline bool isArabicCharacter(UChar character) |
| { |
| return character >= 0x0600 && character <= 0x06FF; |
| } |
| #endif |
| |
| #if ASSERT_ENABLED |
| static bool isUserInstalledFont(CTFontRef font) |
| { |
| return adoptCF(CTFontCopyAttribute(font, kCTFontUserInstalledAttribute)) == kCFBooleanTrue; |
| } |
| #endif |
| |
| #if HAVE(CTFONTCREATEFORCHARACTERSWITHLANGUAGEANDOPTION) |
| static RetainPtr<CTFontRef> createFontForCharacters(CTFontRef font, CFStringRef localeString, AllowUserInstalledFonts allowUserInstalledFonts, const UChar* characters, unsigned length) |
| { |
| CFIndex coveredLength = 0; |
| auto fallbackOption = allowUserInstalledFonts == AllowUserInstalledFonts::No ? kCTFontFallbackOptionSystem : kCTFontFallbackOptionDefault; |
| return adoptCF(CTFontCreateForCharactersWithLanguageAndOption(font, reinterpret_cast<const UniChar*>(characters), length, localeString, fallbackOption, &coveredLength)); |
| } |
| #else |
| static RetainPtr<CTFontRef> createFontForCharacters(CTFontRef font, CFStringRef localeString, AllowUserInstalledFonts, const UChar* characters, unsigned length) |
| { |
| CFIndex coveredLength = 0; |
| return adoptCF(CTFontCreateForCharactersWithLanguage(font, reinterpret_cast<const UniChar*>(characters), length, localeString, &coveredLength)); |
| } |
| #endif |
| |
| static RetainPtr<CTFontRef> lookupFallbackFont(CTFontRef font, FontSelectionValue fontWeight, const AtomString& locale, AllowUserInstalledFonts allowUserInstalledFonts, const UChar* characters, unsigned length) |
| { |
| ASSERT(length > 0); |
| |
| RetainPtr<CFStringRef> localeString; |
| // FIXME: Why not do this on watchOS and tvOS? |
| #if PLATFORM(IOS) || PLATFORM(MAC) |
| if (!locale.isNull()) |
| localeString = locale.string().createCFString(); |
| #else |
| UNUSED_PARAM(locale); |
| #endif |
| |
| auto result = createFontForCharacters(font, localeString.get(), allowUserInstalledFonts, characters, length); |
| ASSERT(!isUserInstalledFont(result.get()) || allowUserInstalledFonts == AllowUserInstalledFonts::Yes); |
| |
| #if PLATFORM(IOS_FAMILY) |
| // Callers of this function won't include multiple code points. "Length" is to know how many code units |
| // are in the code point. |
| UChar firstCharacter = characters[0]; |
| if (isArabicCharacter(firstCharacter)) { |
| auto familyName = adoptCF(static_cast<CFStringRef>(CTFontCopyAttribute(result.get(), kCTFontFamilyNameAttribute))); |
| if (fontFamilyShouldNotBeUsedForArabic(familyName.get())) { |
| CFStringRef newFamilyName = isFontWeightBold(fontWeight) ? CFSTR("GeezaPro-Bold") : CFSTR("GeezaPro"); |
| CFTypeRef keys[] = { kCTFontNameAttribute }; |
| CFTypeRef values[] = { newFamilyName }; |
| auto attributes = adoptCF(CFDictionaryCreate(kCFAllocatorDefault, keys, values, WTF_ARRAY_LENGTH(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| auto modification = adoptCF(CTFontDescriptorCreateWithAttributes(attributes.get())); |
| result = adoptCF(CTFontCreateCopyWithAttributes(result.get(), CTFontGetSize(result.get()), nullptr, modification.get())); |
| } |
| } |
| #else |
| UNUSED_PARAM(fontWeight); |
| #endif |
| |
| return result; |
| } |
| |
| RefPtr<Font> FontCache::systemFallbackForCharacters(const FontDescription& description, const Font* originalFontData, IsForPlatformFont isForPlatformFont, PreferColoredFont, const UChar* characters, unsigned length) |
| { |
| const FontPlatformData& platformData = originalFontData->platformData(); |
| |
| auto fullName = String(adoptCF(CTFontCopyFullName(platformData.font())).get()); |
| if (!fullName.isEmpty()) |
| m_fontNamesRequiringSystemFallbackForPrewarming.add(fullName); |
| |
| auto result = lookupFallbackFont(platformData.font(), description.weight(), description.computedLocale(), description.shouldAllowUserInstalledFonts(), characters, length); |
| result = preparePlatformFont(result.get(), description, { }); |
| |
| if (!result) |
| return lastResortFallbackFont(description); |
| |
| // FontCascade::drawGlyphBuffer() requires that there are no duplicate Font objects which refer to the same thing. This is enforced in |
| // FontCache::fontForPlatformData(), where our equality check is based on hashing the FontPlatformData, whose hash includes the raw CoreText |
| // font pointer. |
| CTFontRef substituteFont = fallbackDedupSet().add(result).iterator->get(); |
| |
| auto [syntheticBold, syntheticOblique] = computeNecessarySynthesis(substituteFont, description, ShouldComputePhysicalTraits::No, isForPlatformFont == IsForPlatformFont::Yes).boldObliquePair(); |
| |
| FontPlatformData alternateFont(substituteFont, platformData.size(), syntheticBold, syntheticOblique, platformData.orientation(), platformData.widthVariant(), platformData.textRenderingMode()); |
| |
| return fontForPlatformData(alternateFont); |
| } |
| |
| std::optional<ASCIILiteral> FontCache::platformAlternateFamilyName(const String& familyName) |
| { |
| static const UChar heitiString[] = { 0x9ed1, 0x4f53 }; |
| static const UChar songtiString[] = { 0x5b8b, 0x4f53 }; |
| static const UChar weiruanXinXiMingTi[] = { 0x5fae, 0x8edf, 0x65b0, 0x7d30, 0x660e, 0x9ad4 }; |
| static const UChar weiruanYaHeiString[] = { 0x5fae, 0x8f6f, 0x96c5, 0x9ed1 }; |
| static const UChar weiruanZhengHeitiString[] = { 0x5fae, 0x8edf, 0x6b63, 0x9ed1, 0x9ad4 }; |
| |
| static constexpr ASCIILiteral songtiSC = "Songti SC"_s; |
| static constexpr ASCIILiteral songtiTC = "Songti TC"_s; |
| static constexpr ASCIILiteral heitiSCReplacement = "PingFang SC"_s; |
| static constexpr ASCIILiteral heitiTCReplacement = "PingFang TC"_s; |
| |
| switch (familyName.length()) { |
| case 2: |
| if (equal(familyName, songtiString)) |
| return songtiSC; |
| if (equal(familyName, heitiString)) |
| return heitiSCReplacement; |
| break; |
| case 4: |
| if (equal(familyName, weiruanYaHeiString)) |
| return heitiSCReplacement; |
| break; |
| case 5: |
| if (equal(familyName, weiruanZhengHeitiString)) |
| return heitiTCReplacement; |
| break; |
| case 6: |
| if (equalLettersIgnoringASCIICase(familyName, "simsun")) |
| return songtiSC; |
| if (equal(familyName, weiruanXinXiMingTi)) |
| return songtiTC; |
| break; |
| case 10: |
| if (equalLettersIgnoringASCIICase(familyName, "ms mingliu")) |
| return songtiTC; |
| if (equalIgnoringASCIICase(familyName, "\\5b8b\\4f53")) |
| return songtiSC; |
| break; |
| case 18: |
| if (equalLettersIgnoringASCIICase(familyName, "microsoft jhenghei")) |
| return heitiTCReplacement; |
| break; |
| } |
| |
| return std::nullopt; |
| } |
| |
| void addAttributesForInstalledFonts(CFMutableDictionaryRef attributes, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| if (allowUserInstalledFonts == AllowUserInstalledFonts::No) { |
| CFDictionaryAddValue(attributes, kCTFontUserInstalledAttribute, kCFBooleanFalse); |
| CTFontFallbackOption fallbackOption = kCTFontFallbackOptionSystem; |
| auto fallbackOptionNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &fallbackOption)); |
| CFDictionaryAddValue(attributes, kCTFontFallbackOptionAttribute, fallbackOptionNumber.get()); |
| } |
| } |
| |
| RetainPtr<CTFontRef> createFontForInstalledFonts(CTFontDescriptorRef fontDescriptor, CGFloat size, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| auto attributes = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| addAttributesForInstalledFonts(attributes.get(), allowUserInstalledFonts); |
| if (CFDictionaryGetCount(attributes.get())) { |
| auto resultFontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithAttributes(fontDescriptor, attributes.get())); |
| return adoptCF(CTFontCreateWithFontDescriptor(resultFontDescriptor.get(), size, nullptr)); |
| } |
| return adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor, size, nullptr)); |
| } |
| |
| static inline bool isFontMatchingUserInstalledFontFallback(CTFontRef font, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| bool willFallbackToSystemOnly = false; |
| if (auto fontFallbackOptionAttributeRef = adoptCF(static_cast<CFNumberRef>(CTFontCopyAttribute(font, kCTFontFallbackOptionAttribute)))) { |
| int64_t fontFallbackOptionAttribute; |
| CFNumberGetValue(fontFallbackOptionAttributeRef.get(), kCFNumberSInt64Type, &fontFallbackOptionAttribute); |
| willFallbackToSystemOnly = fontFallbackOptionAttribute == kCTFontFallbackOptionSystem; |
| } |
| |
| bool shouldFallbackToSystemOnly = allowUserInstalledFonts == AllowUserInstalledFonts::No; |
| return willFallbackToSystemOnly == shouldFallbackToSystemOnly; |
| } |
| |
| RetainPtr<CTFontRef> createFontForInstalledFonts(CTFontRef font, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| if (isFontMatchingUserInstalledFontFallback(font, allowUserInstalledFonts)) |
| return font; |
| |
| auto attributes = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); |
| addAttributesForInstalledFonts(attributes.get(), allowUserInstalledFonts); |
| if (CFDictionaryGetCount(attributes.get())) { |
| auto modification = adoptCF(CTFontDescriptorCreateWithAttributes(attributes.get())); |
| return adoptCF(CTFontCreateCopyWithAttributes(font, CTFontGetSize(font), nullptr, modification.get())); |
| } |
| return font; |
| } |
| |
| void addAttributesForWebFonts(CFMutableDictionaryRef attributes, AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| if (allowUserInstalledFonts == AllowUserInstalledFonts::No) { |
| CTFontFallbackOption fallbackOption = kCTFontFallbackOptionSystem; |
| auto fallbackOptionNumber = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &fallbackOption)); |
| CFDictionaryAddValue(attributes, kCTFontFallbackOptionAttribute, fallbackOptionNumber.get()); |
| } |
| } |
| |
| RetainPtr<CFSetRef> installedFontMandatoryAttributes(AllowUserInstalledFonts allowUserInstalledFonts) |
| { |
| if (allowUserInstalledFonts == AllowUserInstalledFonts::No) { |
| CFTypeRef mandatoryAttributesValues[] = { kCTFontFamilyNameAttribute, kCTFontPostScriptNameAttribute, kCTFontEnabledAttribute, kCTFontUserInstalledAttribute, kCTFontFallbackOptionAttribute }; |
| return adoptCF(CFSetCreate(kCFAllocatorDefault, mandatoryAttributesValues, WTF_ARRAY_LENGTH(mandatoryAttributesValues), &kCFTypeSetCallBacks)); |
| } |
| return nullptr; |
| } |
| |
| Ref<Font> FontCache::lastResortFallbackFont(const FontDescription& fontDescription) |
| { |
| // FIXME: Would be even better to somehow get the user's default font here. For now we'll pick |
| // the default that the user would get without changing any prefs. |
| if (auto result = fontForFamily(fontDescription, AtomString("Times", AtomString::ConstructFromLiteral))) |
| return *result; |
| |
| // LastResort is guaranteed to be non-null. |
| auto fontDescriptor = adoptCF(CTFontDescriptorCreateLastResort()); |
| auto font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), fontDescription.computedPixelSize(), nullptr)); |
| auto [syntheticBold, syntheticOblique] = computeNecessarySynthesis(font.get(), fontDescription).boldObliquePair(); |
| FontPlatformData platformData(font.get(), fontDescription.computedPixelSize(), syntheticBold, syntheticOblique, fontDescription.orientation(), fontDescription.widthVariant(), fontDescription.textRenderingMode()); |
| return fontForPlatformData(platformData); |
| } |
| |
| FontCache::PrewarmInformation FontCache::collectPrewarmInformation() const |
| { |
| return { copyToVector(m_seenFamiliesForPrewarming), copyToVector(m_fontNamesRequiringSystemFallbackForPrewarming) }; |
| } |
| |
| void FontCache::prewarm(const PrewarmInformation& prewarmInformation) |
| { |
| if (prewarmInformation.isEmpty()) |
| return; |
| |
| if (!m_prewarmQueue) |
| m_prewarmQueue = WorkQueue::create("WebKit font prewarm queue"); |
| |
| auto& database = FontDatabase::singletonDisallowingUserInstalledFonts(); |
| |
| m_prewarmQueue->dispatch([&database, prewarmInformation = prewarmInformation.isolatedCopy()] { |
| for (auto& family : prewarmInformation.seenFamilies) |
| database.collectionForFamily(family); |
| |
| for (auto& fontName : prewarmInformation.fontNamesRequiringSystemFallback) { |
| auto cfFontName = fontName.createCFString(); |
| if (auto warmingFont = adoptCF(CTFontCreateWithName(cfFontName.get(), 0, nullptr))) { |
| // This is sufficient to warm CoreText caches for language and character specific fallbacks. |
| CFIndex coveredLength = 0; |
| UniChar character = ' '; |
| |
| #if HAVE(CTFONTCREATEFORCHARACTERSWITHLANGUAGEANDOPTION) |
| auto fallbackWarmingFont = adoptCF(CTFontCreateForCharactersWithLanguageAndOption(warmingFont.get(), &character, 1, nullptr, kCTFontFallbackOptionSystem, &coveredLength)); |
| #else |
| auto fallbackWarmingFont = adoptCF(CTFontCreateForCharactersWithLanguage(warmingFont.get(), &character, 1, nullptr, &coveredLength)); |
| #endif |
| } |
| } |
| }); |
| } |
| |
| void FontCache::prewarmGlobally() |
| { |
| #if !HAVE(STATIC_FONT_REGISTRY) |
| if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) |
| return; |
| |
| Vector<String> families { |
| #if PLATFORM(MAC) || PLATFORM(MACCATALYST) |
| ".SF NS Text"_s, |
| ".SF NS Display"_s, |
| #endif |
| "Arial"_s, |
| "Helvetica"_s, |
| "Helvetica Neue"_s, |
| "Lucida Grande"_s, |
| "Times"_s, |
| "Times New Roman"_s, |
| }; |
| |
| FontCache::PrewarmInformation prewarmInfo; |
| prewarmInfo.seenFamilies = WTFMove(families); |
| FontCache::singleton().prewarm(prewarmInfo); |
| #endif |
| } |
| |
| } |