summaryrefslogtreecommitdiffstats
path: root/gfx/2d/ScaledFontMac.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/2d/ScaledFontMac.cpp791
1 files changed, 791 insertions, 0 deletions
diff --git a/gfx/2d/ScaledFontMac.cpp b/gfx/2d/ScaledFontMac.cpp
new file mode 100644
index 0000000000..d34d6b3ed6
--- /dev/null
+++ b/gfx/2d/ScaledFontMac.cpp
@@ -0,0 +1,791 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScaledFontMac.h"
+#include "UnscaledFontMac.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "PathSkia.h"
+#include "skia/include/core/SkPaint.h"
+#include "skia/include/core/SkPath.h"
+#include "skia/include/ports/SkTypeface_mac.h"
+#include <vector>
+#include <dlfcn.h>
+#ifdef MOZ_WIDGET_UIKIT
+# include <CoreFoundation/CoreFoundation.h>
+#endif
+#include "nsCocoaFeatures.h"
+#include "mozilla/gfx/Logging.h"
+
+#ifdef MOZ_WIDGET_COCOA
+// prototype for private API
+extern "C" {
+CGPathRef CGFontGetGlyphPath(CGFontRef fontRef,
+ CGAffineTransform* textTransform, int unknown,
+ CGGlyph glyph);
+};
+#endif
+
+#include "cairo-quartz.h"
+
+namespace mozilla {
+namespace gfx {
+
+// Simple helper class to automatically release a CFObject when it goes out
+// of scope.
+template <class T>
+class AutoRelease final {
+ public:
+ explicit AutoRelease(T aObject) : mObject(aObject) {}
+
+ ~AutoRelease() {
+ if (mObject) {
+ CFRelease(mObject);
+ }
+ }
+
+ void operator=(T aObject) {
+ if (mObject) {
+ CFRelease(mObject);
+ }
+ mObject = aObject;
+ }
+
+ operator T() { return mObject; }
+
+ T forget() {
+ T obj = mObject;
+ mObject = nullptr;
+ return obj;
+ }
+
+ private:
+ T mObject;
+};
+
+// Helper to create a CTFont from a CGFont, copying any variations that were
+// set on the original CGFont.
+static CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont,
+ CGFloat aSize,
+ bool aInstalledFont) {
+ // Avoid calling potentially buggy variation APIs on pre-Sierra macOS
+ // versions (see bug 1331683).
+ //
+ // And on HighSierra, CTFontCreateWithGraphicsFont properly carries over
+ // variation settings from the CGFont to CTFont, so we don't need to do
+ // the extra work here -- and this seems to avoid Core Text crashiness
+ // seen in bug 1454094.
+ //
+ // However, for installed fonts it seems we DO need to copy the variations
+ // explicitly even on 10.13, otherwise fonts fail to render (as in bug
+ // 1455494) when non-default values are used. Fortunately, the crash
+ // mentioned above occurs with data fonts, not (AFAICT) with system-
+ // installed fonts.
+ //
+ // So we only need to do this "the hard way" on Sierra, and for installed
+ // fonts on HighSierra+; otherwise, just let the standard CTFont function
+ // do its thing.
+ //
+ // NOTE in case this ever needs further adjustment: there is similar logic
+ // in four places in the tree (sadly):
+ // CreateCTFontFromCGFontWithVariations in gfxMacFont.cpp
+ // CreateCTFontFromCGFontWithVariations in ScaledFontMac.cpp
+ // CreateCTFontFromCGFontWithVariations in cairo-quartz-font.c
+ // ctfont_create_exact_copy in SkFontHost_mac.cpp
+ CTFontRef ctFont;
+ if (nsCocoaFeatures::OnSierraExactly() ||
+ (aInstalledFont && nsCocoaFeatures::OnHighSierraOrLater())) {
+ CFDictionaryRef vars = CGFontCopyVariations(aCGFont);
+ if (vars) {
+ CFDictionaryRef varAttr = CFDictionaryCreate(
+ nullptr, (const void**)&kCTFontVariationAttribute,
+ (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFRelease(vars);
+
+ CTFontDescriptorRef varDesc =
+ CTFontDescriptorCreateWithAttributes(varAttr);
+ CFRelease(varAttr);
+
+ ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, varDesc);
+ CFRelease(varDesc);
+ } else {
+ ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
+ }
+ } else {
+ ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr);
+ }
+ return ctFont;
+}
+
+ScaledFontMac::ScaledFontMac(CGFontRef aFont,
+ const RefPtr<UnscaledFont>& aUnscaledFont,
+ Float aSize, bool aOwnsFont,
+ const DeviceColor& aFontSmoothingBackgroundColor,
+ bool aUseFontSmoothing, bool aApplySyntheticBold,
+ bool aHasColorGlyphs)
+ : ScaledFontBase(aUnscaledFont, aSize),
+ mFont(aFont),
+ mFontSmoothingBackgroundColor(aFontSmoothingBackgroundColor),
+ mUseFontSmoothing(aUseFontSmoothing),
+ mApplySyntheticBold(aApplySyntheticBold),
+ mHasColorGlyphs(aHasColorGlyphs) {
+ if (!aOwnsFont) {
+ // XXX: should we be taking a reference
+ CGFontRetain(aFont);
+ }
+
+ auto unscaledMac = static_cast<UnscaledFontMac*>(aUnscaledFont.get());
+ bool dataFont = unscaledMac->IsDataFont();
+ mCTFont = CreateCTFontFromCGFontWithVariations(aFont, aSize, !dataFont);
+}
+
+ScaledFontMac::ScaledFontMac(CTFontRef aFont,
+ const RefPtr<UnscaledFont>& aUnscaledFont,
+ const DeviceColor& aFontSmoothingBackgroundColor,
+ bool aUseFontSmoothing, bool aApplySyntheticBold,
+ bool aHasColorGlyphs)
+ : ScaledFontBase(aUnscaledFont, CTFontGetSize(aFont)),
+ mCTFont(aFont),
+ mFontSmoothingBackgroundColor(aFontSmoothingBackgroundColor),
+ mUseFontSmoothing(aUseFontSmoothing),
+ mApplySyntheticBold(aApplySyntheticBold),
+ mHasColorGlyphs(aHasColorGlyphs) {
+ mFont = CTFontCopyGraphicsFont(aFont, nullptr);
+
+ CFRetain(mCTFont);
+}
+
+ScaledFontMac::~ScaledFontMac() {
+ CFRelease(mCTFont);
+ CGFontRelease(mFont);
+}
+
+SkTypeface* ScaledFontMac::CreateSkTypeface() {
+ return SkCreateTypefaceFromCTFont(mCTFont);
+}
+
+void ScaledFontMac::SetupSkFontDrawOptions(SkFont& aFont) {
+ aFont.setSubpixel(true);
+
+ // Normally, Skia enables LCD FontSmoothing which creates thicker fonts
+ // and also enables subpixel AA. CoreGraphics without font smoothing
+ // explicitly creates thinner fonts and grayscale AA.
+ // CoreGraphics doesn't support a configuration that produces thicker
+ // fonts with grayscale AA as LCD Font Smoothing enables or disables
+ // both. However, Skia supports it by enabling font smoothing (producing
+ // subpixel AA) and converts it to grayscale AA. Since Skia doesn't
+ // support subpixel AA on transparent backgrounds, we still want font
+ // smoothing for the thicker fonts, even if it is grayscale AA.
+ //
+ // With explicit Grayscale AA (from -moz-osx-font-smoothing:grayscale),
+ // we want to have grayscale AA with no smoothing at all. This means
+ // disabling the LCD font smoothing behaviour.
+ // To accomplish this we have to explicitly disable hinting,
+ // and disable LCDRenderText.
+ if (aFont.getEdging() == SkFont::Edging::kAntiAlias && !mUseFontSmoothing) {
+ aFont.setHinting(SkFontHinting::kNone);
+ }
+}
+
+// private API here are the public options on OS X
+// CTFontCreatePathForGlyph
+// ATSUGlyphGetCubicPaths
+// we've used this in cairo sucessfully for some time.
+// Note: cairo dlsyms it. We could do that but maybe it's
+// safe just to use?
+
+already_AddRefed<Path> ScaledFontMac::GetPathForGlyphs(
+ const GlyphBuffer& aBuffer, const DrawTarget* aTarget) {
+ return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
+}
+
+static uint32_t CalcTableChecksum(const uint32_t* tableStart, uint32_t length,
+ bool skipChecksumAdjust = false) {
+ uint32_t sum = 0L;
+ const uint32_t* table = tableStart;
+ const uint32_t* end = table + length / sizeof(uint32_t);
+ while (table < end) {
+ if (skipChecksumAdjust && (table - tableStart) == 2) {
+ table++;
+ } else {
+ sum += CFSwapInt32BigToHost(*table++);
+ }
+ }
+
+ // The length is not 4-byte aligned, but we still must process the remaining
+ // bytes.
+ if (length & 3) {
+ // Pad with zero before adding to the checksum.
+ uint32_t last = 0;
+ memcpy(&last, end, length & 3);
+ sum += CFSwapInt32BigToHost(last);
+ }
+
+ return sum;
+}
+
+struct TableRecord {
+ uint32_t tag;
+ uint32_t checkSum;
+ uint32_t offset;
+ uint32_t length;
+ CFDataRef data;
+};
+
+static int maxPow2LessThanEqual(int a) {
+ int x = 1;
+ int shift = 0;
+ while ((x << (shift + 1)) <= a) {
+ shift++;
+ }
+ return shift;
+}
+
+struct writeBuf final {
+ explicit writeBuf(int size) {
+ this->data = new unsigned char[size];
+ this->offset = 0;
+ }
+ ~writeBuf() { delete[] this->data; }
+
+ template <class T>
+ void writeElement(T a) {
+ *reinterpret_cast<T*>(&this->data[this->offset]) = a;
+ this->offset += sizeof(T);
+ }
+
+ void writeMem(const void* data, unsigned long length) {
+ memcpy(&this->data[this->offset], data, length);
+ this->offset += length;
+ }
+
+ void align() {
+ while (this->offset & 3) {
+ this->data[this->offset] = 0;
+ this->offset++;
+ }
+ }
+
+ unsigned char* data;
+ int offset;
+};
+
+bool UnscaledFontMac::GetFontFileData(FontFileDataOutput aDataCallback,
+ void* aBaton) {
+ // We'll reconstruct a TTF font from the tables we can get from the CGFont
+ CFArrayRef tags = CGFontCopyTableTags(mFont);
+ CFIndex count = CFArrayGetCount(tags);
+
+ TableRecord* records = new TableRecord[count];
+ uint32_t offset = 0;
+ offset += sizeof(uint32_t) * 3;
+ offset += sizeof(uint32_t) * 4 * count;
+ bool CFF = false;
+ for (CFIndex i = 0; i < count; i++) {
+ uint32_t tag = (uint32_t)(uintptr_t)CFArrayGetValueAtIndex(tags, i);
+ if (tag == 0x43464620) { // 'CFF '
+ CFF = true;
+ }
+ CFDataRef data = CGFontCopyTableForTag(mFont, tag);
+ // Bug 1602391 suggests CGFontCopyTableForTag can fail, even though we just
+ // got the tag from the font via CGFontCopyTableTags above. If we can catch
+ // this (e.g. in fuzz-testing) it'd be good to understand when it happens,
+ // but in any case we'll handle it safely below by treating the table as
+ // zero-length.
+ MOZ_ASSERT(data, "failed to get font table data");
+ records[i].tag = tag;
+ records[i].offset = offset;
+ records[i].data = data;
+ if (data) {
+ records[i].length = CFDataGetLength(data);
+ bool skipChecksumAdjust = (tag == 0x68656164); // 'head'
+ records[i].checkSum = CalcTableChecksum(
+ reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(data)),
+ records[i].length, skipChecksumAdjust);
+ offset += records[i].length;
+ // 32 bit align the tables
+ offset = (offset + 3) & ~3;
+ } else {
+ records[i].length = 0;
+ records[i].checkSum = 0;
+ }
+ }
+ CFRelease(tags);
+
+ struct writeBuf buf(offset);
+ // write header/offset table
+ if (CFF) {
+ buf.writeElement(CFSwapInt32HostToBig(0x4f54544f));
+ } else {
+ buf.writeElement(CFSwapInt32HostToBig(0x00010000));
+ }
+ buf.writeElement(CFSwapInt16HostToBig(count));
+ int maxPow2Count = maxPow2LessThanEqual(count);
+ buf.writeElement(CFSwapInt16HostToBig((1 << maxPow2Count) * 16));
+ buf.writeElement(CFSwapInt16HostToBig(maxPow2Count));
+ buf.writeElement(CFSwapInt16HostToBig((count - (1 << maxPow2Count)) * 16));
+
+ // write table record entries
+ for (CFIndex i = 0; i < count; i++) {
+ buf.writeElement(CFSwapInt32HostToBig(records[i].tag));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].checkSum));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].offset));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].length));
+ }
+
+ // write tables
+ int checkSumAdjustmentOffset = 0;
+ for (CFIndex i = 0; i < count; i++) {
+ if (records[i].tag == 0x68656164) {
+ checkSumAdjustmentOffset = buf.offset + 2 * 4;
+ }
+ if (records[i].data) {
+ buf.writeMem(CFDataGetBytePtr(records[i].data), records[i].length);
+ buf.align();
+ CFRelease(records[i].data);
+ }
+ }
+ delete[] records;
+
+ // clear the checksumAdjust field before checksumming the whole font
+ memset(&buf.data[checkSumAdjustmentOffset], 0, sizeof(uint32_t));
+ uint32_t fontChecksum = CFSwapInt32HostToBig(
+ 0xb1b0afba -
+ CalcTableChecksum(reinterpret_cast<const uint32_t*>(buf.data), offset));
+ // set checkSumAdjust to the computed checksum
+ memcpy(&buf.data[checkSumAdjustmentOffset], &fontChecksum,
+ sizeof(fontChecksum));
+
+ // we always use an index of 0
+ aDataCallback(buf.data, buf.offset, 0, aBaton);
+
+ return true;
+}
+
+bool UnscaledFontMac::GetFontDescriptor(FontDescriptorOutput aCb,
+ void* aBaton) {
+ if (mIsDataFont) {
+ return false;
+ }
+
+ AutoRelease<CFStringRef> psname(CGFontCopyPostScriptName(mFont));
+ if (!psname) {
+ return false;
+ }
+
+ char buf[256];
+ const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8);
+ if (!cstr) {
+ if (!CFStringGetCString(psname, buf, sizeof(buf), kCFStringEncodingUTF8)) {
+ return false;
+ }
+ cstr = buf;
+ }
+
+ aCb(reinterpret_cast<const uint8_t*>(cstr), strlen(cstr), 0, aBaton);
+ return true;
+}
+
+static void CollectVariationsFromDictionary(const void* aKey,
+ const void* aValue,
+ void* aContext) {
+ auto keyPtr = static_cast<const CFTypeRef>(aKey);
+ auto valuePtr = static_cast<const CFTypeRef>(aValue);
+ auto outVariations = static_cast<std::vector<FontVariation>*>(aContext);
+ if (CFGetTypeID(keyPtr) == CFNumberGetTypeID() &&
+ CFGetTypeID(valuePtr) == CFNumberGetTypeID()) {
+ uint64_t t;
+ double v;
+ if (CFNumberGetValue(static_cast<CFNumberRef>(keyPtr), kCFNumberSInt64Type,
+ &t) &&
+ CFNumberGetValue(static_cast<CFNumberRef>(valuePtr),
+ kCFNumberDoubleType, &v)) {
+ outVariations->push_back(FontVariation{uint32_t(t), float(v)});
+ }
+ }
+}
+
+static bool GetVariationsForCTFont(CTFontRef aCTFont,
+ std::vector<FontVariation>* aOutVariations) {
+ if (!aCTFont) {
+ return true;
+ }
+ AutoRelease<CFDictionaryRef> dict(CTFontCopyVariation(aCTFont));
+ CFIndex count = dict ? CFDictionaryGetCount(dict) : 0;
+ if (count > 0) {
+ aOutVariations->reserve(count);
+ CFDictionaryApplyFunction(dict, CollectVariationsFromDictionary,
+ aOutVariations);
+ }
+ return true;
+}
+
+bool ScaledFontMac::GetFontInstanceData(FontInstanceDataOutput aCb,
+ void* aBaton) {
+ // Collect any variation settings that were incorporated into the CTFont.
+ std::vector<FontVariation> variations;
+ if (!GetVariationsForCTFont(mCTFont, &variations)) {
+ return false;
+ }
+
+ InstanceData instance(this);
+ aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance),
+ variations.data(), variations.size(), aBaton);
+ return true;
+}
+
+bool ScaledFontMac::GetWRFontInstanceOptions(
+ Maybe<wr::FontInstanceOptions>* aOutOptions,
+ Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
+ std::vector<FontVariation>* aOutVariations) {
+ GetVariationsForCTFont(mCTFont, aOutVariations);
+
+ wr::FontInstanceOptions options;
+ options.render_mode = wr::FontRenderMode::Subpixel;
+ options.flags = wr::FontInstanceFlags::SUBPIXEL_POSITION;
+ if (mUseFontSmoothing) {
+ options.flags |= wr::FontInstanceFlags::FONT_SMOOTHING;
+ }
+ if (mApplySyntheticBold) {
+ options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD;
+ }
+ if (mHasColorGlyphs) {
+ options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
+ }
+ options.bg_color = wr::ToColorU(mFontSmoothingBackgroundColor);
+ options.synthetic_italics =
+ wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());
+ *aOutOptions = Some(options);
+ return true;
+}
+
+ScaledFontMac::InstanceData::InstanceData(
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions)
+ : mUseFontSmoothing(true),
+ mApplySyntheticBold(false),
+ mHasColorGlyphs(false) {
+ if (aOptions) {
+ if (!(aOptions->flags & wr::FontInstanceFlags::FONT_SMOOTHING)) {
+ mUseFontSmoothing = false;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) {
+ mApplySyntheticBold = true;
+ }
+ if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) {
+ mHasColorGlyphs = true;
+ }
+ if (aOptions->bg_color.a != 0) {
+ mFontSmoothingBackgroundColor =
+ DeviceColor::FromU8(aOptions->bg_color.r, aOptions->bg_color.g,
+ aOptions->bg_color.b, aOptions->bg_color.a);
+ }
+ }
+}
+
+static CFDictionaryRef CreateVariationDictionaryOrNull(
+ CGFontRef aCGFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache,
+ uint32_t aVariationCount, const FontVariation* aVariations) {
+ if (!aCGAxesCache) {
+ aCGAxesCache = CGFontCopyVariationAxes(aCGFont);
+ if (!aCGAxesCache) {
+ return nullptr;
+ }
+ }
+ if (!aCTAxesCache) {
+ AutoRelease<CTFontRef> ctFont(
+ CTFontCreateWithGraphicsFont(aCGFont, 0, nullptr, nullptr));
+ aCTAxesCache = CTFontCopyVariationAxes(ctFont);
+ if (!aCTAxesCache) {
+ return nullptr;
+ }
+ }
+
+ CFIndex axisCount = CFArrayGetCount(aCTAxesCache);
+ if (CFArrayGetCount(aCGAxesCache) != axisCount) {
+ return nullptr;
+ }
+
+ AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ // Number of variation settings passed in the aVariations parameter.
+ // This will typically be a very low value, so we just linear-search them.
+ bool allDefaultValues = true;
+
+ for (CFIndex i = 0; i < axisCount; ++i) {
+ // We sanity-check the axis info found in the CTFont, and bail out
+ // (returning null) if it doesn't have the expected types.
+ CFTypeRef axisInfo = CFArrayGetValueAtIndex(aCTAxesCache, i);
+ if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
+ return nullptr;
+ }
+ CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
+
+ CFTypeRef axisTag =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
+ if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ int64_t tagLong;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
+ kCFNumberSInt64Type, &tagLong)) {
+ return nullptr;
+ }
+
+ axisInfo = CFArrayGetValueAtIndex(aCGAxesCache, i);
+ if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
+ return nullptr;
+ }
+ CFTypeRef axisName = CFDictionaryGetValue(
+ static_cast<CFDictionaryRef>(axisInfo), kCGFontVariationAxisName);
+ if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) {
+ return nullptr;
+ }
+
+ // Clamp axis values to the supported range.
+ CFTypeRef min =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
+ CFTypeRef max =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
+ CFTypeRef def =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
+ if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
+ CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
+ CFGetTypeID(def) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ double minDouble;
+ double maxDouble;
+ double defDouble;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
+ &minDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
+ &maxDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
+ &defDouble)) {
+ return nullptr;
+ }
+
+ double value = defDouble;
+ for (uint32_t j = 0; j < aVariationCount; ++j) {
+ if (aVariations[j].mTag == tagLong) {
+ value = std::min(std::max<double>(aVariations[j].mValue, minDouble),
+ maxDouble);
+ if (value != defDouble) {
+ allDefaultValues = false;
+ }
+ break;
+ }
+ }
+ AutoRelease<CFNumberRef> valueNumber(
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
+ CFDictionaryAddValue(dict, axisName, valueNumber);
+ }
+
+ if (allDefaultValues) {
+ // We didn't actually set any non-default values, so throw away the
+ // variations dictionary and just use the default rendering.
+ return nullptr;
+ }
+
+ return dict.forget();
+}
+
+static CFDictionaryRef CreateVariationTagDictionaryOrNull(
+ CTFontRef aCTFont, uint32_t aVariationCount,
+ const FontVariation* aVariations) {
+ AutoRelease<CFArrayRef> axes(CTFontCopyVariationAxes(aCTFont));
+ CFIndex axisCount = CFArrayGetCount(axes);
+
+ AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
+
+ // Number of variation settings passed in the aVariations parameter.
+ // This will typically be a very low value, so we just linear-search them.
+ bool allDefaultValues = true;
+
+ for (CFIndex i = 0; i < axisCount; ++i) {
+ // We sanity-check the axis info found in the CTFont, and bail out
+ // (returning null) if it doesn't have the expected types.
+ CFTypeRef axisInfo = CFArrayGetValueAtIndex(axes, i);
+ if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) {
+ return nullptr;
+ }
+ CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo);
+
+ CFTypeRef axisTag =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
+ if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ int64_t tagLong;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag),
+ kCFNumberSInt64Type, &tagLong)) {
+ return nullptr;
+ }
+
+ // Clamp axis values to the supported range.
+ CFTypeRef min =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey);
+ CFTypeRef max =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey);
+ CFTypeRef def =
+ CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey);
+ if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max ||
+ CFGetTypeID(max) != CFNumberGetTypeID() || !def ||
+ CFGetTypeID(def) != CFNumberGetTypeID()) {
+ return nullptr;
+ }
+ double minDouble;
+ double maxDouble;
+ double defDouble;
+ if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType,
+ &minDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType,
+ &maxDouble) ||
+ !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType,
+ &defDouble)) {
+ return nullptr;
+ }
+
+ double value = defDouble;
+ for (uint32_t j = 0; j < aVariationCount; ++j) {
+ if (aVariations[j].mTag == tagLong) {
+ value = std::min(std::max<double>(aVariations[j].mValue, minDouble),
+ maxDouble);
+ if (value != defDouble) {
+ allDefaultValues = false;
+ }
+ break;
+ }
+ }
+ AutoRelease<CFNumberRef> valueNumber(
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
+ CFDictionaryAddValue(dict, axisTag, valueNumber);
+ }
+
+ if (allDefaultValues) {
+ // We didn't actually set any non-default values, so throw away the
+ // variations dictionary and just use the default rendering.
+ return nullptr;
+ }
+
+ return dict.forget();
+}
+
+/* static */
+CGFontRef UnscaledFontMac::CreateCGFontWithVariations(
+ CGFontRef aFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache,
+ uint32_t aVariationCount, const FontVariation* aVariations) {
+ if (!aVariationCount) {
+ return nullptr;
+ }
+ MOZ_ASSERT(aVariations);
+
+ AutoRelease<CFDictionaryRef> varDict(CreateVariationDictionaryOrNull(
+ aFont, aCGAxesCache, aCTAxesCache, aVariationCount, aVariations));
+ if (!varDict) {
+ return nullptr;
+ }
+
+ return CGFontCreateCopyWithVariations(aFont, varDict);
+}
+
+already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFont(
+ Float aGlyphSize, const uint8_t* aInstanceData,
+ uint32_t aInstanceDataLength, const FontVariation* aVariations,
+ uint32_t aNumVariations)
+
+{
+ if (aInstanceDataLength < sizeof(ScaledFontMac::InstanceData)) {
+ gfxWarning() << "Mac scaled font instance data is truncated.";
+ return nullptr;
+ }
+ const ScaledFontMac::InstanceData& instanceData =
+ *reinterpret_cast<const ScaledFontMac::InstanceData*>(aInstanceData);
+ RefPtr<ScaledFontMac> scaledFont;
+ if (mFontDesc) {
+ AutoRelease<CTFontRef> font(
+ CTFontCreateWithFontDescriptor(mFontDesc, aGlyphSize, nullptr));
+ if (aNumVariations > 0) {
+ AutoRelease<CFDictionaryRef> varDict(CreateVariationTagDictionaryOrNull(
+ font, aNumVariations, aVariations));
+ CFDictionaryRef varAttr = CFDictionaryCreate(
+ nullptr, (const void**)&kCTFontVariationAttribute,
+ (const void**)&varDict, 1, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ AutoRelease<CTFontDescriptorRef> fontDesc(
+ CTFontDescriptorCreateCopyWithAttributes(mFontDesc, varAttr));
+ if (!fontDesc) {
+ return nullptr;
+ }
+ font = CTFontCreateWithFontDescriptor(fontDesc, aGlyphSize, nullptr);
+ }
+ scaledFont = new ScaledFontMac(
+ font, this, instanceData.mFontSmoothingBackgroundColor,
+ instanceData.mUseFontSmoothing, instanceData.mApplySyntheticBold,
+ instanceData.mHasColorGlyphs);
+ } else {
+ CGFontRef fontRef = mFont;
+ if (aNumVariations > 0) {
+ CGFontRef varFont = CreateCGFontWithVariations(
+ mFont, mCGAxesCache, mCTAxesCache, aNumVariations, aVariations);
+ if (varFont) {
+ fontRef = varFont;
+ }
+ }
+
+ scaledFont = new ScaledFontMac(fontRef, this, aGlyphSize, fontRef != mFont,
+ instanceData.mFontSmoothingBackgroundColor,
+ instanceData.mUseFontSmoothing,
+ instanceData.mApplySyntheticBold,
+ instanceData.mHasColorGlyphs);
+ }
+ return scaledFont.forget();
+}
+
+already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFontFromWRFont(
+ Float aGlyphSize, const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, uint32_t aNumVariations) {
+ ScaledFontMac::InstanceData instanceData(aOptions, aPlatformOptions);
+ return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData),
+ sizeof(instanceData), aVariations, aNumVariations);
+}
+
+cairo_font_face_t* ScaledFontMac::CreateCairoFontFace(
+ cairo_font_options_t* aFontOptions) {
+ MOZ_ASSERT(mFont);
+ return cairo_quartz_font_face_create_for_cgfont(mFont);
+}
+
+already_AddRefed<UnscaledFont> UnscaledFontMac::CreateFromFontDescriptor(
+ const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) {
+ if (aDataLength == 0) {
+ gfxWarning() << "Mac font descriptor is truncated.";
+ return nullptr;
+ }
+ CFStringRef name =
+ CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aData,
+ aDataLength, kCFStringEncodingUTF8, false);
+ if (!name) {
+ return nullptr;
+ }
+ CGFontRef font = CGFontCreateWithFontName(name);
+ CFRelease(name);
+ if (!font) {
+ return nullptr;
+ }
+ RefPtr<UnscaledFont> unscaledFont = new UnscaledFontMac(font);
+ CFRelease(font);
+ return unscaledFont.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla