| /* |
| * This file is part of the internal font implementation. |
| * |
| * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. |
| * Copyright (c) 2010 Google Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #import "third_party/blink/renderer/platform/fonts/mac/font_platform_data_mac.h" |
| |
| #import <AppKit/NSFont.h> |
| #import <AvailabilityMacros.h> |
| |
| #import "base/mac/foundation_util.h" |
| #import "base/mac/scoped_nsobject.h" |
| #include "base/stl_util.h" |
| #include "third_party/blink/public/platform/mac/web_sandbox_support.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/platform/fonts/font.h" |
| #include "third_party/blink/renderer/platform/fonts/font_platform_data.h" |
| #include "third_party/blink/renderer/platform/fonts/mac/core_text_font_format_support.h" |
| #include "third_party/blink/renderer/platform/fonts/opentype/font_settings.h" |
| #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h" |
| #include "third_party/blink/renderer/platform/web_test_support.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| #include "third_party/skia/include/core/SkFont.h" |
| #include "third_party/skia/include/core/SkStream.h" |
| #include "third_party/skia/include/core/SkTypeface.h" |
| #include "third_party/skia/include/core/SkTypes.h" |
| #import "third_party/skia/include/ports/SkTypeface_mac.h" |
| |
| namespace { |
| constexpr SkFourByteTag kOpszTag = SkSetFourByteTag('o', 'p', 's', 'z'); |
| } |
| |
| namespace blink { |
| |
| bool VariableAxisChangeEffective(SkTypeface* typeface, |
| SkFourByteTag axis, |
| float new_value) { |
| // First clamp new value to within range of min and max of variable axis. |
| int num_axes = typeface->getVariationDesignParameters(nullptr, 0); |
| if (num_axes <= 0) |
| return false; |
| |
| Vector<SkFontParameters::Variation::Axis> axes_parameters(num_axes); |
| int returned_axes = |
| typeface->getVariationDesignParameters(axes_parameters.data(), num_axes); |
| DCHECK_EQ(num_axes, returned_axes); |
| DCHECK_GE(num_axes, 0); |
| |
| float clamped_new_value = new_value; |
| for (auto& axis_parameters : axes_parameters) { |
| if (axis_parameters.tag == axis) { |
| clamped_new_value = std::min(new_value, axis_parameters.max); |
| clamped_new_value = std::max(clamped_new_value, axis_parameters.min); |
| } |
| } |
| |
| int num_coordinates = typeface->getVariationDesignPosition(nullptr, 0); |
| if (num_coordinates <= 0) |
| return true; // Font has axes, but no positions, setting one would have an |
| // effect. |
| |
| // Then compare if clamped value differs from what is set on the font. |
| Vector<SkFontArguments::VariationPosition::Coordinate> coordinates( |
| num_coordinates); |
| int returned_coordinates = |
| typeface->getVariationDesignPosition(coordinates.data(), num_coordinates); |
| |
| if (returned_coordinates != num_coordinates) |
| return false; // Something went wrong in retrieving actual axis positions, |
| // font broken? |
| |
| for (auto& coordinate : coordinates) { |
| if (coordinate.axis == axis) |
| return coordinate.value != clamped_new_value; |
| } |
| return false; |
| } |
| |
| static bool CanLoadInProcess(NSFont* ns_font) { |
| base::ScopedCFTypeRef<CGFontRef> cg_font( |
| CTFontCopyGraphicsFont(base::mac::NSToCFCast(ns_font), 0)); |
| // Toll-free bridged types CFStringRef and NSString*. |
| base::scoped_nsobject<NSString> font_name( |
| base::mac::CFToNSCast(CGFontCopyPostScriptName(cg_font))); |
| return ![font_name isEqualToString:@"LastResort"]; |
| } |
| |
| static CFDictionaryRef CascadeToLastResortFontAttributes() { |
| static CFDictionaryRef attributes; |
| if (attributes) |
| return attributes; |
| |
| base::ScopedCFTypeRef<CTFontDescriptorRef> last_resort( |
| CTFontDescriptorCreateWithNameAndSize(CFSTR("LastResort"), 0)); |
| const void* descriptors[] = {last_resort}; |
| base::ScopedCFTypeRef<CFArrayRef> values_array( |
| CFArrayCreate(kCFAllocatorDefault, descriptors, base::size(descriptors), |
| &kCFTypeArrayCallBacks)); |
| |
| const void* keys[] = {kCTFontCascadeListAttribute}; |
| const void* values[] = {values_array}; |
| attributes = CFDictionaryCreate( |
| kCFAllocatorDefault, keys, values, base::size(keys), |
| &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| return attributes; |
| } |
| |
| static sk_sp<SkTypeface> LoadFromBrowserProcess(NSFont* ns_font, |
| float text_size) { |
| // Send cross-process request to load font. |
| WebSandboxSupport* sandbox_support = Platform::Current()->GetSandboxSupport(); |
| if (!sandbox_support) { |
| // This function should only be called in response to an error loading a |
| // font due to being blocked by the sandbox. |
| // This by definition shouldn't happen if there is no sandbox support. |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| base::ScopedCFTypeRef<CTFontDescriptorRef> loaded_data_descriptor; |
| uint32_t font_id; |
| if (!sandbox_support->LoadFont(base::mac::NSToCFCast(ns_font), |
| &loaded_data_descriptor, &font_id)) { |
| // TODO crbug.com/461279: Make this appear in the inspector console? |
| DLOG(ERROR) |
| << "Loading user font \"" << [[ns_font familyName] UTF8String] |
| << "\" from non system location failed. Corrupt or missing font file?"; |
| return nullptr; |
| } |
| |
| base::ScopedCFTypeRef<CTFontDescriptorRef> data_descriptor_with_cascade( |
| CTFontDescriptorCreateCopyWithAttributes( |
| loaded_data_descriptor, CascadeToLastResortFontAttributes())); |
| base::ScopedCFTypeRef<CTFontRef> ct_font(CTFontCreateWithFontDescriptor( |
| data_descriptor_with_cascade.get(), text_size, 0)); |
| sk_sp<SkTypeface> return_font = SkMakeTypefaceFromCTFont(ct_font); |
| |
| if (!return_font.get()) |
| // TODO crbug.com/461279: Make this appear in the inspector console? |
| DLOG(ERROR) |
| << "Instantiating SkTypeface from user font failed for font family \"" |
| << [[ns_font familyName] UTF8String] << "\"."; |
| return return_font; |
| } |
| |
| std::unique_ptr<FontPlatformData> FontPlatformDataFromNSFont( |
| NSFont* ns_font, |
| float size, |
| bool synthetic_bold, |
| bool synthetic_italic, |
| FontOrientation orientation, |
| OpticalSizing optical_sizing, |
| FontVariationSettings* variation_settings) { |
| DCHECK(ns_font); |
| sk_sp<SkTypeface> typeface; |
| if (CanLoadInProcess(ns_font)) { |
| typeface = SkMakeTypefaceFromCTFont(base::mac::NSToCFCast(ns_font)); |
| } else { |
| // In process loading fails for cases where third party font manager |
| // software registers fonts in non system locations such as /Library/Fonts |
| // and ~/Library Fonts, see crbug.com/72727 or crbug.com/108645. |
| typeface = LoadFromBrowserProcess(ns_font, size); |
| } |
| |
| auto make_typeface_fontplatformdata = [&typeface, &size, &synthetic_bold, |
| &synthetic_italic, &orientation]() { |
| return std::make_unique<FontPlatformData>( |
| std::move(typeface), std::string(), size, synthetic_bold, |
| synthetic_italic, orientation); |
| }; |
| |
| wtf_size_t valid_configured_axes = |
| variation_settings && variation_settings->size() < UINT16_MAX |
| ? variation_settings->size() |
| : 0; |
| |
| // No variable font requested, return static font. |
| if (!valid_configured_axes && optical_sizing == kNoneOpticalSizing) |
| return make_typeface_fontplatformdata(); |
| |
| if (!typeface) |
| return nullptr; |
| |
| int existing_axes = typeface->getVariationDesignPosition(nullptr, 0); |
| // Don't apply variation parameters if the font does not have axes or we |
| // fail to retrieve the existing ones. |
| if (existing_axes <= 0) |
| return make_typeface_fontplatformdata(); |
| |
| Vector<SkFontArguments::VariationPosition::Coordinate> coordinates_to_set; |
| coordinates_to_set.resize(existing_axes); |
| |
| if (typeface->getVariationDesignPosition(coordinates_to_set.data(), |
| existing_axes) != existing_axes) { |
| return make_typeface_fontplatformdata(); |
| } |
| |
| // Iterate over the font's axes and find a missing tag from variation |
| // settings, special case 'opsz', track the number of axes reconfigured. |
| bool axes_reconfigured = false; |
| for (auto& coordinate : coordinates_to_set) { |
| // Set 'opsz' to font size but allow having it overridden by |
| // font-variation-settings in case it has 'opsz'. |
| if (coordinate.axis == kOpszTag && optical_sizing == kAutoOpticalSizing) { |
| if (VariableAxisChangeEffective(typeface.get(), coordinate.axis, size)) { |
| coordinate.value = SkFloatToScalar(size); |
| axes_reconfigured = true; |
| } |
| } |
| FontVariationAxis found_variation_setting(0, 0); |
| if (variation_settings && variation_settings->FindPair( |
| coordinate.axis, &found_variation_setting)) { |
| if (VariableAxisChangeEffective(typeface.get(), coordinate.axis, |
| found_variation_setting.Value())) { |
| coordinate.value = found_variation_setting.Value(); |
| axes_reconfigured = true; |
| } |
| } |
| } |
| |
| if (!axes_reconfigured) { |
| // No variable axes touched, return the previous typeface. |
| return make_typeface_fontplatformdata(); |
| } |
| |
| SkFontArguments::VariationPosition variation_design_position{ |
| coordinates_to_set.data(), coordinates_to_set.size()}; |
| |
| sk_sp<SkTypeface> cloned_typeface(typeface->makeClone( |
| SkFontArguments().setVariationDesignPosition(variation_design_position))); |
| |
| if (!cloned_typeface) { |
| // Applying variation parameters failed, return original typeface. |
| return make_typeface_fontplatformdata(); |
| } |
| typeface = cloned_typeface; |
| return make_typeface_fontplatformdata(); |
| } |
| |
| void FontPlatformData::SetupSkFont(SkFont* skfont, |
| float, |
| const Font* font) const { |
| bool should_smooth_fonts = true; |
| bool should_antialias = true; |
| bool should_subpixel_position = true; |
| |
| if (font) { |
| switch (font->GetFontDescription().FontSmoothing()) { |
| case kAntialiased: |
| should_smooth_fonts = false; |
| break; |
| case kSubpixelAntialiased: |
| break; |
| case kNoSmoothing: |
| should_antialias = false; |
| should_smooth_fonts = false; |
| break; |
| case kAutoSmoothing: |
| // For the AutoSmooth case, don't do anything! Keep the default |
| // settings. |
| break; |
| } |
| } |
| |
| if (WebTestSupport::IsRunningWebTest()) { |
| should_smooth_fonts = false; |
| should_antialias = |
| should_antialias && WebTestSupport::IsFontAntialiasingEnabledForTest(); |
| should_subpixel_position = |
| WebTestSupport::IsTextSubpixelPositioningAllowedForTest(); |
| } |
| |
| if (should_antialias && should_smooth_fonts) { |
| skfont->setEdging(SkFont::Edging::kSubpixelAntiAlias); |
| } else if (should_antialias) { |
| skfont->setEdging(SkFont::Edging::kAntiAlias); |
| } else { |
| skfont->setEdging(SkFont::Edging::kAlias); |
| } |
| skfont->setEmbeddedBitmaps(false); |
| const float ts = text_size_ >= 0 ? text_size_ : 12; |
| skfont->setSize(SkFloatToScalar(ts)); |
| skfont->setTypeface(typeface_); |
| skfont->setEmbolden(synthetic_bold_); |
| skfont->setSkewX(synthetic_italic_ ? -SK_Scalar1 / 4 : 0); |
| skfont->setSubpixel(should_subpixel_position); |
| |
| // CoreText always provides linear metrics if it can, so the linear metrics |
| // flag setting doesn't affect typefaces backed by CoreText. However, it |
| // does affect FreeType backed typefaces, so set the flag for consistency. |
| skfont->setLinearMetrics(should_subpixel_position); |
| |
| // When rendering using CoreGraphics, disable hinting when |
| // webkit-font-smoothing:antialiased or text-rendering:geometricPrecision is |
| // used. See crbug.com/152304 |
| if (font && |
| (font->GetFontDescription().FontSmoothing() == kAntialiased || |
| font->GetFontDescription().TextRendering() == kGeometricPrecision)) |
| skfont->setHinting(SkFontHinting::kNone); |
| } |
| |
| } // namespace blink |