fune/gfx/2d/ScaledFontMac.cpp
Jonathan Kew 5ce9bfb3a4 Bug 1866105 - Add the font file path to the font descriptor sent to webrender on macOS. r=gfx-reviewers,lsalzman
This allows us to skip additional font registration in content processes,
as it ensures consistent resolution of the font descriptor even when there
are multiple font resources with the same psname present.

Differential Revision: https://phabricator.services.mozilla.com/D195418
2023-12-05 10:59:06 +00:00

826 lines
28 KiB
C++

/* -*- 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 "nsCocoaFeatures.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 "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);
}
}
AutoRelease<T>& operator=(const T& aObject) {
if (aObject != mObject) {
if (mObject) {
CFRelease(mObject);
}
mObject = aObject;
}
return *this;
}
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 CGFont, and applying attributes from (optional) aFontDesc.
CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize,
bool aInstalledFont,
CTFontDescriptorRef aFontDesc) {
// New implementation (see bug 1856035) for macOS 13+.
if (nsCocoaFeatures::OnVenturaOrLater()) {
// Create CTFont, applying any descriptor that was passed (used by
// gfxCoreTextShaper to set features).
AutoRelease<CTFontRef> ctFont(
CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, aFontDesc));
AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
if (vars) {
// Create an attribute dictionary containing the variations.
AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate(
nullptr, (const void**)&kCTFontVariationAttribute,
(const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
// Get the original descriptor from the CTFont, then add the variations
// attribute to it.
AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont));
desc = CTFontDescriptorCreateCopyWithAttributes(desc, attrs);
// Return a copy of the font that has the variations added.
return CTFontCreateCopyWithAttributes(ctFont, 0.0, nullptr, desc);
}
// No variations to set, just return the default CTFont.
return ctFont.forget();
}
// Older implementation used up to macOS 12.
CTFontRef ctFont;
if (aInstalledFont) {
AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont));
if (vars) {
AutoRelease<CFDictionaryRef> varAttr(CFDictionaryCreate(
nullptr, (const void**)&kCTFontVariationAttribute,
(const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
AutoRelease<CTFontDescriptorRef> varDesc(
aFontDesc
? ::CTFontDescriptorCreateCopyWithAttributes(aFontDesc, varAttr)
: ::CTFontDescriptorCreateWithAttributes(varAttr));
ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, 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,
bool aUseFontSmoothing, bool aApplySyntheticBold,
bool aHasColorGlyphs)
: ScaledFontBase(aUnscaledFont, aSize),
mFont(aFont),
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,
bool aUseFontSmoothing, bool aApplySyntheticBold,
bool aHasColorGlyphs)
: ScaledFontBase(aUnscaledFont, CTFontGetSize(aFont)),
mCTFont(aFont),
mUseFontSmoothing(aUseFontSmoothing),
mApplySyntheticBold(aApplySyntheticBold),
mHasColorGlyphs(aHasColorGlyphs) {
mFont = CTFontCopyGraphicsFont(aFont, nullptr);
CFRetain(mCTFont);
}
ScaledFontMac::~ScaledFontMac() {
CFRelease(mCTFont);
CGFontRelease(mFont);
}
SkTypeface* ScaledFontMac::CreateSkTypeface() {
return SkMakeTypefaceFromCTFont(mCTFont).release();
}
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[1024];
const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8);
if (!cstr) {
if (!CFStringGetCString(psname, buf, sizeof(buf), kCFStringEncodingUTF8)) {
return false;
}
cstr = buf;
}
nsAutoCString descriptor(cstr);
uint32_t psNameLen = descriptor.Length();
AutoRelease<CTFontRef> ctFont(
CTFontCreateWithGraphicsFont(mFont, 0, nullptr, nullptr));
AutoRelease<CFURLRef> fontUrl(
(CFURLRef)CTFontCopyAttribute(ctFont, kCTFontURLAttribute));
if (fontUrl) {
CFStringRef urlStr(CFURLCopyFileSystemPath(fontUrl, kCFURLPOSIXPathStyle));
cstr = CFStringGetCStringPtr(urlStr, kCFStringEncodingUTF8);
if (!cstr) {
if (!CFStringGetCString(urlStr, buf, sizeof(buf),
kCFStringEncodingUTF8)) {
return false;
}
cstr = buf;
}
descriptor.Append(cstr);
}
aCb(reinterpret_cast<const uint8_t*>(descriptor.get()), descriptor.Length(),
psNameLen, 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.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;
}
}
}
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));
if (varDict) {
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.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.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;
}
AutoRelease<CFStringRef> name(
CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aData, aIndex,
kCFStringEncodingUTF8, false));
if (!name) {
return nullptr;
}
CGFontRef font = CGFontCreateWithFontName(name);
if (!font) {
return nullptr;
}
// If the descriptor included a font file path, apply that attribute and
// refresh the font in case it changed.
if (aIndex < aDataLength) {
AutoRelease<CFStringRef> path(CFStringCreateWithBytes(
kCFAllocatorDefault, (const UInt8*)aData + aIndex, aDataLength - aIndex,
kCFStringEncodingUTF8, false));
AutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath(
kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, false));
AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate(
nullptr, (const void**)&kCTFontURLAttribute, (const void**)&url, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
AutoRelease<CTFontRef> ctFont(
CTFontCreateWithGraphicsFont(font, 0.0, nullptr, nullptr));
AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont));
AutoRelease<CTFontDescriptorRef> newDesc(
CTFontDescriptorCreateCopyWithAttributes(desc, attrs));
AutoRelease<CTFontRef> newFont(
CTFontCreateWithFontDescriptor(newDesc, 0.0, nullptr));
CFRelease(font);
font = CTFontCopyGraphicsFont(newFont, nullptr);
}
RefPtr<UnscaledFont> unscaledFont = new UnscaledFontMac(font);
CFRelease(font);
return unscaledFont.forget();
}
} // namespace gfx
} // namespace mozilla