diff options
Diffstat (limited to 'gfx/thebes')
-rw-r--r-- | gfx/thebes/PrintTargetCG.cpp | 120 | ||||
-rw-r--r-- | gfx/thebes/PrintTargetCG.h | 42 | ||||
-rw-r--r-- | gfx/thebes/gfxCoreTextShaper.cpp | 800 | ||||
-rw-r--r-- | gfx/thebes/gfxCoreTextShaper.h | 71 | ||||
-rw-r--r-- | gfx/thebes/gfxFontUtils.cpp | 41 | ||||
-rw-r--r-- | gfx/thebes/gfxMacFont.cpp | 475 | ||||
-rw-r--r-- | gfx/thebes/gfxMacFont.h | 102 | ||||
-rw-r--r-- | gfx/thebes/gfxMacPlatformFontList.h | 182 | ||||
-rw-r--r-- | gfx/thebes/gfxMacPlatformFontList.mm | 1444 | ||||
-rw-r--r-- | gfx/thebes/gfxPlatform.cpp | 7 | ||||
-rw-r--r-- | gfx/thebes/gfxPlatformMac.cpp | 617 | ||||
-rw-r--r-- | gfx/thebes/gfxPlatformMac.h | 93 | ||||
-rw-r--r-- | gfx/thebes/gfxPrefs.h | 3 | ||||
-rw-r--r-- | gfx/thebes/gfxQuartzNativeDrawing.cpp | 74 | ||||
-rw-r--r-- | gfx/thebes/gfxQuartzNativeDrawing.h | 71 | ||||
-rw-r--r-- | gfx/thebes/gfxQuartzSurface.cpp | 137 | ||||
-rw-r--r-- | gfx/thebes/gfxQuartzSurface.h | 43 | ||||
-rw-r--r-- | gfx/thebes/gfxTextRun.cpp | 5 | ||||
-rw-r--r-- | gfx/thebes/moz.build | 25 |
19 files changed, 4344 insertions, 8 deletions
diff --git a/gfx/thebes/PrintTargetCG.cpp b/gfx/thebes/PrintTargetCG.cpp new file mode 100644 index 0000000000..5fe838182a --- /dev/null +++ b/gfx/thebes/PrintTargetCG.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "PrintTargetCG.h" + +#include "cairo.h" +#include "cairo-quartz.h" +#include "mozilla/gfx/HelpersCairo.h" + +namespace mozilla { +namespace gfx { + +PrintTargetCG::PrintTargetCG(cairo_surface_t* aCairoSurface, + const IntSize& aSize) + : PrintTarget(aCairoSurface, aSize) +{ + // TODO: Add memory reporting like gfxQuartzSurface. + //RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); +} + +/* static */ already_AddRefed<PrintTargetCG> +PrintTargetCG::CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat) +{ + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + unsigned int width = static_cast<unsigned int>(aSize.width); + unsigned int height = static_cast<unsigned int>(aSize.height); + + cairo_format_t cformat = GfxFormatToCairoFormat(aFormat); + cairo_surface_t* surface = + cairo_quartz_surface_create(cformat, width, height); + + if (cairo_surface_status(surface)) { + return nullptr; + } + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetCG> target = new PrintTargetCG(surface, aSize); + + return target.forget(); +} + +/* static */ already_AddRefed<PrintTargetCG> +PrintTargetCG::CreateOrNull(CGContextRef aContext, const IntSize& aSize) +{ + if (!Factory::CheckSurfaceSize(aSize)) { + return nullptr; + } + + unsigned int width = static_cast<unsigned int>(aSize.width); + unsigned int height = static_cast<unsigned int>(aSize.height); + + cairo_surface_t* surface = + cairo_quartz_surface_create_for_cg_context(aContext, width, height); + + if (cairo_surface_status(surface)) { + return nullptr; + } + + // The new object takes ownership of our surface reference. + RefPtr<PrintTargetCG> target = new PrintTargetCG(surface, aSize); + + return target.forget(); +} + +static size_t +PutBytesNull(void* info, const void* buffer, size_t count) +{ + return count; +} + +already_AddRefed<DrawTarget> +PrintTargetCG::GetReferenceDrawTarget(DrawEventRecorder* aRecorder) +{ + if (!mRefDT) { + const IntSize size(1, 1); + + CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr}; + CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks); + CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr); + CGDataConsumerRelease(consumer); + + cairo_surface_t* similar = + cairo_quartz_surface_create_for_cg_context( + pdfContext, size.width, size.height); + + CGContextRelease(pdfContext); + + if (cairo_surface_status(similar)) { + return nullptr; + } + + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForCairoSurface(similar, size); + + // The DT addrefs the surface, so we need drop our own reference to it: + cairo_surface_destroy(similar); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + + if (aRecorder) { + dt = CreateRecordingDrawTarget(aRecorder, dt); + if (!dt || !dt->IsValid()) { + return nullptr; + } + } + + mRefDT = dt.forget(); + } + return do_AddRef(mRefDT); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/thebes/PrintTargetCG.h b/gfx/thebes/PrintTargetCG.h new file mode 100644 index 0000000000..87dbdbc2c7 --- /dev/null +++ b/gfx/thebes/PrintTargetCG.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef MOZILLA_GFX_PRINTTARGETCG_H +#define MOZILLA_GFX_PRINTTARGETCG_H + +#include <Carbon/Carbon.h> +#include "PrintTarget.h" + +namespace mozilla { +namespace gfx { + +/** + * CoreGraphics printing target. + * + * Note that a CGContextRef obtained from PMSessionGetCGGraphicsContext is + * valid only for the current page. As a consequence instances of this class + * should only be used to print a single page. + */ +class PrintTargetCG final : public PrintTarget +{ +public: + static already_AddRefed<PrintTargetCG> + CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat); + + static already_AddRefed<PrintTargetCG> + CreateOrNull(CGContextRef aContext, const IntSize& aSize); + + virtual already_AddRefed<DrawTarget> + GetReferenceDrawTarget(DrawEventRecorder* aRecorder) final; + +private: + PrintTargetCG(cairo_surface_t* aCairoSurface, + const IntSize& aSize); +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PRINTTARGETCG_H */ diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp new file mode 100644 index 0000000000..08217b82f9 --- /dev/null +++ b/gfx/thebes/gfxCoreTextShaper.cpp @@ -0,0 +1,800 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "mozilla/ArrayUtils.h" +#include "gfxCoreTextShaper.h" +#include "gfxMacFont.h" +#include "gfxFontUtils.h" +#include "gfxTextRun.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/UniquePtrExtensions.h" + +#include <algorithm> + +#include <dlfcn.h> + +using namespace mozilla; + +// standard font descriptors that we construct the first time they're needed +CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = nullptr; +CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr; +CTFontDescriptorRef gfxCoreTextShaper::sIndicFeaturesDescriptor = nullptr; +CTFontDescriptorRef gfxCoreTextShaper::sIndicDisableLigaturesDescriptor = nullptr; + +static CFStringRef sCTWritingDirectionAttributeName = nullptr; + +// See CTStringAttributes.h +enum { + kMyCTWritingDirectionEmbedding = (0 << 1), + kMyCTWritingDirectionOverride = (1 << 1) +}; + +// Helper to create a CFDictionary with the right attributes for shaping our +// text, including imposing the given directionality. +// This will only be called if we're on 10.8 or later. +CFDictionaryRef +gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft) +{ + // Because we always shape unidirectional runs, and may have applied + // directional overrides, we want to force a direction rather than + // allowing CoreText to do its own unicode-based bidi processing. + SInt16 dirOverride = kMyCTWritingDirectionOverride | + (aRightToLeft ? kCTWritingDirectionRightToLeft + : kCTWritingDirectionLeftToRight); + CFNumberRef dirNumber = + ::CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt16Type, &dirOverride); + CFArrayRef dirArray = + ::CFArrayCreate(kCFAllocatorDefault, + (const void **) &dirNumber, 1, + &kCFTypeArrayCallBacks); + ::CFRelease(dirNumber); + CFTypeRef attrs[] = { kCTFontAttributeName, sCTWritingDirectionAttributeName }; + CFTypeRef values[] = { mCTFont, dirArray }; + CFDictionaryRef attrDict = + ::CFDictionaryCreate(kCFAllocatorDefault, + attrs, values, ArrayLength(attrs), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + ::CFRelease(dirArray); + return attrDict; +} + +CFDictionaryRef +gfxCoreTextShaper::CreateAttrDictWithoutDirection() +{ + CFTypeRef attrs[] = { kCTFontAttributeName }; + CFTypeRef values[] = { mCTFont }; + CFDictionaryRef attrDict = + ::CFDictionaryCreate(kCFAllocatorDefault, + attrs, values, ArrayLength(attrs), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + return attrDict; +} + +gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont) + : gfxFontShaper(aFont) + , mAttributesDictLTR(nullptr) + , mAttributesDictRTL(nullptr) +{ + static bool sInitialized = false; + if (!sInitialized) { + CFStringRef* pstr = (CFStringRef*) + dlsym(RTLD_DEFAULT, "kCTWritingDirectionAttributeName"); + if (pstr) { + sCTWritingDirectionAttributeName = *pstr; + } + sInitialized = true; + } + + // Create our CTFontRef + mCTFont = CreateCTFontWithFeatures(aFont->GetAdjustedSize(), + GetDefaultFeaturesDescriptor()); +} + +gfxCoreTextShaper::~gfxCoreTextShaper() +{ + if (mAttributesDictLTR) { + ::CFRelease(mAttributesDictLTR); + } + if (mAttributesDictRTL) { + ::CFRelease(mAttributesDictRTL); + } + if (mCTFont) { + ::CFRelease(mCTFont); + } +} + +static bool +IsBuggyIndicScript(unicode::Script aScript) +{ + return aScript == unicode::Script::BENGALI || + aScript == unicode::Script::KANNADA; +} + +bool +gfxCoreTextShaper::ShapeText(DrawTarget *aDrawTarget, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + Script aScript, + bool aVertical, + gfxShapedText *aShapedText) +{ + // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out. + bool isRightToLeft = aShapedText->IsRightToLeft(); + const UniChar* text = reinterpret_cast<const UniChar*>(aText); + uint32_t length = aLength; + + uint32_t startOffset; + CFStringRef stringObj; + CFDictionaryRef attrObj; + + if (sCTWritingDirectionAttributeName) { + startOffset = 0; + stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + text, length, + kCFAllocatorNull); + + // Get an attributes dictionary suitable for shaping text in the + // current direction, creating it if necessary. + attrObj = isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR; + if (!attrObj) { + attrObj = CreateAttrDict(isRightToLeft); + (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj; + } + } else { + // OS is too old to support kCTWritingDirectionAttributeName: + // we need to bidi-wrap the text if the run is RTL, + // or if it is an LTR run but may contain (overridden) RTL chars + bool bidiWrap = isRightToLeft; + if (!bidiWrap && !aShapedText->TextIs8Bit()) { + uint32_t i; + for (i = 0; i < length; ++i) { + if (gfxFontUtils::PotentialRTLChar(aText[i])) { + bidiWrap = true; + break; + } + } + } + + // If there's a possibility of any bidi, we wrap the text with + // direction overrides to ensure neutrals or characters that were + // bidi-overridden in HTML behave properly. + static const UniChar beginLTR[] = { 0x202d, 0x20 }; + static const UniChar beginRTL[] = { 0x202e, 0x20 }; + static const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c }; + + if (bidiWrap) { + startOffset = isRightToLeft ? ArrayLength(beginRTL) + : ArrayLength(beginLTR); + CFMutableStringRef mutableString = + ::CFStringCreateMutable(kCFAllocatorDefault, + length + startOffset + + ArrayLength(endBidiWrap)); + ::CFStringAppendCharacters(mutableString, + isRightToLeft ? beginRTL : beginLTR, + startOffset); + ::CFStringAppendCharacters(mutableString, text, length); + ::CFStringAppendCharacters(mutableString, endBidiWrap, + ArrayLength(endBidiWrap)); + stringObj = mutableString; + } else { + startOffset = 0; + stringObj = + ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + text, length, + kCFAllocatorNull); + } + + // Get an attributes dictionary suitable for shaping text, + // creating it if necessary. (This dict is not LTR-specific, + // but we use that field to store it anyway.) + if (!mAttributesDictLTR) { + mAttributesDictLTR = CreateAttrDictWithoutDirection(); + } + attrObj = mAttributesDictLTR; + } + + CTFontRef tempCTFont = nullptr; + if (IsBuggyIndicScript(aScript)) { + // To work around buggy Indic AAT fonts shipped with OS X, + // we re-enable the Line Initial Smart Swashes feature that is needed + // for "split vowels" to work in at least Bengali and Kannada fonts. + // Affected fonts include Bangla MN, Bangla Sangam MN, Kannada MN, + // Kannada Sangam MN. See bugs 686225, 728557, 953231, 1145515. + tempCTFont = + CreateCTFontWithFeatures(::CTFontGetSize(mCTFont), + aShapedText->DisableLigatures() + ? GetIndicDisableLigaturesDescriptor() + : GetIndicFeaturesDescriptor()); + } else if (aShapedText->DisableLigatures()) { + // For letterspacing (or maybe other situations) we need to make + // a copy of the CTFont with the ligature feature disabled. + tempCTFont = + CreateCTFontWithFeatures(::CTFontGetSize(mCTFont), + GetDisableLigaturesDescriptor()); + } + + // For the disabled-ligature or buggy-indic-font case, we need to replace + // the standard CTFont in the attribute dictionary with a tweaked version. + CFMutableDictionaryRef mutableAttr = nullptr; + if (tempCTFont) { + mutableAttr = ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2, + attrObj); + ::CFDictionaryReplaceValue(mutableAttr, + kCTFontAttributeName, tempCTFont); + // Having created the dict, we're finished with our temporary + // Indic and/or ligature-disabled CTFontRef. + ::CFRelease(tempCTFont); + attrObj = mutableAttr; + } + + // Now we can create an attributed string + CFAttributedStringRef attrStringObj = + ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj); + ::CFRelease(stringObj); + + // Create the CoreText line from our string, then we're done with it + CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj); + ::CFRelease(attrStringObj); + + // and finally retrieve the glyph data and store into the gfxTextRun + CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line); + uint32_t numRuns = ::CFArrayGetCount(glyphRuns); + + // Iterate through the glyph runs. + // Note that this includes the bidi wrapper, so we have to be careful + // not to include the extra glyphs from there + bool success = true; + for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) { + CTRunRef aCTRun = + (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex); + // If the range is purely within bidi-wrapping text, ignore it. + CFRange range = ::CTRunGetStringRange(aCTRun); + if (uint32_t(range.location + range.length) <= startOffset || + range.location - startOffset >= aLength) { + continue; + } + CFDictionaryRef runAttr = ::CTRunGetAttributes(aCTRun); + if (runAttr != attrObj) { + // If Core Text manufactured a new dictionary, this may indicate + // unexpected font substitution. In that case, we fail (and fall + // back to harfbuzz shaping)... + const void* font1 = + ::CFDictionaryGetValue(attrObj, kCTFontAttributeName); + const void* font2 = + ::CFDictionaryGetValue(runAttr, kCTFontAttributeName); + if (font1 != font2) { + // ...except that if the fallback was only for a variation + // selector or join control that is otherwise unsupported, + // we just ignore it. + if (range.length == 1) { + char16_t ch = aText[range.location - startOffset]; + if (gfxFontUtils::IsJoinControl(ch) || + gfxFontUtils::IsVarSelector(ch)) { + continue; + } + } + NS_WARNING("unexpected font fallback in Core Text"); + success = false; + break; + } + } + if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun, + startOffset) != NS_OK) { + success = false; + break; + } + } + + if (mutableAttr) { + ::CFRelease(mutableAttr); + } + ::CFRelease(line); + + return success; +} + +#define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data; + // some testing indicates that 90%+ of glyph runs will fit + // without requiring a separate allocation + +nsresult +gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText *aShapedText, + uint32_t aOffset, + uint32_t aLength, + CTRunRef aCTRun, + int32_t aStringOffset) +{ + // The word has been bidi-wrapped; aStringOffset is the number + // of chars at the beginning of the CTLine that we should skip. + // aCTRun is a glyph run from the CoreText layout process. + + int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1; + + int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun); + if (numGlyphs == 0) { + return NS_OK; + } + + int32_t wordLength = aLength; + + // character offsets get really confusing here, as we have to keep track of + // (a) the text in the actual textRun we're constructing + // (c) the string that was handed to CoreText, which contains the text of the font run + // plus directional-override padding + // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line + // (but may extend beyond the actual font run into the bidi wrapping text). + // aStringOffset tells us how many initial characters of the line to ignore. + + // get the source string range within the CTLine's text + CFRange stringRange = ::CTRunGetStringRange(aCTRun); + // skip the run if it is entirely outside the actual range of the font run + if (stringRange.location - aStringOffset + stringRange.length <= 0 || + stringRange.location - aStringOffset >= wordLength) { + return NS_OK; + } + + // retrieve the laid-out glyph data from the CTRun + UniquePtr<CGGlyph[]> glyphsArray; + UniquePtr<CGPoint[]> positionsArray; + UniquePtr<CFIndex[]> glyphToCharArray; + const CGGlyph* glyphs = nullptr; + const CGPoint* positions = nullptr; + const CFIndex* glyphToChar = nullptr; + + // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds, + // and so allocating a new array and copying data with CTRunGetGlyphs + // will be extremely rare. + // If this were not the case, we could use an AutoTArray<> to + // try and avoid the heap allocation for small runs. + // It's possible that some future change to CoreText will mean that + // CTRunGetGlyphsPtr fails more often; if this happens, AutoTArray<> + // may become an attractive option. + glyphs = ::CTRunGetGlyphsPtr(aCTRun); + if (!glyphs) { + glyphsArray = MakeUniqueFallible<CGGlyph[]>(numGlyphs); + if (!glyphsArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get()); + glyphs = glyphsArray.get(); + } + + positions = ::CTRunGetPositionsPtr(aCTRun); + if (!positions) { + positionsArray = MakeUniqueFallible<CGPoint[]>(numGlyphs); + if (!positionsArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get()); + positions = positionsArray.get(); + } + + // Remember that the glyphToChar indices relate to the CoreText line, + // not to the beginning of the textRun, the font run, + // or the stringRange of the glyph run + glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun); + if (!glyphToChar) { + glyphToCharArray = MakeUniqueFallible<CFIndex[]>(numGlyphs); + if (!glyphToCharArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get()); + glyphToChar = glyphToCharArray.get(); + } + + double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0), + nullptr, nullptr, nullptr); + + AutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs; + gfxShapedText::CompressedGlyph *charGlyphs = + aShapedText->GetCharacterGlyphs() + aOffset; + + // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph + // to a source text character; we also need the charindex-to-glyphindex mapping to + // find the glyph for a given char. Note that some chars may not map to any glyph + // (ligature continuations), and some may map to several glyphs (eg Indic split vowels). + // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we + // record the last glyph index for cases where the char maps to several glyphs, + // so that our clumping will include all the glyph fragments for the character. + + // The charToGlyph array is indexed by char position within the stringRange of the glyph run. + + static const int32_t NO_GLYPH = -1; + AutoTArray<int32_t,SMALL_GLYPH_RUN> charToGlyphArray; + if (!charToGlyphArray.SetLength(stringRange.length, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + int32_t *charToGlyph = charToGlyphArray.Elements(); + for (int32_t offset = 0; offset < stringRange.length; ++offset) { + charToGlyph[offset] = NO_GLYPH; + } + for (int32_t i = 0; i < numGlyphs; ++i) { + int32_t loc = glyphToChar[i] - stringRange.location; + if (loc >= 0 && loc < stringRange.length) { + charToGlyph[loc] = i; + } + } + + // Find character and glyph clumps that correspond, allowing for ligatures, + // indic reordering, split glyphs, etc. + // + // The idea is that we'll find a character sequence starting at the first char of stringRange, + // and extend it until it includes the character associated with the first glyph; + // we also extend it as long as there are "holes" in the range of glyphs. So we + // will eventually have a contiguous sequence of characters, starting at the beginning + // of the range, that map to a contiguous sequence of glyphs, starting at the beginning + // of the glyph array. That's a clump; then we update the starting positions and repeat. + // + // NB: In the case of RTL layouts, we iterate over the stringRange in reverse. + // + + // This may find characters that fall outside the range 0:wordLength, + // so we won't necessarily use everything we find here. + + bool isRightToLeft = aShapedText->IsRightToLeft(); + int32_t glyphStart = 0; // looking for a clump that starts at this glyph index + int32_t charStart = isRightToLeft ? + stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run) + + while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for + bool inOrder = true; + int32_t charEnd = glyphToChar[glyphStart] - stringRange.location; + NS_WARNING_ASSERTION( + charEnd >= 0 && charEnd < stringRange.length, + "glyph-to-char mapping points outside string range"); + // clamp charEnd to the valid range of the string + charEnd = std::max(charEnd, 0); + charEnd = std::min(charEnd, int32_t(stringRange.length)); + + int32_t glyphEnd = glyphStart; + int32_t charLimit = isRightToLeft ? -1 : stringRange.length; + do { + // This is normally executed once for each iteration of the outer loop, + // but in unusual cases where the character/glyph association is complex, + // the initial character range might correspond to a non-contiguous + // glyph range with "holes" in it. If so, we will repeat this loop to + // extend the character range until we have a contiguous glyph sequence. + NS_ASSERTION((direction > 0 && charEnd < charLimit) || + (direction < 0 && charEnd > charLimit), + "no characters left in range?"); + charEnd += direction; + while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) { + charEnd += direction; + } + + // find the maximum glyph index covered by the clump so far + if (isRightToLeft) { + for (int32_t i = charStart; i > charEnd; --i) { + if (charToGlyph[i] != NO_GLYPH) { + // update extent of glyph range + glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); + } + } + } else { + for (int32_t i = charStart; i < charEnd; ++i) { + if (charToGlyph[i] != NO_GLYPH) { + // update extent of glyph range + glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1); + } + } + } + + if (glyphEnd == glyphStart + 1) { + // for the common case of a single-glyph clump, we can skip the following checks + break; + } + + if (glyphEnd == glyphStart) { + // no glyphs, try to extend the clump + continue; + } + + // check whether all glyphs in the range are associated with the characters + // in our clump; if not, we have a discontinuous range, and should extend it + // unless we've reached the end of the text + bool allGlyphsAreWithinCluster = true; + int32_t prevGlyphCharIndex = charStart; + for (int32_t i = glyphStart; i < glyphEnd; ++i) { + int32_t glyphCharIndex = glyphToChar[i] - stringRange.location; + if (isRightToLeft) { + if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + if (glyphCharIndex > prevGlyphCharIndex) { + inOrder = false; + } + prevGlyphCharIndex = glyphCharIndex; + } else { + if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) { + allGlyphsAreWithinCluster = false; + break; + } + if (glyphCharIndex < prevGlyphCharIndex) { + inOrder = false; + } + prevGlyphCharIndex = glyphCharIndex; + } + } + if (allGlyphsAreWithinCluster) { + break; + } + } while (charEnd != charLimit); + + NS_WARNING_ASSERTION(glyphStart < glyphEnd, + "character/glyph clump contains no glyphs!"); + if (glyphStart == glyphEnd) { + ++glyphStart; // make progress - avoid potential infinite loop + charStart = charEnd; + continue; + } + + NS_WARNING_ASSERTION(charStart != charEnd, + "character/glyph clump contains no characters!"); + if (charStart == charEnd) { + glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s), + // as there's nowhere to attach them + continue; + } + + // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd; + // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature), + // and endCharIndex to the limit (position beyond the last char), + // adjusting for the offset of the stringRange relative to the textRun. + int32_t baseCharIndex, endCharIndex; + if (isRightToLeft) { + while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) { + charEnd--; + } + baseCharIndex = charEnd + stringRange.location - aStringOffset + 1; + endCharIndex = charStart + stringRange.location - aStringOffset + 1; + } else { + while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) { + charEnd++; + } + baseCharIndex = charStart + stringRange.location - aStringOffset; + endCharIndex = charEnd + stringRange.location - aStringOffset; + } + + // Then we check if the clump falls outside our actual string range; if so, just go to the next. + if (endCharIndex <= 0 || baseCharIndex >= wordLength) { + glyphStart = glyphEnd; + charStart = charEnd; + continue; + } + // Ensure we won't try to go beyond the valid length of the word's text + baseCharIndex = std::max(baseCharIndex, 0); + endCharIndex = std::min(endCharIndex, wordLength); + + // Now we're ready to set the glyph info in the textRun; measure the glyph width + // of the first (perhaps only) glyph, to see if it is "Simple" + int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit(); + double toNextGlyph; + if (glyphStart < numGlyphs-1) { + toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; + } else { + toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; + } + int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit); + + // Check if it's a simple one-to-one mapping + int32_t glyphsInClump = glyphEnd - glyphStart; + if (glyphsInClump == 1 && + gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) && + gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && + charGlyphs[baseCharIndex].IsClusterStart() && + positions[glyphStart].y == 0.0) + { + charGlyphs[baseCharIndex].SetSimpleGlyph(advance, + glyphs[glyphStart]); + } else { + // collect all glyphs in a list to be assigned to the first char; + // there must be at least one in the clump, and we already measured its advance, + // hence the placement of the loop-exit test and the measurement of the next glyph + while (1) { + gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement(); + details->mGlyphID = glyphs[glyphStart]; + details->mXOffset = 0; + details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit; + details->mAdvance = advance; + if (++glyphStart >= glyphEnd) { + break; + } + if (glyphStart < numGlyphs-1) { + toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x; + } else { + toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x; + } + advance = int32_t(toNextGlyph * appUnitsPerDevUnit); + } + + gfxTextRun::CompressedGlyph textRunGlyph; + textRunGlyph.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(), + true, detailedGlyphs.Length()); + aShapedText->SetGlyphs(aOffset + baseCharIndex, textRunGlyph, + detailedGlyphs.Elements()); + + detailedGlyphs.Clear(); + } + + // the rest of the chars in the group are ligature continuations, no associated glyphs + while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) { + gfxShapedText::CompressedGlyph &shapedTextGlyph = charGlyphs[baseCharIndex]; + NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(), "overwriting a simple glyph"); + shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(), false, 0); + } + + glyphStart = glyphEnd; + charStart = charEnd; + } + + return NS_OK; +} + +#undef SMALL_GLYPH_RUN + +// Construct the font attribute descriptor that we'll apply by default when +// creating a CTFontRef. This will turn off line-edge swashes by default, +// because we don't know the actual line breaks when doing glyph shaping. + +// We also cache feature descriptors for shaping with disabled ligatures, and +// for buggy Indic AAT font workarounds, created on an as-needed basis. + +#define MAX_FEATURES 3 // max used by any of our Get*Descriptor functions + +CTFontDescriptorRef +gfxCoreTextShaper::CreateFontFeaturesDescriptor( + const std::pair<SInt16,SInt16> aFeatures[], + size_t aCount) +{ + MOZ_ASSERT(aCount <= MAX_FEATURES); + + CFDictionaryRef featureSettings[MAX_FEATURES]; + + for (size_t i = 0; i < aCount; i++) { + CFNumberRef type = ::CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt16Type, + &aFeatures[i].first); + CFNumberRef selector = ::CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt16Type, + &aFeatures[i].second); + + CFTypeRef keys[] = { kCTFontFeatureTypeIdentifierKey, + kCTFontFeatureSelectorIdentifierKey }; + CFTypeRef values[] = { type, selector }; + featureSettings[i] = + ::CFDictionaryCreate(kCFAllocatorDefault, + (const void **) keys, + (const void **) values, + ArrayLength(keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + ::CFRelease(selector); + ::CFRelease(type); + } + + CFArrayRef featuresArray = + ::CFArrayCreate(kCFAllocatorDefault, + (const void **) featureSettings, + aCount, // not ArrayLength(featureSettings), as we + // may not have used all the allocated slots + &kCFTypeArrayCallBacks); + + for (size_t i = 0; i < aCount; i++) { + ::CFRelease(featureSettings[i]); + } + + const CFTypeRef attrKeys[] = { kCTFontFeatureSettingsAttribute }; + const CFTypeRef attrValues[] = { featuresArray }; + CFDictionaryRef attributesDict = + ::CFDictionaryCreate(kCFAllocatorDefault, + (const void **) attrKeys, + (const void **) attrValues, + ArrayLength(attrKeys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + ::CFRelease(featuresArray); + + CTFontDescriptorRef descriptor = + ::CTFontDescriptorCreateWithAttributes(attributesDict); + ::CFRelease(attributesDict); + + return descriptor; +} + +CTFontDescriptorRef +gfxCoreTextShaper::GetDefaultFeaturesDescriptor() +{ + if (sDefaultFeaturesDescriptor == nullptr) { + const std::pair<SInt16,SInt16> kDefaultFeatures[] = { + { kSmartSwashType, kLineInitialSwashesOffSelector }, + { kSmartSwashType, kLineFinalSwashesOffSelector } + }; + sDefaultFeaturesDescriptor = + CreateFontFeaturesDescriptor(kDefaultFeatures, + ArrayLength(kDefaultFeatures)); + } + return sDefaultFeaturesDescriptor; +} + +CTFontDescriptorRef +gfxCoreTextShaper::GetDisableLigaturesDescriptor() +{ + if (sDisableLigaturesDescriptor == nullptr) { + const std::pair<SInt16,SInt16> kDisableLigatures[] = { + { kSmartSwashType, kLineInitialSwashesOffSelector }, + { kSmartSwashType, kLineFinalSwashesOffSelector }, + { kLigaturesType, kCommonLigaturesOffSelector } + }; + sDisableLigaturesDescriptor = + CreateFontFeaturesDescriptor(kDisableLigatures, + ArrayLength(kDisableLigatures)); + } + return sDisableLigaturesDescriptor; +} + +CTFontDescriptorRef +gfxCoreTextShaper::GetIndicFeaturesDescriptor() +{ + if (sIndicFeaturesDescriptor == nullptr) { + const std::pair<SInt16,SInt16> kIndicFeatures[] = { + { kSmartSwashType, kLineFinalSwashesOffSelector } + }; + sIndicFeaturesDescriptor = + CreateFontFeaturesDescriptor(kIndicFeatures, + ArrayLength(kIndicFeatures)); + } + return sIndicFeaturesDescriptor; +} + +CTFontDescriptorRef +gfxCoreTextShaper::GetIndicDisableLigaturesDescriptor() +{ + if (sIndicDisableLigaturesDescriptor == nullptr) { + const std::pair<SInt16,SInt16> kIndicDisableLigatures[] = { + { kSmartSwashType, kLineFinalSwashesOffSelector }, + { kLigaturesType, kCommonLigaturesOffSelector } + }; + sIndicDisableLigaturesDescriptor = + CreateFontFeaturesDescriptor(kIndicDisableLigatures, + ArrayLength(kIndicDisableLigatures)); + } + return sIndicDisableLigaturesDescriptor; +} + +CTFontRef +gfxCoreTextShaper::CreateCTFontWithFeatures(CGFloat aSize, + CTFontDescriptorRef aDescriptor) +{ + gfxMacFont *f = static_cast<gfxMacFont*>(mFont); + return ::CTFontCreateWithGraphicsFont(f->GetCGFontRef(), aSize, nullptr, + aDescriptor); +} + +void +gfxCoreTextShaper::Shutdown() // [static] +{ + if (sIndicDisableLigaturesDescriptor != nullptr) { + ::CFRelease(sIndicDisableLigaturesDescriptor); + sIndicDisableLigaturesDescriptor = nullptr; + } + if (sIndicFeaturesDescriptor != nullptr) { + ::CFRelease(sIndicFeaturesDescriptor); + sIndicFeaturesDescriptor = nullptr; + } + if (sDisableLigaturesDescriptor != nullptr) { + ::CFRelease(sDisableLigaturesDescriptor); + sDisableLigaturesDescriptor = nullptr; + } + if (sDefaultFeaturesDescriptor != nullptr) { + ::CFRelease(sDefaultFeaturesDescriptor); + sDefaultFeaturesDescriptor = nullptr; + } +} diff --git a/gfx/thebes/gfxCoreTextShaper.h b/gfx/thebes/gfxCoreTextShaper.h new file mode 100644 index 0000000000..8e5d24f91f --- /dev/null +++ b/gfx/thebes/gfxCoreTextShaper.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef GFX_CORETEXTSHAPER_H +#define GFX_CORETEXTSHAPER_H + +#include "gfxFont.h" + +#include <ApplicationServices/ApplicationServices.h> + +class gfxMacFont; + +class gfxCoreTextShaper : public gfxFontShaper { +public: + explicit gfxCoreTextShaper(gfxMacFont *aFont); + + virtual ~gfxCoreTextShaper(); + + virtual bool ShapeText(DrawTarget *aDrawTarget, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + Script aScript, + bool aVertical, + gfxShapedText *aShapedText); + + // clean up static objects that may have been cached + static void Shutdown(); + +protected: + CTFontRef mCTFont; + + // attributes for shaping text with LTR or RTL directionality + CFDictionaryRef mAttributesDictLTR; + CFDictionaryRef mAttributesDictRTL; + + nsresult SetGlyphsFromRun(gfxShapedText *aShapedText, + uint32_t aOffset, + uint32_t aLength, + CTRunRef aCTRun, + int32_t aStringOffset); + + CTFontRef CreateCTFontWithFeatures(CGFloat aSize, + CTFontDescriptorRef aDescriptor); + + CFDictionaryRef CreateAttrDict(bool aRightToLeft); + CFDictionaryRef CreateAttrDictWithoutDirection(); + + static CTFontDescriptorRef + CreateFontFeaturesDescriptor(const std::pair<SInt16,SInt16> aFeatures[], + size_t aCount); + + static CTFontDescriptorRef GetDefaultFeaturesDescriptor(); + static CTFontDescriptorRef GetDisableLigaturesDescriptor(); + static CTFontDescriptorRef GetIndicFeaturesDescriptor(); + static CTFontDescriptorRef GetIndicDisableLigaturesDescriptor(); + + // cached font descriptor, created the first time it's needed + static CTFontDescriptorRef sDefaultFeaturesDescriptor; + + // cached descriptor for adding disable-ligatures setting to a font + static CTFontDescriptorRef sDisableLigaturesDescriptor; + + // feature descriptors for buggy Indic AAT font workaround + static CTFontDescriptorRef sIndicFeaturesDescriptor; + static CTFontDescriptorRef sIndicDisableLigaturesDescriptor; +}; + +#endif /* GFX_CORETEXTSHAPER_H */ diff --git a/gfx/thebes/gfxFontUtils.cpp b/gfx/thebes/gfxFontUtils.cpp index 0d4b309f6c..54ca03ff6c 100644 --- a/gfx/thebes/gfxFontUtils.cpp +++ b/gfx/thebes/gfxFontUtils.cpp @@ -379,14 +379,24 @@ gfxFontUtils::ReadCMAPTableFormat14(const uint8_t *aBuf, uint32_t aLength, return NS_OK; } -// For fonts with two format-4 tables, we allow the first one (Unicode platform) -// to be replaced by the Microsoft-platform subtable. +// For fonts with two format-4 tables, the first one (Unicode platform) is preferred on the Mac; +// on other platforms we allow the Microsoft-platform subtable to replace it. -#define acceptableFormat4(p,e,k) (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) || \ - ((p) == PLATFORM_ID_UNICODE)) +#if defined(XP_MACOSX) + #define acceptableFormat4(p,e,k) (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft && !(k)) || \ + ((p) == PLATFORM_ID_UNICODE)) -#define acceptableUCS4Encoding(p, e, k) \ + #define acceptableUCS4Encoding(p, e, k) \ + (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform) && (k) != 12 || \ + ((p) == PLATFORM_ID_UNICODE && \ + ((e) != EncodingIDUVSForUnicodePlatform))) +#else + #define acceptableFormat4(p,e,k) (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) || \ + ((p) == PLATFORM_ID_UNICODE)) + + #define acceptableUCS4Encoding(p, e, k) \ ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform) +#endif #define acceptablePlatform(p) ((p) == PLATFORM_ID_UNICODE || (p) == PLATFORM_ID_MICROSOFT) #define isSymbol(p,e) ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDSymbol) @@ -1220,8 +1230,13 @@ gfxFontUtils::GetFamilyNameFromTable(hb_blob_t *aNameTable, } enum { +#if defined(XP_MACOSX) + CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MAC_ENGLISH, + PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MAC +#else CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MICROSOFT_EN_US, PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MICROSOFT +#endif }; nsresult @@ -1262,6 +1277,22 @@ gfxFontUtils::ReadCanonicalName(const char *aNameData, uint32_t aDataLen, NS_ENSURE_SUCCESS(rv, rv); } +#if defined(XP_MACOSX) + // may be dealing with font that only has Microsoft name entries + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ID_MICROSOFT_EN_US, + PLATFORM_ID_MICROSOFT, names); + NS_ENSURE_SUCCESS(rv, rv); + + // getting really desperate now, take anything! + if (names.Length() == 0) { + rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL, + PLATFORM_ID_MICROSOFT, names); + NS_ENSURE_SUCCESS(rv, rv); + } + } +#endif + // return the first name (99.9% of the time names will // contain a single English name) if (names.Length()) { diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp new file mode 100644 index 0000000000..f512c689f7 --- /dev/null +++ b/gfx/thebes/gfxMacFont.cpp @@ -0,0 +1,475 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "gfxMacFont.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Sprintf.h" + +#include "gfxCoreTextShaper.h" +#include <algorithm> +#include "gfxPlatformMac.h" +#include "gfxContext.h" +#include "gfxFontUtils.h" +#include "gfxMacPlatformFontList.h" +#include "gfxFontConstants.h" +#include "gfxTextRun.h" + +#include "cairo-quartz.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, + bool aNeedsBold) + : gfxFont(aFontEntry, aFontStyle), + mCGFont(nullptr), + mCTFont(nullptr), + mFontFace(nullptr) +{ + mApplySyntheticBold = aNeedsBold; + + mCGFont = aFontEntry->GetFontRef(); + if (!mCGFont) { + mIsValid = false; + return; + } + + // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize + InitMetrics(); + if (!mIsValid) { + return; + } + + mFontFace = cairo_quartz_font_face_create_for_cgfont(mCGFont); + + cairo_status_t cairoerr = cairo_font_face_status(mFontFace); + if (cairoerr != CAIRO_STATUS_SUCCESS) { + mIsValid = false; +#ifdef DEBUG + char warnBuf[1024]; + SprintfLiteral(warnBuf, + "Failed to create Cairo font face: %s status: %d", + NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); + NS_WARNING(warnBuf); +#endif + return; + } + + cairo_matrix_t sizeMatrix, ctm; + cairo_matrix_init_identity(&ctm); + cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize); + + // synthetic oblique by skewing via the font matrix + bool needsOblique = mFontEntry != nullptr && + mFontEntry->IsUpright() && + mStyle.style != NS_FONT_STYLE_NORMAL && + mStyle.allowSyntheticStyle; + + if (needsOblique) { + cairo_matrix_t style; + cairo_matrix_init(&style, + 1, //xx + 0, //yx + -1 * OBLIQUE_SKEW_FACTOR, //xy + 1, //yy + 0, //x0 + 0); //y0 + cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style); + } + + cairo_font_options_t *fontOptions = cairo_font_options_create(); + + // turn off font anti-aliasing based on user pref setting + if (mAdjustedSize <= + (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) { + cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE); + mAntialiasOption = kAntialiasNone; + } else if (mStyle.useGrayscaleAntialiasing) { + cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_GRAY); + mAntialiasOption = kAntialiasGrayscale; + } + + mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, + fontOptions); + cairo_font_options_destroy(fontOptions); + + cairoerr = cairo_scaled_font_status(mScaledFont); + if (cairoerr != CAIRO_STATUS_SUCCESS) { + mIsValid = false; +#ifdef DEBUG + char warnBuf[1024]; + SprintfLiteral(warnBuf, "Failed to create scaled font: %s status: %d", + NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); + NS_WARNING(warnBuf); +#endif + } +} + +gfxMacFont::~gfxMacFont() +{ + if (mCTFont) { + ::CFRelease(mCTFont); + } + if (mScaledFont) { + cairo_scaled_font_destroy(mScaledFont); + } + if (mFontFace) { + cairo_font_face_destroy(mFontFace); + } +} + +bool +gfxMacFont::ShapeText(DrawTarget *aDrawTarget, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + Script aScript, + bool aVertical, + gfxShapedText *aShapedText) +{ + if (!mIsValid) { + NS_WARNING("invalid font! expect incorrect text rendering"); + return false; + } + + // Currently, we don't support vertical shaping via CoreText, + // so we ignore RequiresAATLayout if vertical is requested. + if (static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout() && + !aVertical) { + if (!mCoreTextShaper) { + mCoreTextShaper = MakeUnique<gfxCoreTextShaper>(this); + } + if (mCoreTextShaper->ShapeText(aDrawTarget, aText, aOffset, aLength, + aScript, aVertical, aShapedText)) { + PostShapingFixup(aDrawTarget, aText, aOffset, + aLength, aVertical, aShapedText); + return true; + } + } + + return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript, + aVertical, aShapedText); +} + +bool +gfxMacFont::SetupCairoFont(DrawTarget* aDrawTarget) +{ + if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) { + // Don't cairo_set_scaled_font as that would propagate the error to + // the cairo_t, precluding any further drawing. + return false; + } + cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), mScaledFont); + return true; +} + +gfxFont::RunMetrics +gfxMacFont::Measure(const gfxTextRun *aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget *aRefDrawTarget, + Spacing *aSpacing, + uint16_t aOrientation) +{ + gfxFont::RunMetrics metrics = + gfxFont::Measure(aTextRun, aStart, aEnd, + aBoundingBoxType, aRefDrawTarget, aSpacing, + aOrientation); + + // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add + // a pixel column each side of the bounding box in case of antialiasing "bleed" + if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS && + metrics.mBoundingBox.width > 0) { + metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit(); + metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2; + } + + return metrics; +} + +void +gfxMacFont::InitMetrics() +{ + mIsValid = false; + ::memset(&mMetrics, 0, sizeof(mMetrics)); + + uint32_t upem = 0; + + // try to get unitsPerEm from sfnt head table, to avoid calling CGFont + // if possible (bug 574368) and because CGFontGetUnitsPerEm does not + // return the true value for OpenType/CFF fonts (it normalizes to 1000, + // which then leads to metrics errors when we read the 'hmtx' table to + // get glyph advances for HarfBuzz, see bug 580863) + CFDataRef headData = + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h','e','a','d')); + if (headData) { + if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) { + const HeadTable *head = + reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData)); + upem = head->unitsPerEm; + } + ::CFRelease(headData); + } + if (!upem) { + upem = ::CGFontGetUnitsPerEm(mCGFont); + } + + if (upem < 16 || upem > 16384) { + // See http://www.microsoft.com/typography/otspec/head.htm +#ifdef DEBUG + char warnBuf[1024]; + SprintfLiteral(warnBuf, + "Bad font metrics for: %s (invalid unitsPerEm value)", + NS_ConvertUTF16toUTF8(mFontEntry->Name()).get()); + NS_WARNING(warnBuf); +#endif + return; + } + + mAdjustedSize = std::max(mStyle.size, 1.0); + mFUnitsConvFactor = mAdjustedSize / upem; + + // For CFF fonts, when scaling values read from CGFont* APIs, we need to + // use CG's idea of unitsPerEm, which may differ from the "true" value in + // the head table of the font (see bug 580863) + gfxFloat cgConvFactor; + if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); + } else { + cgConvFactor = mFUnitsConvFactor; + } + + // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to + // platform APIs. The InitMetrics...() functions will set mIsValid on success. + if (!InitMetricsFromSfntTables(mMetrics) && + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { + InitMetricsFromPlatform(); + } + if (!mIsValid) { + return; + } + + if (mMetrics.xHeight == 0.0) { + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; + } + + if (mMetrics.capHeight == 0.0) { + mMetrics.capHeight = ::CGFontGetCapHeight(mCGFont) * cgConvFactor; + } + + if (mStyle.sizeAdjust > 0.0 && mStyle.size > 0.0 && + mMetrics.xHeight > 0.0) { + // apply font-size-adjust, and recalculate metrics + gfxFloat aspect = mMetrics.xHeight / mStyle.size; + mAdjustedSize = mStyle.GetAdjustedSize(aspect); + mFUnitsConvFactor = mAdjustedSize / upem; + if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); + } else { + cgConvFactor = mFUnitsConvFactor; + } + mMetrics.xHeight = 0.0; + if (!InitMetricsFromSfntTables(mMetrics) && + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { + InitMetricsFromPlatform(); + } + if (!mIsValid) { + // this shouldn't happen, as we succeeded earlier before applying + // the size-adjust factor! But check anyway, for paranoia's sake. + return; + } + if (mMetrics.xHeight == 0.0) { + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; + } + } + + // Once we reach here, we've got basic metrics and set mIsValid = TRUE; + // there should be no further points of actual failure in InitMetrics(). + // (If one is introduced, be sure to reset mIsValid to FALSE!) + + mMetrics.emHeight = mAdjustedSize; + + // Measure/calculate additional metrics, independent of whether we used + // the tables directly or ATS metrics APIs + + CFDataRef cmap = + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c','m','a','p')); + + uint32_t glyphID; + if (mMetrics.aveCharWidth <= 0) { + mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID, + cgConvFactor); + if (glyphID == 0) { + // we didn't find 'x', so use maxAdvance rather than zero + mMetrics.aveCharWidth = mMetrics.maxAdvance; + } + } + if (IsSyntheticBold()) { + mMetrics.aveCharWidth += GetSyntheticBoldOffset(); + mMetrics.maxAdvance += GetSyntheticBoldOffset(); + } + + mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor); + if (glyphID == 0) { + // no space glyph?! + mMetrics.spaceWidth = mMetrics.aveCharWidth; + } + mSpaceGlyph = glyphID; + + mMetrics.zeroOrAveCharWidth = GetCharWidth(cmap, '0', &glyphID, + cgConvFactor); + if (glyphID == 0) { + mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth; + } + + if (cmap) { + ::CFRelease(cmap); + } + + CalculateDerivedMetrics(mMetrics); + + SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont); + +#if 0 + fprintf (stderr, "Font: %p (%s) size: %f\n", this, + NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size); +// fprintf (stderr, " fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height); + fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); + fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance); + fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading); + fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f capHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight, mMetrics.capHeight); + fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize); +#endif +} + +gfxFloat +gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar, + uint32_t *aGlyphID, gfxFloat aConvFactor) +{ + CGGlyph glyph = 0; + + if (aCmap) { + glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap), + ::CFDataGetLength(aCmap), + aUniChar); + } + + if (aGlyphID) { + *aGlyphID = glyph; + } + + if (glyph) { + int advance; + if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) { + return advance * aConvFactor; + } + } + + return 0; +} + +int32_t +gfxMacFont::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID) +{ + if (!mCTFont) { + mCTFont = ::CTFontCreateWithGraphicsFont(mCGFont, mAdjustedSize, + nullptr, nullptr); + if (!mCTFont) { // shouldn't happen, but let's be safe + NS_WARNING("failed to create CTFontRef to measure glyph width"); + return 0; + } + } + + CGSize advance; + ::CTFontGetAdvancesForGlyphs(mCTFont, kCTFontDefaultOrientation, &aGID, + &advance, 1); + return advance.width * 0x10000; +} + +// Try to initialize font metrics via platform APIs (CG/CT), +// and set mIsValid = TRUE on success. +// We ONLY call this for local (platform) fonts that are not sfnt format; +// for sfnts, including ALL downloadable fonts, we prefer to use +// InitMetricsFromSfntTables and avoid platform APIs. +void +gfxMacFont::InitMetricsFromPlatform() +{ + CTFontRef ctFont = ::CTFontCreateWithGraphicsFont(mCGFont, + mAdjustedSize, + nullptr, nullptr); + if (!ctFont) { + return; + } + + mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont); + mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont); + + mMetrics.externalLeading = ::CTFontGetLeading(ctFont); + + mMetrics.maxAscent = ::CTFontGetAscent(ctFont); + mMetrics.maxDescent = ::CTFontGetDescent(ctFont); + + // this is not strictly correct, but neither CTFont nor CGFont seems to + // provide maxAdvance, unless we were to iterate over all the glyphs + // (which isn't worth the cost here) + CGRect r = ::CTFontGetBoundingBox(ctFont); + mMetrics.maxAdvance = r.size.width; + + // aveCharWidth is also not provided, so leave it at zero + // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x'); + // this could lead to less-than-"perfect" text field sizing when width is + // specified as a number of characters, and the font in use is a non-sfnt + // legacy font, but that's a sufficiently obscure edge case that we can + // ignore the potential discrepancy. + mMetrics.aveCharWidth = 0; + + mMetrics.xHeight = ::CTFontGetXHeight(ctFont); + mMetrics.capHeight = ::CTFontGetCapHeight(ctFont); + + ::CFRelease(ctFont); + + mIsValid = true; +} + +already_AddRefed<ScaledFont> +gfxMacFont::GetScaledFont(DrawTarget *aTarget) +{ + if (!mAzureScaledFont) { + NativeFont nativeFont; + nativeFont.mType = NativeFontType::MAC_FONT_FACE; + nativeFont.mFont = GetCGFontRef(); + mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont); + } + + RefPtr<ScaledFont> scaledFont(mAzureScaledFont); + return scaledFont.forget(); +} + +already_AddRefed<mozilla::gfx::GlyphRenderingOptions> +gfxMacFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams) +{ + if (aRunParams) { + return mozilla::gfx::Factory::CreateCGGlyphRenderingOptions(aRunParams->fontSmoothingBGColor); + } + return nullptr; +} + +void +gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); + // mCGFont is shared with the font entry, so not counted here; + // and we don't have APIs to measure the cairo mFontFace object +} + +void +gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + aSizes->mFontInstances += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h new file mode 100644 index 0000000000..d12cc717ba --- /dev/null +++ b/gfx/thebes/gfxMacFont.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef GFX_MACFONT_H +#define GFX_MACFONT_H + +#include "mozilla/MemoryReporting.h" +#include "gfxFont.h" +#include "cairo.h" +#include <ApplicationServices/ApplicationServices.h> + +class MacOSFontEntry; + +class gfxMacFont : public gfxFont +{ +public: + gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, + bool aNeedsBold); + + virtual ~gfxMacFont(); + + CGFontRef GetCGFontRef() const { return mCGFont; } + + /* overrides for the pure virtual methods in gfxFont */ + virtual uint32_t GetSpaceGlyph() override { + return mSpaceGlyph; + } + + virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override; + + /* override Measure to add padding for antialiasing */ + virtual RunMetrics Measure(const gfxTextRun *aTextRun, + uint32_t aStart, uint32_t aEnd, + BoundingBoxType aBoundingBoxType, + DrawTarget *aDrawTargetForTightBoundingBox, + Spacing *aSpacing, + uint16_t aOrientation) override; + + // We need to provide hinted (non-linear) glyph widths if using a font + // with embedded color bitmaps (Apple Color Emoji), as Core Text renders + // the glyphs with non-linear scaling at small pixel sizes. + virtual bool ProvidesGlyphWidths() const override { + return mFontEntry->HasFontTable(TRUETYPE_TAG('s','b','i','x')); + } + + virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget, + uint16_t aGID) override; + + virtual already_AddRefed<mozilla::gfx::ScaledFont> + GetScaledFont(mozilla::gfx::DrawTarget *aTarget) override; + + virtual already_AddRefed<mozilla::gfx::GlyphRenderingOptions> + GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) override; + + virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontCacheSizes* aSizes) const override; + + virtual FontType GetType() const override { return FONT_TYPE_MAC; } + +protected: + virtual const Metrics& GetHorizontalMetrics() override { + return mMetrics; + } + + // override to prefer CoreText shaping with fonts that depend on AAT + virtual bool ShapeText(DrawTarget *aDrawTarget, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + Script aScript, + bool aVertical, + gfxShapedText *aShapedText) override; + + void InitMetrics(); + void InitMetricsFromPlatform(); + + // Get width and glyph ID for a character; uses aConvFactor + // to convert font units as returned by CG to actual dimensions + gfxFloat GetCharWidth(CFDataRef aCmap, char16_t aUniChar, + uint32_t *aGlyphID, gfxFloat aConvFactor); + + // a weak reference to the CoreGraphics font: this is owned by the + // MacOSFontEntry, it is not retained or released by gfxMacFont + CGFontRef mCGFont; + + // a Core Text font reference, created only if we're using CT to measure + // glyph widths; otherwise null. + CTFontRef mCTFont; + + cairo_font_face_t *mFontFace; + + mozilla::UniquePtr<gfxFontShaper> mCoreTextShaper; + + Metrics mMetrics; + uint32_t mSpaceGlyph; +}; + +#endif /* GFX_MACFONT_H */ diff --git a/gfx/thebes/gfxMacPlatformFontList.h b/gfx/thebes/gfxMacPlatformFontList.h new file mode 100644 index 0000000000..0ab062dca4 --- /dev/null +++ b/gfx/thebes/gfxMacPlatformFontList.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef gfxMacPlatformFontList_H_ +#define gfxMacPlatformFontList_H_ + +#include <CoreFoundation/CoreFoundation.h> + +#include "mozilla/MemoryReporting.h" +#include "nsDataHashtable.h" +#include "nsRefPtrHashtable.h" + +#include "gfxPlatformFontList.h" +#include "gfxPlatform.h" +#include "gfxPlatformMac.h" + +#include "nsUnicharUtils.h" +#include "nsTArray.h" +#include "mozilla/LookAndFeel.h" + +class gfxMacPlatformFontList; + +// a single member of a font family (i.e. a single face, such as Times Italic) +class MacOSFontEntry : public gfxFontEntry +{ +public: + friend class gfxMacPlatformFontList; + + MacOSFontEntry(const nsAString& aPostscriptName, int32_t aWeight, + bool aIsStandardFace = false, + double aSizeHint = 0.0); + + // for use with data fonts + MacOSFontEntry(const nsAString& aPostscriptName, CGFontRef aFontRef, + uint16_t aWeight, uint16_t aStretch, uint8_t aStyle, + bool aIsDataUserFont, bool aIsLocal); + + virtual ~MacOSFontEntry() { + ::CGFontRelease(mFontRef); + } + + virtual CGFontRef GetFontRef(); + + // override gfxFontEntry table access function to bypass table cache, + // use CGFontRef API to get direct access to system font data + virtual hb_blob_t *GetFontTable(uint32_t aTag) override; + + virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const override; + + nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr) override; + + bool RequiresAATLayout() const { return mRequiresAAT; } + + bool IsCFF(); + +protected: + virtual gfxFont* CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) override; + + virtual bool HasFontTable(uint32_t aTableTag) override; + + static void DestroyBlobFunc(void* aUserData); + + CGFontRef mFontRef; // owning reference to the CGFont, released on destruction + + double mSizeHint; + + bool mFontRefInitialized; + bool mRequiresAAT; + bool mIsCFF; + bool mIsCFFInitialized; + nsTHashtable<nsUint32HashKey> mAvailableTables; +}; + +class gfxMacPlatformFontList : public gfxPlatformFontList { +public: + static gfxMacPlatformFontList* PlatformFontList() { + return static_cast<gfxMacPlatformFontList*>(sPlatformFontList); + } + + static int32_t AppleWeightToCSSWeight(int32_t aAppleWeight); + + bool GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) override; + + gfxFontEntry* LookupLocalFont(const nsAString& aFontName, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle) override; + + gfxFontEntry* MakePlatformFont(const nsAString& aFontName, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle, + const uint8_t* aFontData, + uint32_t aLength) override; + + bool FindAndAddFamilies(const nsAString& aFamily, + nsTArray<gfxFontFamily*>* aOutput, + gfxFontStyle* aStyle = nullptr, + gfxFloat aDevToCssSize = 1.0) override; + + // lookup the system font for a particular system font type and set + // the name and style characteristics + void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID, + nsAString& aSystemFontName, + gfxFontStyle &aFontStyle, + float aDevPixPerCSSPixel); + +protected: + virtual gfxFontFamily* + GetDefaultFontForPlatform(const gfxFontStyle* aStyle) override; + +private: + friend class gfxPlatformMac; + + gfxMacPlatformFontList(); + virtual ~gfxMacPlatformFontList(); + + // initialize font lists + virtual nsresult InitFontListForPlatform() override; + + // special case font faces treated as font families (set via prefs) + void InitSingleFaceList(); + + // initialize system fonts + void InitSystemFontNames(); + + // helper function to lookup in both hidden system fonts and normal fonts + gfxFontFamily* FindSystemFontFamily(const nsAString& aFamily); + + static void RegisteredFontsChangedNotificationCallback(CFNotificationCenterRef center, + void *observer, + CFStringRef name, + const void *object, + CFDictionaryRef userInfo); + + // attempt to use platform-specific fallback for the given character + // return null if no usable result found + gfxFontEntry* + PlatformGlobalFontFallback(const uint32_t aCh, + Script aRunScript, + const gfxFontStyle* aMatchStyle, + gfxFontFamily** aMatchedFamily) override; + + bool UsesSystemFallback() override { return true; } + + already_AddRefed<FontInfoData> CreateFontInfoData() override; + + // Add the specified family to mSystemFontFamilies or mFontFamilies. + // Ideally we'd use NSString* instead of CFStringRef here, but this header + // file is included in .cpp files, so we can't use objective C classes here. + // But CFStringRef and NSString* are the same thing anyway (they're + // toll-free bridged). + void AddFamily(CFStringRef aFamily); + +#ifdef MOZ_BUNDLED_FONTS + void ActivateBundledFonts(); +#endif + + enum { + kATSGenerationInitial = -1 + }; + + // default font for use with system-wide font fallback + CTFontRef mDefaultFont; + + // hidden system fonts used within UI elements, there may be a whole set + // for different locales (e.g. .Helvetica Neue UI, .SF NS Text) + FontFamilyTable mSystemFontFamilies; + + // font families that -apple-system maps to + // Pre-10.11 this was always a single font family, such as Lucida Grande + // or Helvetica Neue. For OSX 10.11, Apple uses pair of families + // for the UI, one for text sizes and another for display sizes + bool mUseSizeSensitiveSystemFont; + nsString mSystemTextFontFamilyName; + nsString mSystemDisplayFontFamilyName; // only used on OSX 10.11 +}; + +#endif /* gfxMacPlatformFontList_H_ */ diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm new file mode 100644 index 0000000000..4536ab527b --- /dev/null +++ b/gfx/thebes/gfxMacPlatformFontList.mm @@ -0,0 +1,1444 @@ +/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: BSD + * + * Copyright (C) 2006-2009 Mozilla Corporation. All rights reserved. + * + * Contributor(s): + * Vladimir Vukicevic <vladimir@pobox.com> + * Masayuki Nakano <masayuki@d-toybox.com> + * John Daggett <jdaggett@mozilla.com> + * Jonathan Kew <jfkthame@gmail.com> + * + * Copyright (C) 2006 Apple Computer, 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + * + * ***** END LICENSE BLOCK ***** */ + +#include "mozilla/Logging.h" + +#include <algorithm> + +#import <AppKit/AppKit.h> + +#include "gfxPlatformMac.h" +#include "gfxMacPlatformFontList.h" +#include "gfxMacFont.h" +#include "gfxUserFontSet.h" +#include "harfbuzz/hb.h" + +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" + +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsISimpleEnumerator.h" +#include "nsCharTraits.h" +#include "nsCocoaFeatures.h" +#include "nsCocoaUtils.h" +#include "gfxFontConstants.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/Sprintf.h" +#include "mozilla/gfx/2D.h" + +#include <unistd.h> +#include <time.h> +#include <dlfcn.h> + +using namespace mozilla; + +// indexes into the NSArray objects that the Cocoa font manager returns +// as the available members of a family +#define INDEX_FONT_POSTSCRIPT_NAME 0 +#define INDEX_FONT_FACE_NAME 1 +#define INDEX_FONT_WEIGHT 2 +#define INDEX_FONT_TRAITS 3 + +static const int kAppleMaxWeight = 14; +static const int kAppleExtraLightWeight = 3; +static const int kAppleUltraLightWeight = 2; + +static const int gAppleWeightToCSSWeight[] = { + 0, + 1, // 1. + 1, // 2. W1, ultralight + 2, // 3. W2, extralight + 3, // 4. W3, light + 4, // 5. W4, semilight + 5, // 6. W5, medium + 6, // 7. + 6, // 8. W6, semibold + 7, // 9. W7, bold + 8, // 10. W8, extrabold + 8, // 11. + 9, // 12. W9, ultrabold + 9, // 13 + 9 // 14 +}; + +// cache Cocoa's "shared font manager" for performance +static NSFontManager *sFontManager; + +static void GetStringForNSString(const NSString *aSrc, nsAString& aDist) +{ + aDist.SetLength([aSrc length]); + [aSrc getCharacters:reinterpret_cast<unichar*>(aDist.BeginWriting())]; +} + +static NSString* GetNSStringForString(const nsAString& aSrc) +{ + return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSrc.BeginReading()) + length:aSrc.Length()]; +} + +#define LOG_FONTLIST(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \ + mozilla::LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() MOZ_LOG_TEST( \ + gfxPlatform::GetLog(eGfxLog_fontlist), \ + mozilla::LogLevel::Debug) +#define LOG_CMAPDATA_ENABLED() MOZ_LOG_TEST( \ + gfxPlatform::GetLog(eGfxLog_cmapdata), \ + mozilla::LogLevel::Debug) + +#pragma mark- + +// Complex scripts will not render correctly unless appropriate AAT or OT +// layout tables are present. +// For OpenType, we also check that the GSUB table supports the relevant +// script tag, to avoid using things like Arial Unicode MS for Lao (it has +// the characters, but lacks OpenType support). + +// TODO: consider whether we should move this to gfxFontEntry and do similar +// cmap-masking on other platforms to avoid using fonts that won't shape +// properly. + +nsresult +MacOSFontEntry::ReadCMAP(FontInfoData *aFontInfoData) +{ + // attempt this once, if errors occur leave a blank cmap + if (mCharacterMap) { + return NS_OK; + } + + RefPtr<gfxCharacterMap> charmap; + nsresult rv; + bool symbolFont = false; // currently ignored + + if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData, + mUVSOffset, + symbolFont))) { + rv = NS_OK; + } else { + uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p'); + charmap = new gfxCharacterMap(); + AutoTable cmapTable(this, kCMAP); + + if (cmapTable) { + bool unicodeFont = false; // currently ignored + uint32_t cmapLen; + const uint8_t* cmapData = + reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable, + &cmapLen)); + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, + *charmap, mUVSOffset, + unicodeFont, symbolFont); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + + if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) { + // We assume a Graphite font knows what it's doing, + // and provides whatever shaping is needed for the + // characters it supports, so only check/clear the + // complex-script ranges for non-Graphite fonts + + // for layout support, check for the presence of mort/morx and/or + // opentype layout tables + bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m','o','r','x')) || + HasFontTable(TRUETYPE_TAG('m','o','r','t')); + bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B')); + bool hasGPOS = HasFontTable(TRUETYPE_TAG('G','P','O','S')); + if (hasAATLayout && !(hasGSUB || hasGPOS)) { + mRequiresAAT = true; // prefer CoreText if font has no OTL tables + } + + for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges; + sr->rangeStart; sr++) { + // check to see if the cmap includes complex script codepoints + if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) { + if (hasAATLayout) { + // prefer CoreText for Apple's complex-script fonts, + // even if they also have some OpenType tables + // (e.g. Geeza Pro Bold on 10.6; see bug 614903) + mRequiresAAT = true; + // and don't mask off complex-script ranges, we assume + // the AAT tables will provide the necessary shaping + continue; + } + + // We check for GSUB here, as GPOS alone would not be ok. + if (hasGSUB && SupportsScriptInGSUB(sr->tags)) { + continue; + } + + charmap->ClearRange(sr->rangeStart, sr->rangeEnd); + } + } + + // Bug 1360309, 1393624: several of Apple's Chinese fonts have spurious + // blank glyphs for obscure Tibetan and Arabic-script codepoints. + // Blacklist these so that font fallback will not use them. + if (mRequiresAAT && (FamilyName().EqualsLiteral("Songti SC") || + FamilyName().EqualsLiteral("Songti TC") || + FamilyName().EqualsLiteral("STSong") || + // Bug 1390980: on 10.11, the Kaiti fonts are also affected. + FamilyName().EqualsLiteral("Kaiti SC") || + FamilyName().EqualsLiteral("Kaiti TC") || + FamilyName().EqualsLiteral("STKaiti"))) { + charmap->ClearRange(0x0f6b, 0x0f70); + charmap->ClearRange(0x0f8c, 0x0f8f); + charmap->clear(0x0f98); + charmap->clear(0x0fbd); + charmap->ClearRange(0x0fcd, 0x0fff); + charmap->clear(0x0620); + charmap->clear(0x065f); + charmap->ClearRange(0x06ee, 0x06ef); + charmap->clear(0x06ff); + } + } + + mHasCmapTable = NS_SUCCEEDED(rv); + if (mHasCmapTable) { + gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); + mCharacterMap = pfl->FindCharMap(charmap); + } else { + // if error occurred, initialize to null cmap + mCharacterMap = new gfxCharacterMap(); + } + + LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n", + NS_ConvertUTF16toUTF8(mName).get(), + charmap->SizeOfIncludingThis(moz_malloc_size_of), + charmap->mHash, mCharacterMap == charmap ? " new" : "")); + if (LOG_CMAPDATA_ENABLED()) { + char prefix[256]; + SprintfLiteral(prefix, "(cmapdata) name: %.220s", + NS_ConvertUTF16toUTF8(mName).get()); + charmap->Dump(prefix, eGfxLog_cmapdata); + } + + return rv; +} + +gfxFont* +MacOSFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) +{ + return new gfxMacFont(this, aFontStyle, aNeedsBold); +} + +bool +MacOSFontEntry::IsCFF() +{ + if (!mIsCFFInitialized) { + mIsCFFInitialized = true; + mIsCFF = HasFontTable(TRUETYPE_TAG('C','F','F',' ')); + } + + return mIsCFF; +} + +MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName, + int32_t aWeight, + bool aIsStandardFace, + double aSizeHint) + : gfxFontEntry(aPostscriptName, aIsStandardFace), + mFontRef(NULL), + mSizeHint(aSizeHint), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false) +{ + mWeight = aWeight; +} + +MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName, + CGFontRef aFontRef, + uint16_t aWeight, uint16_t aStretch, + uint8_t aStyle, + bool aIsDataUserFont, + bool aIsLocalUserFont) + : gfxFontEntry(aPostscriptName, false), + mFontRef(NULL), + mSizeHint(0.0), + mFontRefInitialized(false), + mRequiresAAT(false), + mIsCFF(false), + mIsCFFInitialized(false) +{ + mFontRef = aFontRef; + mFontRefInitialized = true; + ::CFRetain(mFontRef); + + mWeight = aWeight; + mStretch = aStretch; + mFixedPitch = false; // xxx - do we need this for downloaded fonts? + mStyle = aStyle; + + NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont), + "userfont is either a data font or a local font"); + mIsDataUserFont = aIsDataUserFont; + mIsLocalUserFont = aIsLocalUserFont; +} + +CGFontRef +MacOSFontEntry::GetFontRef() +{ + if (!mFontRefInitialized) { + mFontRefInitialized = true; + NSString *psname = GetNSStringForString(mName); + mFontRef = ::CGFontCreateWithFontName(CFStringRef(psname)); + if (!mFontRef) { + // This happens on macOS 10.12 for font entry names that start with + // .AppleSystemUIFont. For those fonts, we need to go through NSFont + // to get the correct CGFontRef. + // Both the Text and the Display variant of the display font use + // .AppleSystemUIFontSomethingSomething as their member names. + // That's why we're carrying along mSizeHint to this place so that + // we get the variant that we want for this family. + NSFont* font = [NSFont fontWithName:psname size:mSizeHint]; + if (font) { + mFontRef = CTFontCopyGraphicsFont((CTFontRef)font, nullptr); + } + } + } + return mFontRef; +} + +// For a logging build, we wrap the CFDataRef in a FontTableRec so that we can +// use the MOZ_COUNT_[CD]TOR macros in it. A release build without logging +// does not get this overhead. +class FontTableRec { +public: + explicit FontTableRec(CFDataRef aDataRef) + : mDataRef(aDataRef) + { + MOZ_COUNT_CTOR(FontTableRec); + } + + ~FontTableRec() { + MOZ_COUNT_DTOR(FontTableRec); + ::CFRelease(mDataRef); + } + +private: + CFDataRef mDataRef; +}; + +/*static*/ void +MacOSFontEntry::DestroyBlobFunc(void* aUserData) +{ +#ifdef NS_BUILD_REFCNT_LOGGING + FontTableRec *ftr = static_cast<FontTableRec*>(aUserData); + delete ftr; +#else + ::CFRelease((CFDataRef)aUserData); +#endif +} + +hb_blob_t * +MacOSFontEntry::GetFontTable(uint32_t aTag) +{ + CGFontRef fontRef = GetFontRef(); + if (!fontRef) { + return nullptr; + } + + CFDataRef dataRef = ::CGFontCopyTableForTag(fontRef, aTag); + if (dataRef) { + return hb_blob_create((const char*)::CFDataGetBytePtr(dataRef), + ::CFDataGetLength(dataRef), + HB_MEMORY_MODE_READONLY, +#ifdef NS_BUILD_REFCNT_LOGGING + new FontTableRec(dataRef), +#else + (void*)dataRef, +#endif + DestroyBlobFunc); + } + + return nullptr; +} + +bool +MacOSFontEntry::HasFontTable(uint32_t aTableTag) +{ + if (mAvailableTables.Count() == 0) { + nsAutoreleasePool localPool; + + CGFontRef fontRef = GetFontRef(); + if (!fontRef) { + return false; + } + CFArrayRef tags = ::CGFontCopyTableTags(fontRef); + if (!tags) { + return false; + } + int numTags = (int) ::CFArrayGetCount(tags); + for (int t = 0; t < numTags; t++) { + uint32_t tag = (uint32_t)(uintptr_t)::CFArrayGetValueAtIndex(tags, t); + mAvailableTables.PutEntry(tag); + } + ::CFRelease(tags); + } + + return mAvailableTables.GetEntry(aTableTag); +} + +void +MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); +} + +/* gfxMacFontFamily */ +#pragma mark- + +class gfxMacFontFamily : public gfxFontFamily +{ +public: + explicit gfxMacFontFamily(nsAString& aName, double aSizeHint) : + gfxFontFamily(aName), + mSizeHint(aSizeHint) + {} + + virtual ~gfxMacFontFamily() {} + + virtual void LocalizedName(nsAString& aLocalizedName); + + virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr); + +protected: + double mSizeHint; +}; + +void +gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName) +{ + nsAutoreleasePool localPool; + + if (!HasOtherFamilyNames()) { + aLocalizedName = mName; + return; + } + + NSString *family = GetNSStringForString(mName); + NSString *localized = [sFontManager + localizedNameForFamily:family + face:nil]; + + if (localized) { + GetStringForNSString(localized, aLocalizedName); + return; + } + + // failed to get localized name, just use the canonical one + aLocalizedName = mName; +} + +// Return the CSS weight value to use for the given face, overriding what +// AppKit gives us (used to adjust families with bad weight values, see +// bug 931426). +// A return value of 0 indicates no override - use the existing weight. +static inline int +GetWeightOverride(const nsAString& aPSName) +{ + nsAutoCString prefName("font.weight-override."); + // The PostScript name is required to be ASCII; if it's not, the font is + // broken anyway, so we really don't care that this is lossy. + LossyAppendUTF16toASCII(aPSName, prefName); + return Preferences::GetInt(prefName.get(), 0); +} + +void +gfxMacFontFamily::FindStyleVariations(FontInfoData *aFontInfoData) +{ + if (mHasStyles) + return; + + nsAutoreleasePool localPool; + + NSString *family = GetNSStringForString(mName); + + // create a font entry for each face + NSArray *fontfaces = [sFontManager + availableMembersOfFontFamily:family]; // returns an array of [psname, style name, weight, traits] elements, goofy api + int faceCount = [fontfaces count]; + int faceIndex; + + for (faceIndex = 0; faceIndex < faceCount; faceIndex++) { + NSArray *face = [fontfaces objectAtIndex:faceIndex]; + NSString *psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME]; + int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue]; + uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue]; + NSString *facename = [face objectAtIndex:INDEX_FONT_FACE_NAME]; + bool isStandardFace = false; + + if (appKitWeight == kAppleExtraLightWeight) { + // if the facename contains UltraLight, set the weight to the ultralight weight value + NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch]; + if (range.location != NSNotFound) { + appKitWeight = kAppleUltraLightWeight; + } + } + + // make a nsString + nsAutoString postscriptFontName; + GetStringForNSString(psname, postscriptFontName); + + int32_t cssWeight = GetWeightOverride(postscriptFontName); + if (cssWeight) { + // scale down and clamp, to get a value from 1..9 + cssWeight = ((cssWeight + 50) / 100); + cssWeight = std::max(1, std::min(cssWeight, 9)); + } else { + cssWeight = + gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight); + } + cssWeight *= 100; // scale up to CSS values + + if ([facename isEqualToString:@"Regular"] || + [facename isEqualToString:@"Bold"] || + [facename isEqualToString:@"Italic"] || + [facename isEqualToString:@"Oblique"] || + [facename isEqualToString:@"Bold Italic"] || + [facename isEqualToString:@"Bold Oblique"]) + { + isStandardFace = true; + } + + // create a font entry + MacOSFontEntry *fontEntry = + new MacOSFontEntry(postscriptFontName, cssWeight, isStandardFace, mSizeHint); + if (!fontEntry) { + break; + } + + // set additional properties based on the traits reported by Cocoa + if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) { + fontEntry->mStretch = NS_FONT_STRETCH_CONDENSED; + } else if (macTraits & NSExpandedFontMask) { + fontEntry->mStretch = NS_FONT_STRETCH_EXPANDED; + } + // Cocoa fails to set the Italic traits bit for HelveticaLightItalic, + // at least (see bug 611855), so check for style name endings as well + if ((macTraits & NSItalicFontMask) || + [facename hasSuffix:@"Italic"] || + [facename hasSuffix:@"Oblique"]) + { + fontEntry->mStyle = NS_FONT_STYLE_ITALIC; + } + if (macTraits & NSFixedPitchFontMask) { + fontEntry->mFixedPitch = true; + } + + if (LOG_FONTLIST_ENABLED()) { + LOG_FONTLIST(("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %d stretch: %d" + " (apple-weight: %d macTraits: %8.8x)", + NS_ConvertUTF16toUTF8(fontEntry->Name()).get(), + NS_ConvertUTF16toUTF8(Name()).get(), + fontEntry->IsItalic() ? "italic" : "normal", + cssWeight, fontEntry->Stretch(), + appKitWeight, macTraits)); + } + + // insert into font entry array of family + AddFontEntry(fontEntry); + } + + SortAvailableFonts(); + SetHasStyles(true); + + if (mIsBadUnderlineFamily) { + SetBadUnderlineFonts(); + } +} + +/* gfxSingleFaceMacFontFamily */ +#pragma mark- + +class gfxSingleFaceMacFontFamily : public gfxFontFamily +{ +public: + explicit gfxSingleFaceMacFontFamily(nsAString& aName) : + gfxFontFamily(aName) + { + mFaceNamesInitialized = true; // omit from face name lists + } + + virtual ~gfxSingleFaceMacFontFamily() {} + + virtual void LocalizedName(nsAString& aLocalizedName); + + virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList); +}; + +void +gfxSingleFaceMacFontFamily::LocalizedName(nsAString& aLocalizedName) +{ + nsAutoreleasePool localPool; + + if (!HasOtherFamilyNames()) { + aLocalizedName = mName; + return; + } + + gfxFontEntry *fe = mAvailableFonts[0]; + NSFont *font = [NSFont fontWithName:GetNSStringForString(fe->Name()) + size:0.0]; + if (font) { + NSString *localized = [font displayName]; + if (localized) { + GetStringForNSString(localized, aLocalizedName); + return; + } + } + + // failed to get localized name, just use the canonical one + aLocalizedName = mName; +} + +void +gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList) +{ + if (mOtherFamilyNamesInitialized) { + return; + } + + gfxFontEntry *fe = mAvailableFonts[0]; + if (!fe) { + return; + } + + const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); + + gfxFontEntry::AutoTable nameTable(fe, kNAME); + if (!nameTable) { + return; + } + + mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, + nameTable, + true); + + mOtherFamilyNamesInitialized = true; +} + +/* gfxMacPlatformFontList */ +#pragma mark- + +gfxMacPlatformFontList::gfxMacPlatformFontList() : + gfxPlatformFontList(false), + mDefaultFont(nullptr), + mUseSizeSensitiveSystemFont(false) +{ +#ifdef MOZ_BUNDLED_FONTS + ActivateBundledFonts(); +#endif + + ::CFNotificationCenterAddObserver(::CFNotificationCenterGetLocalCenter(), + this, + RegisteredFontsChangedNotificationCallback, + kCTFontManagerRegisteredFontsChangedNotification, + 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + + // cache this in a static variable so that MacOSFontFamily objects + // don't have to repeatedly look it up + sFontManager = [NSFontManager sharedFontManager]; +} + +gfxMacPlatformFontList::~gfxMacPlatformFontList() +{ + if (mDefaultFont) { + ::CFRelease(mDefaultFont); + } +} + +void +gfxMacPlatformFontList::AddFamily(CFStringRef aFamily) +{ + NSString* family = (NSString*)aFamily; + + // CTFontManager includes weird internal family names and + // LastResort, skip over those + if (!family || [family caseInsensitiveCompare:@"LastResort"] == NSOrderedSame) { + return; + } + + bool hiddenSystemFont = [family hasPrefix:@"."]; + + FontFamilyTable& table = + hiddenSystemFont ? mSystemFontFamilies : mFontFamilies; + + nsAutoString familyName; + nsCocoaUtils::GetStringForNSString(family, familyName); + + double sizeHint = 0.0; + if (hiddenSystemFont && mUseSizeSensitiveSystemFont && + mSystemDisplayFontFamilyName.Equals(familyName)) { + sizeHint = 128.0; + } + + nsAutoString key; + ToLowerCase(familyName, key); + + RefPtr<gfxFontFamily> familyEntry = new gfxMacFontFamily(familyName, sizeHint); + table.Put(key, familyEntry); + + // check the bad underline blacklist + if (mBadUnderlineFamilyNames.Contains(key)) { + familyEntry->SetBadUnderlineFamily(); + } +} + +nsresult +gfxMacPlatformFontList::InitFontListForPlatform() +{ + nsAutoreleasePool localPool; + + // reset system font list + mSystemFontFamilies.Clear(); + + // iterate over available families + + InitSystemFontNames(); + + CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames(); + + for (NSString* familyName in (NSArray*)familyNames) { + AddFamily((CFStringRef)familyName); + } + + CFRelease(familyNames); + + InitSingleFaceList(); + + // to avoid full search of font name tables, seed the other names table with localized names from + // some of the prefs fonts which are accessed via their localized names. changes in the pref fonts will only cause + // a font lookup miss earlier. this is a simple optimization, it's not required for correctness + PreloadNamesList(); + + // start the delayed cmap loader + GetPrefsAndStartLoader(); + + return NS_OK; +} + +void +gfxMacPlatformFontList::InitSingleFaceList() +{ + AutoTArray<nsString, 10> singleFaceFonts; + gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts); + + for (const auto& singleFaceFamily : singleFaceFonts) { + LOG_FONTLIST(("(fontlist-singleface) face name: %s\n", + NS_ConvertUTF16toUTF8(singleFaceFamily).get())); + // Each entry in the "single face families" list is expected to be a + // colon-separated pair of FaceName:Family, + // where FaceName is the individual face name (psname) of a font + // that should be exposed as a separate family name, + // and Family is the standard family to which that face belongs. + // The only such face listed by default is + // Osaka-Mono:Osaka + nsAutoString familyName(singleFaceFamily); + auto colon = familyName.FindChar(':'); + if (colon == kNotFound) { + continue; + } + + // Look for the parent family in the main font family list, + // and ensure we have loaded its list of available faces. + nsAutoString key(Substring(familyName, colon + 1)); + ToLowerCase(key); + gfxFontFamily* family = mFontFamilies.GetWeak(key); + if (!family) { + continue; + } + family->FindStyleVariations(); + + // Truncate the entry from prefs at the colon, so now it is just the + // desired single-face-family name. + familyName.Truncate(colon); + + // Look through the family's faces to see if this one is present. + const gfxFontEntry* fe = nullptr; + for (const auto& face : family->GetFontList()) { + if (face->Name().Equals(familyName)) { + fe = face; + break; + } + } + if (!fe) { + continue; + } + + // We found the correct face, so create the single-face family record. + GenerateFontListKey(familyName, key); + LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n", + NS_ConvertUTF16toUTF8(familyName).get(), + NS_ConvertUTF16toUTF8(key).get())); + + // add only if doesn't exist already + if (!mFontFamilies.GetWeak(key)) { + RefPtr<gfxFontFamily> familyEntry = + new gfxSingleFaceMacFontFamily(familyName); + // We need a separate font entry, because its family name will + // differ from the one we found in the main list. + MacOSFontEntry* fontEntry = + new MacOSFontEntry(fe->Name(), fe->mWeight, true, + static_cast<const MacOSFontEntry*>(fe)-> + mSizeHint); + familyEntry->AddFontEntry(fontEntry); + familyEntry->SetHasStyles(true); + mFontFamilies.Put(key, familyEntry); + LOG_FONTLIST(("(fontlist-singleface) added new family\n", + NS_ConvertUTF16toUTF8(familyName).get(), + NS_ConvertUTF16toUTF8(key).get())); + } + } +} + +// System fonts under OSX may contain weird "meta" names but if we create +// a new font using just the Postscript name, the NSFont api returns an object +// with the actual real family name. For example, under OSX 10.11: +// +// [[NSFont menuFontOfSize:8.0] familyName] ==> .AppleSystemUIFont +// [[NSFont fontWithName:[[[NSFont menuFontOfSize:8.0] fontDescriptor] postscriptName] +// size:8.0] familyName] ==> .SF NS Text + +static NSString* GetRealFamilyName(NSFont* aFont) +{ + NSFont* f = [NSFont fontWithName: [[aFont fontDescriptor] postscriptName] + size: 0.0]; + return [f familyName]; +} + +// System fonts under OSX 10.11 use a combination of two families, one +// for text sizes and another for larger, display sizes. Each has a +// different number of weights. There aren't efficient API's for looking +// this information up, so hard code the logic here but confirm via +// debug assertions that the logic is correct. + +const CGFloat kTextDisplayCrossover = 20.0; // use text family below this size + +void +gfxMacPlatformFontList::InitSystemFontNames() +{ + // system font under 10.11 are two distinct families for text/display sizes + if (nsCocoaFeatures::OnElCapitanOrLater()) { + mUseSizeSensitiveSystemFont = true; + } + + // text font family + NSFont* sys = [NSFont systemFontOfSize: 0.0]; + NSString* textFamilyName = GetRealFamilyName(sys); + nsAutoString familyName; + nsCocoaUtils::GetStringForNSString(textFamilyName, familyName); + mSystemTextFontFamilyName = familyName; + + // display font family, if on OSX 10.11 + if (mUseSizeSensitiveSystemFont) { + NSFont* displaySys = [NSFont systemFontOfSize: 128.0]; + NSString* displayFamilyName = GetRealFamilyName(displaySys); + nsCocoaUtils::GetStringForNSString(displayFamilyName, familyName); + mSystemDisplayFontFamilyName = familyName; + +#if DEBUG + // confirm that the optical size switch is at 20.0 + NS_ASSERTION([textFamilyName compare:displayFamilyName] != NSOrderedSame, + "system text/display fonts are the same!"); + NSString* fam19 = GetRealFamilyName([NSFont systemFontOfSize: + (kTextDisplayCrossover - 1.0)]); + NSString* fam20 = GetRealFamilyName([NSFont systemFontOfSize: + kTextDisplayCrossover]); + NS_ASSERTION(fam19 && fam20 && [fam19 compare:fam20] != NSOrderedSame, + "system text/display font size switch point is not as expected!"); +#endif + } + +#ifdef DEBUG + // different system font API's always map to the same family under OSX, so + // just assume that and emit a warning if that ever changes + NSString *sysFamily = GetRealFamilyName([NSFont systemFontOfSize:0.0]); + if ([sysFamily compare:GetRealFamilyName([NSFont boldSystemFontOfSize:0.0])] != NSOrderedSame || + [sysFamily compare:GetRealFamilyName([NSFont controlContentFontOfSize:0.0])] != NSOrderedSame || + [sysFamily compare:GetRealFamilyName([NSFont menuBarFontOfSize:0.0])] != NSOrderedSame || + [sysFamily compare:GetRealFamilyName([NSFont toolTipsFontOfSize:0.0])] != NSOrderedSame) { + NS_WARNING("system font types map to different font families" + " -- please log a bug!!"); + } +#endif +} + +gfxFontFamily* +gfxMacPlatformFontList::FindSystemFontFamily(const nsAString& aFamily) +{ + nsAutoString key; + GenerateFontListKey(aFamily, key); + + gfxFontFamily* familyEntry; + + // lookup in hidden system family name list + if ((familyEntry = mSystemFontFamilies.GetWeak(key))) { + return CheckFamily(familyEntry); + } + + // lookup in user-exposed family name list + if ((familyEntry = mFontFamilies.GetWeak(key))) { + return CheckFamily(familyEntry); + } + + return nullptr; +} + +bool +gfxMacPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) +{ + gfxFontFamily *family = FindFamily(aFontName); + if (family) { + family->LocalizedName(aFamilyName); + return true; + } + + return false; +} + +void +gfxMacPlatformFontList::RegisteredFontsChangedNotificationCallback(CFNotificationCenterRef center, + void *observer, + CFStringRef name, + const void *object, + CFDictionaryRef userInfo) +{ + if (!::CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) { + return; + } + + gfxMacPlatformFontList* fl = static_cast<gfxMacPlatformFontList*>(observer); + + // xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch + fl->UpdateFontList(); + + // modify a preference that will trigger reflow everywhere + fl->ForceGlobalReflow(); +} + +gfxFontEntry* +gfxMacPlatformFontList::PlatformGlobalFontFallback(const uint32_t aCh, + Script aRunScript, + const gfxFontStyle* aMatchStyle, + gfxFontFamily** aMatchedFamily) +{ + CFStringRef str; + UniChar ch[2]; + CFIndex length = 1; + + if (IS_IN_BMP(aCh)) { + ch[0] = aCh; + str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 1, + kCFAllocatorNull); + } else { + ch[0] = H_SURROGATE(aCh); + ch[1] = L_SURROGATE(aCh); + str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 2, + kCFAllocatorNull); + if (!str) { + return nullptr; + } + length = 2; + } + + // use CoreText to find the fallback family + + gfxFontEntry *fontEntry = nullptr; + CTFontRef fallback; + bool cantUseFallbackFont = false; + + if (!mDefaultFont) { + mDefaultFont = ::CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f, + NULL); + } + + fallback = ::CTFontCreateForString(mDefaultFont, str, + ::CFRangeMake(0, length)); + + if (fallback) { + CFStringRef familyNameRef = ::CTFontCopyFamilyName(fallback); + ::CFRelease(fallback); + + if (familyNameRef && + ::CFStringCompare(familyNameRef, CFSTR("LastResort"), + kCFCompareCaseInsensitive) != kCFCompareEqualTo) + { + AutoTArray<UniChar, 1024> buffer; + CFIndex familyNameLen = ::CFStringGetLength(familyNameRef); + buffer.SetLength(familyNameLen+1); + ::CFStringGetCharacters(familyNameRef, ::CFRangeMake(0, familyNameLen), + buffer.Elements()); + buffer[familyNameLen] = 0; + nsDependentString familyNameString(reinterpret_cast<char16_t*>(buffer.Elements()), familyNameLen); + + bool needsBold; // ignored in the system fallback case + + gfxFontFamily *family = FindFamily(familyNameString); + if (family) { + fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold); + if (fontEntry) { + if (fontEntry->HasCharacter(aCh)) { + *aMatchedFamily = family; + } else { + fontEntry = nullptr; + cantUseFallbackFont = true; + } + } + } + } + + if (familyNameRef) { + ::CFRelease(familyNameRef); + } + } + + ::CFRelease(str); + + return fontEntry; +} + +gfxFontFamily* +gfxMacPlatformFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle) +{ + nsAutoreleasePool localPool; + + NSString *defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName]; + nsAutoString familyName; + + GetStringForNSString(defaultFamily, familyName); + return FindFamily(familyName); +} + +int32_t +gfxMacPlatformFontList::AppleWeightToCSSWeight(int32_t aAppleWeight) +{ + if (aAppleWeight < 1) + aAppleWeight = 1; + else if (aAppleWeight > kAppleMaxWeight) + aAppleWeight = kAppleMaxWeight; + return gAppleWeightToCSSWeight[aAppleWeight]; +} + +gfxFontEntry* +gfxMacPlatformFontList::LookupLocalFont(const nsAString& aFontName, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle) +{ + nsAutoreleasePool localPool; + + NSString *faceName = GetNSStringForString(aFontName); + MacOSFontEntry *newFontEntry; + + // lookup face based on postscript or full name + CGFontRef fontRef = ::CGFontCreateWithFontName(CFStringRef(faceName)); + if (!fontRef) { + return nullptr; + } + + NS_ASSERTION(aWeight >= 100 && aWeight <= 900, + "bogus font weight value!"); + + newFontEntry = + new MacOSFontEntry(aFontName, fontRef, aWeight, aStretch, aStyle, + false, true); + ::CFRelease(fontRef); + + return newFontEntry; +} + +static void ReleaseData(void *info, const void *data, size_t size) +{ + free((void*)data); +} + +gfxFontEntry* +gfxMacPlatformFontList::MakePlatformFont(const nsAString& aFontName, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle, + const uint8_t* aFontData, + uint32_t aLength) +{ + NS_ASSERTION(aFontData, "MakePlatformFont called with null data"); + + NS_ASSERTION(aWeight >= 100 && aWeight <= 900, "bogus font weight value!"); + + // create the font entry + nsAutoString uniqueName; + + nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName); + if (NS_FAILED(rv)) { + return nullptr; + } + + CGDataProviderRef provider = + ::CGDataProviderCreateWithData(nullptr, aFontData, aLength, + &ReleaseData); + CGFontRef fontRef = ::CGFontCreateWithDataProvider(provider); + ::CGDataProviderRelease(provider); + + if (!fontRef) { + return nullptr; + } + + auto newFontEntry = + MakeUnique<MacOSFontEntry>(uniqueName, fontRef, aWeight, aStretch, + aStyle, true, false); + ::CFRelease(fontRef); + + // if succeeded and font cmap is good, return the new font + if (newFontEntry->mIsValid && NS_SUCCEEDED(newFontEntry->ReadCMAP())) { + return newFontEntry.release(); + } + + // if something is funky about this font, delete immediately + +#if DEBUG + NS_WARNING("downloaded font not loaded properly"); +#endif + + return nullptr; +} + +// Webkit code uses a system font meta name, so mimic that here +// WebCore/platform/graphics/mac/FontCacheMac.mm +static const char kSystemFont_system[] = "-apple-system"; + +bool +gfxMacPlatformFontList::FindAndAddFamilies(const nsAString& aFamily, + nsTArray<gfxFontFamily*>* aOutput, + gfxFontStyle* aStyle, + gfxFloat aDevToCssSize) +{ + // search for special system font name, -apple-system + if (aFamily.EqualsLiteral(kSystemFont_system)) { + if (mUseSizeSensitiveSystemFont && + aStyle && (aStyle->size * aDevToCssSize) >= kTextDisplayCrossover) { + aOutput->AppendElement(FindSystemFontFamily(mSystemDisplayFontFamilyName)); + return true; + } + aOutput->AppendElement(FindSystemFontFamily(mSystemTextFontFamilyName)); + return true; + } + + return gfxPlatformFontList::FindAndAddFamilies(aFamily, aOutput, aStyle, + aDevToCssSize); +} + +void +gfxMacPlatformFontList::LookupSystemFont(LookAndFeel::FontID aSystemFontID, + nsAString& aSystemFontName, + gfxFontStyle &aFontStyle, + float aDevPixPerCSSPixel) +{ + // code moved here from widget/cocoa/nsLookAndFeel.mm + NSFont *font = nullptr; + char* systemFontName = nullptr; + switch (aSystemFontID) { + case LookAndFeel::eFont_MessageBox: + case LookAndFeel::eFont_StatusBar: + case LookAndFeel::eFont_List: + case LookAndFeel::eFont_Field: + case LookAndFeel::eFont_Button: + case LookAndFeel::eFont_Widget: + font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + systemFontName = (char*) kSystemFont_system; + break; + + case LookAndFeel::eFont_SmallCaption: + font = [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]]; + systemFontName = (char*) kSystemFont_system; + break; + + case LookAndFeel::eFont_Icon: // used in urlbar; tried labelFont, but too small + case LookAndFeel::eFont_Workspace: + case LookAndFeel::eFont_Desktop: + case LookAndFeel::eFont_Info: + font = [NSFont controlContentFontOfSize:0.0]; + systemFontName = (char*) kSystemFont_system; + break; + + case LookAndFeel::eFont_PullDownMenu: + font = [NSFont menuBarFontOfSize:0.0]; + systemFontName = (char*) kSystemFont_system; + break; + + case LookAndFeel::eFont_Tooltips: + font = [NSFont toolTipsFontOfSize:0.0]; + systemFontName = (char*) kSystemFont_system; + break; + + case LookAndFeel::eFont_Caption: + case LookAndFeel::eFont_Menu: + case LookAndFeel::eFont_Dialog: + default: + font = [NSFont systemFontOfSize:0.0]; + systemFontName = (char*) kSystemFont_system; + break; + } + NS_ASSERTION(font, "system font not set"); + NS_ASSERTION(systemFontName, "system font name not set"); + + if (systemFontName) { + aSystemFontName.AssignASCII(systemFontName); + } + + NSFontSymbolicTraits traits = [[font fontDescriptor] symbolicTraits]; + aFontStyle.style = + (traits & NSFontItalicTrait) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL; + aFontStyle.weight = + (traits & NSFontBoldTrait) ? NS_FONT_WEIGHT_BOLD : NS_FONT_WEIGHT_NORMAL; + aFontStyle.stretch = + (traits & NSFontExpandedTrait) ? + NS_FONT_STRETCH_EXPANDED : (traits & NSFontCondensedTrait) ? + NS_FONT_STRETCH_CONDENSED : NS_FONT_STRETCH_NORMAL; + // convert size from css pixels to device pixels + aFontStyle.size = [font pointSize] * aDevPixPerCSSPixel; + aFontStyle.systemFont = true; +} + +// used to load system-wide font info on off-main thread +class MacFontInfo : public FontInfoData { +public: + MacFontInfo(bool aLoadOtherNames, + bool aLoadFaceNames, + bool aLoadCmaps) : + FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps) + {} + + virtual ~MacFontInfo() {} + + virtual void Load() { + nsAutoreleasePool localPool; + FontInfoData::Load(); + } + + // loads font data for all members of a given family + virtual void LoadFontFamilyData(const nsAString& aFamilyName); +}; + +void +MacFontInfo::LoadFontFamilyData(const nsAString& aFamilyName) +{ + // family name ==> CTFontDescriptor + NSString *famName = GetNSStringForString(aFamilyName); + CFStringRef family = CFStringRef(famName); + + CFMutableDictionaryRef attr = + CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family); + CTFontDescriptorRef fd = CTFontDescriptorCreateWithAttributes(attr); + CFRelease(attr); + CFArrayRef matchingFonts = + CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL); + CFRelease(fd); + if (!matchingFonts) { + return; + } + + nsTArray<nsString> otherFamilyNames; + bool hasOtherFamilyNames = true; + + // iterate over faces in the family + int f, numFaces = (int) CFArrayGetCount(matchingFonts); + for (f = 0; f < numFaces; f++) { + mLoadStats.fonts++; + + CTFontDescriptorRef faceDesc = + (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f); + if (!faceDesc) { + continue; + } + CTFontRef fontRef = CTFontCreateWithFontDescriptor(faceDesc, + 0.0, nullptr); + if (!fontRef) { + NS_WARNING("failed to create a CTFontRef"); + continue; + } + + if (mLoadCmaps) { + // face name + CFStringRef faceName = (CFStringRef) + CTFontDescriptorCopyAttribute(faceDesc, kCTFontNameAttribute); + + AutoTArray<UniChar, 1024> buffer; + CFIndex len = CFStringGetLength(faceName); + buffer.SetLength(len+1); + CFStringGetCharacters(faceName, ::CFRangeMake(0, len), + buffer.Elements()); + buffer[len] = 0; + nsAutoString fontName(reinterpret_cast<char16_t*>(buffer.Elements()), + len); + + // load the cmap data + FontFaceData fontData; + CFDataRef cmapTable = CTFontCopyTable(fontRef, kCTFontTableCmap, + kCTFontTableOptionNoOptions); + + if (cmapTable) { + const uint8_t *cmapData = + (const uint8_t*)CFDataGetBytePtr(cmapTable); + uint32_t cmapLen = CFDataGetLength(cmapTable); + RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap(); + uint32_t offset; + bool unicodeFont = false; // ignored + bool symbolFont = false; + nsresult rv; + + rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset, + unicodeFont, symbolFont); + if (NS_SUCCEEDED(rv)) { + fontData.mCharacterMap = charmap; + fontData.mUVSOffset = offset; + fontData.mSymbolFont = symbolFont; + mLoadStats.cmaps++; + } + CFRelease(cmapTable); + } + + mFontFaceData.Put(fontName, fontData); + CFRelease(faceName); + } + + if (mLoadOtherNames && hasOtherFamilyNames) { + CFDataRef nameTable = CTFontCopyTable(fontRef, kCTFontTableName, + kCTFontTableOptionNoOptions); + + if (nameTable) { + const char *nameData = (const char*)CFDataGetBytePtr(nameTable); + uint32_t nameLen = CFDataGetLength(nameTable); + gfxFontFamily::ReadOtherFamilyNamesForFace(aFamilyName, + nameData, nameLen, + otherFamilyNames, + false); + hasOtherFamilyNames = otherFamilyNames.Length() != 0; + CFRelease(nameTable); + } + } + + CFRelease(fontRef); + } + CFRelease(matchingFonts); + + // if found other names, insert them in the hash table + if (otherFamilyNames.Length() != 0) { + mOtherFamilyNames.Put(aFamilyName, otherFamilyNames); + mLoadStats.othernames += otherFamilyNames.Length(); + } +} + +already_AddRefed<FontInfoData> +gfxMacPlatformFontList::CreateFontInfoData() +{ + bool loadCmaps = !UsesSystemFallback() || + gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback(); + + RefPtr<MacFontInfo> fi = + new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps); + return fi.forget(); +} + +#ifdef MOZ_BUNDLED_FONTS + +void +gfxMacPlatformFontList::ActivateBundledFonts() +{ + nsCOMPtr<nsIFile> localDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) { + return; + } + if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("fonts")))) { + return; + } + bool isDir; + if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> e; + rv = localDir->GetDirectoryEntries(getter_AddRefs(e)); + if (NS_FAILED(rv)) { + return; + } + + bool hasMore; + while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> entry; + if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) { + break; + } + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + if (!file) { + continue; + } + nsCString path; + if (NS_FAILED(file->GetNativePath(path))) { + continue; + } + CFURLRef fontURL = + ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + (uint8_t*)path.get(), + path.Length(), + false); + if (fontURL) { + CFErrorRef error = nullptr; + ::CTFontManagerRegisterFontsForURL(fontURL, + kCTFontManagerScopeProcess, + &error); + ::CFRelease(fontURL); + } + } +} + +#endif diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 21e15bf06f..7ec9139c0a 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -37,6 +37,9 @@ #if defined(XP_WIN) #include "gfxWindowsPlatform.h" +#elif defined(XP_MACOSX) +#include "gfxPlatformMac.h" +#include "gfxQuartzSurface.h" #elif defined(MOZ_WIDGET_GTK) #include "gfxPlatformGtk.h" #elif defined(ANDROID) @@ -571,6 +574,8 @@ gfxPlatform::Init() #if defined(XP_WIN) gPlatform = new gfxWindowsPlatform; +#elif defined(XP_MACOSX) + gPlatform = new gfxPlatformMac; #elif defined(MOZ_WIDGET_GTK) gPlatform = new gfxPlatformGtk; #elif defined(ANDROID) @@ -2127,7 +2132,7 @@ bool gfxPlatform::AccelerateLayersByDefault() { // Note: add any new platform defines here that should get HWA by default. -#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_UIKIT) +#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_UIKIT) return true; #elif defined(MOZ_GL_PROVIDER) // GL provider manually declared diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp new file mode 100644 index 0000000000..75c5236a87 --- /dev/null +++ b/gfx/thebes/gfxPlatformMac.cpp @@ -0,0 +1,617 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "gfxPlatformMac.h" + +#include "gfxQuartzSurface.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/MacIOSurface.h" + +#include "gfxMacPlatformFontList.h" +#include "gfxMacFont.h" +#include "gfxCoreTextShaper.h" +#include "gfxTextRun.h" +#include "gfxUserFontSet.h" + +#include "nsTArray.h" +#include "mozilla/Preferences.h" +#include "mozilla/VsyncDispatcher.h" +#include "nsUnicodeProperties.h" +#include "qcms.h" +#include "gfx2DGlue.h" + +#include <dlfcn.h> +#include <CoreVideo/CoreVideo.h> + +#include "mozilla/layers/CompositorBridgeParent.h" +#include "VsyncSource.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::unicode; + +// cribbed from CTFontManager.h +enum { + kAutoActivationDisabled = 1 +}; +typedef uint32_t AutoActivationSetting; + +// bug 567552 - disable auto-activation of fonts + +static void +DisableFontActivation() +{ + // get the main bundle identifier + CFBundleRef mainBundle = ::CFBundleGetMainBundle(); + CFStringRef mainBundleID = nullptr; + + if (mainBundle) { + mainBundleID = ::CFBundleGetIdentifier(mainBundle); + } + + // bug 969388 and bug 922590 - mainBundlID as null is sometimes problematic + if (!mainBundleID) { + NS_WARNING("missing bundle ID, packaging set up incorrectly"); + return; + } + + // if possible, fetch CTFontManagerSetAutoActivationSetting + void (*CTFontManagerSetAutoActivationSettingPtr) + (CFStringRef, AutoActivationSetting); + CTFontManagerSetAutoActivationSettingPtr = + (void (*)(CFStringRef, AutoActivationSetting)) + dlsym(RTLD_DEFAULT, "CTFontManagerSetAutoActivationSetting"); + + // bug 567552 - disable auto-activation of fonts + if (CTFontManagerSetAutoActivationSettingPtr) { + CTFontManagerSetAutoActivationSettingPtr(mainBundleID, + kAutoActivationDisabled); + } +} + +gfxPlatformMac::gfxPlatformMac() +{ + DisableFontActivation(); + mFontAntiAliasingThreshold = ReadAntiAliasingThreshold(); + + uint32_t canvasMask = BackendTypeBit(BackendType::SKIA); + uint32_t contentMask = BackendTypeBit(BackendType::SKIA); + InitBackendPrefs(canvasMask, BackendType::SKIA, + contentMask, BackendType::SKIA); + + // XXX: Bug 1036682 - we run out of fds on Mac when using tiled layers because + // with 256x256 tiles we can easily hit the soft limit of 800 when using double + // buffered tiles in e10s, so let's bump the soft limit to the hard limit for the OS + // up to a new cap of OPEN_MAX. + struct rlimit limits; + if (getrlimit(RLIMIT_NOFILE, &limits) == 0) { + limits.rlim_cur = std::min(rlim_t(OPEN_MAX), limits.rlim_max); + if (setrlimit(RLIMIT_NOFILE, &limits) != 0) { + NS_WARNING("Unable to bump RLIMIT_NOFILE to the maximum number on this OS"); + } + } + + MacIOSurfaceLib::LoadLibrary(); +} + +gfxPlatformMac::~gfxPlatformMac() +{ + gfxCoreTextShaper::Shutdown(); +} + +gfxPlatformFontList* +gfxPlatformMac::CreatePlatformFontList() +{ + gfxPlatformFontList* list = new gfxMacPlatformFontList(); + if (NS_SUCCEEDED(list->InitFontList())) { + return list; + } + gfxPlatformFontList::Shutdown(); + return nullptr; +} + +already_AddRefed<gfxASurface> +gfxPlatformMac::CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) +{ + if (!Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + RefPtr<gfxASurface> newSurface = + new gfxQuartzSurface(aSize, aFormat); + return newSurface.forget(); +} + +already_AddRefed<ScaledFont> +gfxPlatformMac::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont) +{ + gfxMacFont *font = static_cast<gfxMacFont*>(aFont); + return font->GetScaledFont(aTarget); +} + +gfxFontGroup * +gfxPlatformMac::CreateFontGroup(const FontFamilyList& aFontFamilyList, + const gfxFontStyle *aStyle, + gfxTextPerfMetrics* aTextPerf, + gfxUserFontSet *aUserFontSet, + gfxFloat aDevToCssSize) +{ + return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf, + aUserFontSet, aDevToCssSize); +} + +bool +gfxPlatformMac::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) +{ + // check for strange format flags + NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED), + "strange font format hint set"); + + // accept supported formats + if (aFormatFlags & (gfxUserFontSet::FLAG_FORMATS_COMMON | + gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT)) { + return true; + } + + // reject all other formats, known and unknown + if (aFormatFlags != 0) { + return false; + } + + // no format hint set, need to look at data + return true; +} + +static const char kFontArialUnicodeMS[] = "Arial Unicode MS"; +static const char kFontAppleBraille[] = "Apple Braille"; +static const char kFontAppleColorEmoji[] = "Apple Color Emoji"; +static const char kFontAppleSymbols[] = "Apple Symbols"; +static const char kFontDevanagariSangamMN[] = "Devanagari Sangam MN"; +static const char kFontEuphemiaUCAS[] = "Euphemia UCAS"; +static const char kFontGeneva[] = "Geneva"; +static const char kFontGeezaPro[] = "Geeza Pro"; +static const char kFontGujaratiSangamMN[] = "Gujarati Sangam MN"; +static const char kFontGurmukhiMN[] = "Gurmukhi MN"; +static const char kFontHiraginoKakuGothic[] = "Hiragino Kaku Gothic ProN"; +static const char kFontHiraginoSansGB[] = "Hiragino Sans GB"; +static const char kFontKefa[] = "Kefa"; +static const char kFontKhmerMN[] = "Khmer MN"; +static const char kFontLaoMN[] = "Lao MN"; +static const char kFontLucidaGrande[] = "Lucida Grande"; +static const char kFontMenlo[] = "Menlo"; +static const char kFontMicrosoftTaiLe[] = "Microsoft Tai Le"; +static const char kFontMingLiUExtB[] = "MingLiU-ExtB"; +static const char kFontMyanmarMN[] = "Myanmar MN"; +static const char kFontPlantagenetCherokee[] = "Plantagenet Cherokee"; +static const char kFontSimSunExtB[] = "SimSun-ExtB"; +static const char kFontSongtiSC[] = "Songti SC"; +static const char kFontSTHeiti[] = "STHeiti"; +static const char kFontSTIXGeneral[] = "STIXGeneral"; +static const char kFontTamilMN[] = "Tamil MN"; + +void +gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh, + Script aRunScript, + nsTArray<const char*>& aFontList) +{ + EmojiPresentation emoji = GetEmojiPresentation(aCh); + if (emoji != EmojiPresentation::TextOnly) { + if (aNextCh == kVariationSelector16 || + (aNextCh != kVariationSelector15 && + emoji == EmojiPresentation::EmojiDefault)) { + // if char is followed by VS16, try for a color emoji glyph + aFontList.AppendElement(kFontAppleColorEmoji); + } + } + + aFontList.AppendElement(kFontLucidaGrande); + + if (!IS_IN_BMP(aCh)) { + uint32_t p = aCh >> 16; + if (p == 1) { + aFontList.AppendElement(kFontAppleSymbols); + aFontList.AppendElement(kFontSTIXGeneral); + aFontList.AppendElement(kFontGeneva); + } else if (p == 2) { + // OSX installations with MS Office may have these fonts + aFontList.AppendElement(kFontMingLiUExtB); + aFontList.AppendElement(kFontSimSunExtB); + } + } else { + uint32_t b = (aCh >> 8) & 0xff; + + switch (b) { + case 0x03: + case 0x05: + aFontList.AppendElement(kFontGeneva); + break; + case 0x07: + aFontList.AppendElement(kFontGeezaPro); + break; + case 0x09: + aFontList.AppendElement(kFontDevanagariSangamMN); + break; + case 0x0a: + aFontList.AppendElement(kFontGurmukhiMN); + aFontList.AppendElement(kFontGujaratiSangamMN); + break; + case 0x0b: + aFontList.AppendElement(kFontTamilMN); + break; + case 0x0e: + aFontList.AppendElement(kFontLaoMN); + break; + case 0x0f: + aFontList.AppendElement(kFontSongtiSC); + break; + case 0x10: + aFontList.AppendElement(kFontMenlo); + aFontList.AppendElement(kFontMyanmarMN); + break; + case 0x13: // Cherokee + aFontList.AppendElement(kFontPlantagenetCherokee); + aFontList.AppendElement(kFontKefa); + break; + case 0x14: // Unified Canadian Aboriginal Syllabics + case 0x15: + case 0x16: + aFontList.AppendElement(kFontEuphemiaUCAS); + aFontList.AppendElement(kFontGeneva); + break; + case 0x18: // Mongolian, UCAS + aFontList.AppendElement(kFontSTHeiti); + aFontList.AppendElement(kFontEuphemiaUCAS); + break; + case 0x19: // Khmer + aFontList.AppendElement(kFontKhmerMN); + aFontList.AppendElement(kFontMicrosoftTaiLe); + break; + case 0x1d: + case 0x1e: + aFontList.AppendElement(kFontGeneva); + break; + case 0x20: // Symbol ranges + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2e: + aFontList.AppendElement(kFontHiraginoKakuGothic); + aFontList.AppendElement(kFontAppleSymbols); + aFontList.AppendElement(kFontMenlo); + aFontList.AppendElement(kFontSTIXGeneral); + aFontList.AppendElement(kFontGeneva); + aFontList.AppendElement(kFontAppleColorEmoji); + break; + case 0x2c: + aFontList.AppendElement(kFontGeneva); + break; + case 0x2d: + aFontList.AppendElement(kFontKefa); + aFontList.AppendElement(kFontGeneva); + break; + case 0x28: // Braille + aFontList.AppendElement(kFontAppleBraille); + break; + case 0x31: + aFontList.AppendElement(kFontHiraginoSansGB); + break; + case 0x4d: + aFontList.AppendElement(kFontAppleSymbols); + break; + case 0xa0: // Yi + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + aFontList.AppendElement(kFontSTHeiti); + break; + case 0xa6: + case 0xa7: + aFontList.AppendElement(kFontGeneva); + aFontList.AppendElement(kFontAppleSymbols); + break; + case 0xab: + aFontList.AppendElement(kFontKefa); + break; + case 0xfc: + case 0xff: + aFontList.AppendElement(kFontAppleSymbols); + break; + default: + break; + } + } + + // Arial Unicode MS has lots of glyphs for obscure, use it as a last resort + aFontList.AppendElement(kFontArialUnicodeMS); +} + +/*static*/ void +gfxPlatformMac::LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID, + nsAString& aSystemFontName, + gfxFontStyle& aFontStyle, + float aDevPixPerCSSPixel) +{ + gfxMacPlatformFontList* pfl = gfxMacPlatformFontList::PlatformFontList(); + return pfl->LookupSystemFont(aSystemFontID, aSystemFontName, aFontStyle, + aDevPixPerCSSPixel); +} + +uint32_t +gfxPlatformMac::ReadAntiAliasingThreshold() +{ + uint32_t threshold = 0; // default == no threshold + + // first read prefs flag to determine whether to use the setting or not + bool useAntiAliasingThreshold = Preferences::GetBool("gfx.use_text_smoothing_setting", false); + + // if the pref setting is disabled, return 0 which effectively disables this feature + if (!useAntiAliasingThreshold) + return threshold; + + // value set via Appearance pref panel, "Turn off text smoothing for font sizes xxx and smaller" + CFNumberRef prefValue = (CFNumberRef)CFPreferencesCopyAppValue(CFSTR("AppleAntiAliasingThreshold"), kCFPreferencesCurrentApplication); + + if (prefValue) { + if (!CFNumberGetValue(prefValue, kCFNumberIntType, &threshold)) { + threshold = 0; + } + CFRelease(prefValue); + } + + return threshold; +} + +// This is the renderer output callback function, called on the vsync thread +static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink, + const CVTimeStamp* aNow, + const CVTimeStamp* aOutputTime, + CVOptionFlags aFlagsIn, + CVOptionFlags* aFlagsOut, + void* aDisplayLinkContext); + +class OSXVsyncSource final : public VsyncSource +{ +public: + OSXVsyncSource() + { + } + + virtual Display& GetGlobalDisplay() override + { + return mGlobalDisplay; + } + + class OSXDisplay final : public VsyncSource::Display + { + public: + OSXDisplay() + : mDisplayLink(nullptr) + { + MOZ_ASSERT(NS_IsMainThread()); + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + ~OSXDisplay() + { + MOZ_ASSERT(NS_IsMainThread()); + } + + static void RetryEnableVsync(nsITimer* aTimer, void* aOsxDisplay) + { + MOZ_ASSERT(NS_IsMainThread()); + OSXDisplay* osxDisplay = static_cast<OSXDisplay*>(aOsxDisplay); + MOZ_ASSERT(osxDisplay); + osxDisplay->EnableVsync(); + } + + virtual void EnableVsync() override + { + MOZ_ASSERT(NS_IsMainThread()); + if (IsVsyncEnabled()) { + return; + } + + // Create a display link capable of being used with all active displays + // TODO: See if we need to create an active DisplayLink for each monitor in multi-monitor + // situations. According to the docs, it is compatible with all displays running on the computer + // But if we have different monitors at different display rates, we may hit issues. + if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) { + NS_WARNING("Could not create a display link with all active displays. Retrying"); + CVDisplayLinkRelease(mDisplayLink); + mDisplayLink = nullptr; + + // bug 1142708 - When coming back from sleep, + // or when changing displays, active displays may not be ready yet, + // even if listening for the kIOMessageSystemHasPoweredOn event + // from OS X sleep notifications. + // Active displays are those that are drawable. + // bug 1144638 - When changing display configurations and getting + // notifications from CGDisplayReconfigurationCallBack, the + // callback gets called twice for each active display + // so it's difficult to know when all displays are active. + // Instead, try again soon. The delay is arbitrary. 100ms chosen + // because on a late 2013 15" retina, it takes about that + // long to come back up from sleep. + uint32_t delay = 100; + mTimer->InitWithFuncCallback(RetryEnableVsync, this, delay, nsITimer::TYPE_ONE_SHOT); + return; + } + + if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) { + NS_WARNING("Could not set displaylink output callback"); + CVDisplayLinkRelease(mDisplayLink); + mDisplayLink = nullptr; + return; + } + + mPreviousTimestamp = TimeStamp::Now(); + if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) { + NS_WARNING("Could not activate the display link"); + CVDisplayLinkRelease(mDisplayLink); + mDisplayLink = nullptr; + } + + CVTime vsyncRate = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(mDisplayLink); + if (vsyncRate.flags & kCVTimeIsIndefinite) { + NS_WARNING("Could not get vsync rate, setting to 60."); + mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0); + } else { + int64_t timeValue = vsyncRate.timeValue; + int64_t timeScale = vsyncRate.timeScale; + const int milliseconds = 1000; + float rateInMs = ((double) timeValue / (double) timeScale) * milliseconds; + mVsyncRate = TimeDuration::FromMilliseconds(rateInMs); + } + } + + virtual void DisableVsync() override + { + MOZ_ASSERT(NS_IsMainThread()); + if (!IsVsyncEnabled()) { + return; + } + + // Release the display link + if (mDisplayLink) { + CVDisplayLinkRelease(mDisplayLink); + mDisplayLink = nullptr; + } + } + + virtual bool IsVsyncEnabled() override + { + MOZ_ASSERT(NS_IsMainThread()); + return mDisplayLink != nullptr; + } + + virtual TimeDuration GetVsyncRate() override + { + return mVsyncRate; + } + + virtual void Shutdown() override + { + MOZ_ASSERT(NS_IsMainThread()); + mTimer->Cancel(); + mTimer = nullptr; + DisableVsync(); + } + + // The vsync timestamps given by the CVDisplayLinkCallback are + // in the future for the NEXT frame. Large parts of Gecko, such + // as animations assume a timestamp at either now or in the past. + // Normalize the timestamps given to the VsyncDispatchers to the vsync + // that just occured, not the vsync that is upcoming. + TimeStamp mPreviousTimestamp; + + private: + // Manages the display link render thread + CVDisplayLinkRef mDisplayLink; + RefPtr<nsITimer> mTimer; + TimeDuration mVsyncRate; + }; // OSXDisplay + +private: + virtual ~OSXVsyncSource() + { + } + + OSXDisplay mGlobalDisplay; +}; // OSXVsyncSource + +static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink, + const CVTimeStamp* aNow, + const CVTimeStamp* aOutputTime, + CVOptionFlags aFlagsIn, + CVOptionFlags* aFlagsOut, + void* aDisplayLinkContext) +{ + // Executed on OS X hardware vsync thread + OSXVsyncSource::OSXDisplay* display = (OSXVsyncSource::OSXDisplay*) aDisplayLinkContext; + int64_t nextVsyncTimestamp = aOutputTime->hostTime; + + mozilla::TimeStamp nextVsync = mozilla::TimeStamp::FromSystemTime(nextVsyncTimestamp); + mozilla::TimeStamp previousVsync = display->mPreviousTimestamp; + mozilla::TimeStamp now = TimeStamp::Now(); + + // Snow leopard sometimes sends vsync timestamps very far in the past. + // Normalize the vsync timestamps to now. + if (nextVsync <= previousVsync) { + nextVsync = now; + previousVsync = now; + } else if (now < previousVsync) { + // Bug 1158321 - The VsyncCallback can sometimes execute before the reported + // vsync time. In those cases, normalize the timestamp to Now() as sending + // timestamps in the future has undefined behavior. See the comment above + // OSXDisplay::mPreviousTimestamp + previousVsync = now; + } + + display->mPreviousTimestamp = nextVsync; + + display->NotifyVsync(previousVsync); + return kCVReturnSuccess; +} + +already_AddRefed<mozilla::gfx::VsyncSource> +gfxPlatformMac::CreateHardwareVsyncSource() +{ + RefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource(); + VsyncSource::Display& primaryDisplay = osxVsyncSource->GetGlobalDisplay(); + primaryDisplay.EnableVsync(); + if (!primaryDisplay.IsVsyncEnabled()) { + NS_WARNING("OS X Vsync source not enabled. Falling back to software vsync."); + return gfxPlatform::CreateHardwareVsyncSource(); + } + + primaryDisplay.DisableVsync(); + return osxVsyncSource.forget(); +} + +void +gfxPlatformMac::GetPlatformCMSOutputProfile(void* &mem, size_t &size) +{ + mem = nullptr; + size = 0; + + CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID()); + if (!cspace) { + cspace = ::CGColorSpaceCreateDeviceRGB(); + } + if (!cspace) { + return; + } + + CFDataRef iccp = ::CGColorSpaceCopyICCProfile(cspace); + + ::CFRelease(cspace); + + if (!iccp) { + return; + } + + // copy to external buffer + size = static_cast<size_t>(::CFDataGetLength(iccp)); + if (size > 0) { + void *data = malloc(size); + if (data) { + memcpy(data, ::CFDataGetBytePtr(iccp), size); + mem = data; + } else { + size = 0; + } + } + + ::CFRelease(iccp); +} diff --git a/gfx/thebes/gfxPlatformMac.h b/gfx/thebes/gfxPlatformMac.h new file mode 100644 index 0000000000..ea4c1a1011 --- /dev/null +++ b/gfx/thebes/gfxPlatformMac.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef GFX_PLATFORM_MAC_H +#define GFX_PLATFORM_MAC_H + +#include "nsTArrayForwardDeclare.h" +#include "gfxPlatform.h" +#include "mozilla/LookAndFeel.h" + +namespace mozilla { +namespace gfx { +class DrawTarget; +class VsyncSource; +} // namespace gfx +} // namespace mozilla + +class gfxPlatformMac : public gfxPlatform { +public: + gfxPlatformMac(); + virtual ~gfxPlatformMac(); + + static gfxPlatformMac *GetPlatform() { + return (gfxPlatformMac*) gfxPlatform::GetPlatform(); + } + + virtual already_AddRefed<gfxASurface> + CreateOffscreenSurface(const IntSize& aSize, + gfxImageFormat aFormat) override; + + already_AddRefed<mozilla::gfx::ScaledFont> + GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override; + + gfxFontGroup* + CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList, + const gfxFontStyle *aStyle, + gfxTextPerfMetrics* aTextPerf, + gfxUserFontSet *aUserFontSet, + gfxFloat aDevToCssSize) override; + + virtual gfxPlatformFontList* CreatePlatformFontList() override; + + bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) override; + + virtual void GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh, + Script aRunScript, + nsTArray<const char*>& aFontList) override; + + // lookup the system font for a particular system font type and set + // the name and style characteristics + static void + LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID, + nsAString& aSystemFontName, + gfxFontStyle &aFontStyle, + float aDevPixPerCSSPixel); + + virtual bool CanRenderContentToDataSurface() const override { + return true; + } + + virtual bool SupportsApzWheelInput() const override { + return true; + } + + bool RespectsFontStyleSmoothing() const override { + // gfxMacFont respects the font smoothing hint. + return true; + } + + bool RequiresAcceleratedGLContextForCompositorOGL() const override { + // On OS X in a VM, unaccelerated CompositorOGL shows black flashes, so we + // require accelerated GL for CompositorOGL but allow unaccelerated GL for + // BasicCompositor. + return true; + } + + virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override; + + // lower threshold on font anti-aliasing + uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; } + +private: + virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size) override; + + // read in the pref value for the lower threshold on font anti-aliasing + static uint32_t ReadAntiAliasingThreshold(); + + uint32_t mFontAntiAliasingThreshold; +}; + +#endif /* GFX_PLATFORM_MAC_H */ diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index b02638e5a6..ac5bdd45a2 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -424,6 +424,9 @@ private: DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist", IgnoreDXInterop2Blacklist, bool, false); DECL_GFX_PREF(Live, "gl.msaa-level", MSAALevel, uint32_t, 2); +#if defined(XP_MACOSX) + DECL_GFX_PREF(Live, "gl.multithreaded", GLMultithreaded, bool, false); +#endif DECL_GFX_PREF(Live, "gl.require-hardware", RequireHardwareGL, bool, false); DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024); diff --git a/gfx/thebes/gfxQuartzNativeDrawing.cpp b/gfx/thebes/gfxQuartzNativeDrawing.cpp new file mode 100644 index 0000000000..f14b71d779 --- /dev/null +++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "gfxQuartzNativeDrawing.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/Helpers.h" + +using namespace mozilla::gfx; +using namespace mozilla; + +gfxQuartzNativeDrawing::gfxQuartzNativeDrawing(DrawTarget& aDrawTarget, + const Rect& nativeRect) + : mDrawTarget(&aDrawTarget) + , mNativeRect(nativeRect) + , mCGContext(nullptr) +{ +} + +CGContextRef +gfxQuartzNativeDrawing::BeginNativeDrawing() +{ + NS_ASSERTION(!mCGContext, "BeginNativeDrawing called when drawing already in progress"); + + DrawTarget *dt = mDrawTarget; + if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget() || + dt->GetBackendType() != BackendType::SKIA) { + // We need a DrawTarget that we can get a CGContextRef from: + Matrix transform = dt->GetTransform(); + + mNativeRect = transform.TransformBounds(mNativeRect); + mNativeRect.RoundOut(); + // Quartz theme drawing often adjusts drawing rects, so make + // sure our surface is big enough for that. + mNativeRect.Inflate(5); + if (mNativeRect.IsEmpty()) { + return nullptr; + } + + mTempDrawTarget = + Factory::CreateDrawTarget(BackendType::SKIA, + IntSize::Truncate(mNativeRect.width, mNativeRect.height), + SurfaceFormat::B8G8R8A8); + + if (mTempDrawTarget) { + transform.PostTranslate(-mNativeRect.x, -mNativeRect.y); + mTempDrawTarget->SetTransform(transform); + } + dt = mTempDrawTarget; + } + if (dt) { + MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA); + mCGContext = mBorrowedContext.Init(dt); + MOZ_ASSERT(mCGContext); + } + return mCGContext; +} + +void +gfxQuartzNativeDrawing::EndNativeDrawing() +{ + NS_ASSERTION(mCGContext, "EndNativeDrawing called without BeginNativeDrawing"); + + mBorrowedContext.Finish(); + if (mTempDrawTarget) { + RefPtr<SourceSurface> source = mTempDrawTarget->Snapshot(); + + AutoRestoreTransform autoRestore(mDrawTarget); + mDrawTarget->SetTransform(Matrix()); + mDrawTarget->DrawSurface(source, mNativeRect, + Rect(0, 0, mNativeRect.width, mNativeRect.height)); + } +} diff --git a/gfx/thebes/gfxQuartzNativeDrawing.h b/gfx/thebes/gfxQuartzNativeDrawing.h new file mode 100644 index 0000000000..736f9ce836 --- /dev/null +++ b/gfx/thebes/gfxQuartzNativeDrawing.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef _GFXQUARTZNATIVEDRAWING_H_ +#define _GFXQUARTZNATIVEDRAWING_H_ + +#include "mozilla/Attributes.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BorrowedContext.h" +#include "mozilla/RefPtr.h" + +class gfxQuartzNativeDrawing { + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::Rect Rect; +public: + + /* Create native Quartz drawing for a rectangle bounded by + * nativeRect. + * + * Typical usage looks like: + * + * gfxQuartzNativeDrawing nativeDraw(ctx, nativeRect); + * CGContextRef cgContext = nativeDraw.BeginNativeDrawing(); + * if (!cgContext) + * return NS_ERROR_FAILURE; + * + * ... call Quartz operations on CGContextRef to draw to nativeRect ... + * + * nativeDraw.EndNativeDrawing(); + * + * aNativeRect is the size of the surface (in Quartz/Cocoa points) that + * will be created _if_ the gfxQuartzNativeDrawing decides to create a new + * surface and CGContext for its drawing operations, which it then + * composites into the target DrawTarget. + * + * (Note that aNativeRect will be ignored if the gfxQuartzNativeDrawing + * uses the target DrawTarget directly.) + * + * The optional aBackingScale parameter is a scaling factor that will be + * applied when creating and rendering into such a temporary surface. + */ + gfxQuartzNativeDrawing(DrawTarget& aDrawTarget, + const Rect& aNativeRect); + + /* Returns a CGContextRef which may be used for native drawing. This + * CGContextRef is valid until EndNativeDrawing is called; if it is used + * for drawing after that time, the result is undefined. */ + CGContextRef BeginNativeDrawing(); + + /* Marks the end of native drawing */ + void EndNativeDrawing(); + +private: + // don't allow copying via construction or assignment + gfxQuartzNativeDrawing(const gfxQuartzNativeDrawing&) = delete; + const gfxQuartzNativeDrawing& operator=(const gfxQuartzNativeDrawing&) = delete; + + // Final destination context + RefPtr<DrawTarget> mDrawTarget; + RefPtr<DrawTarget> mTempDrawTarget; + mozilla::gfx::BorrowedCGContext mBorrowedContext; + mozilla::gfx::Rect mNativeRect; + + // saved state + CGContextRef mCGContext; +}; + +#endif diff --git a/gfx/thebes/gfxQuartzSurface.cpp b/gfx/thebes/gfxQuartzSurface.cpp new file mode 100644 index 0000000000..99553e0c07 --- /dev/null +++ b/gfx/thebes/gfxQuartzSurface.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "gfxQuartzSurface.h" +#include "gfxContext.h" +#include "gfxImageSurface.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/HelpersCairo.h" + +#include "cairo-quartz.h" + +void +gfxQuartzSurface::MakeInvalid() +{ + mSize = mozilla::gfx::IntSize(-1, -1); +} + +gfxQuartzSurface::gfxQuartzSurface(const mozilla::gfx::IntSize& desiredSize, + gfxImageFormat format) + : mCGContext(nullptr), mSize(desiredSize) +{ + if (!mozilla::gfx::Factory::CheckSurfaceSize(desiredSize)) + MakeInvalid(); + + unsigned int width = static_cast<unsigned int>(mSize.width); + unsigned int height = static_cast<unsigned int>(mSize.height); + + cairo_format_t cformat = GfxFormatToCairoFormat(format); + cairo_surface_t *surf = cairo_quartz_surface_create(cformat, width, height); + + mCGContext = cairo_quartz_surface_get_cg_context (surf); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + } +} + +gfxQuartzSurface::gfxQuartzSurface(CGContextRef context, + const mozilla::gfx::IntSize& size) + : mCGContext(context), mSize(size) +{ + if (!mozilla::gfx::Factory::CheckSurfaceSize(size)) + MakeInvalid(); + + unsigned int width = static_cast<unsigned int>(mSize.width); + unsigned int height = static_cast<unsigned int>(mSize.height); + + cairo_surface_t *surf = + cairo_quartz_surface_create_for_cg_context(context, + width, height); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface)); + } +} + +gfxQuartzSurface::gfxQuartzSurface(cairo_surface_t *csurf, + const mozilla::gfx::IntSize& aSize) : + mSize(aSize) +{ + mCGContext = cairo_quartz_surface_get_cg_context (csurf); + CGContextRetain (mCGContext); + + Init(csurf, true); +} + +gfxQuartzSurface::gfxQuartzSurface(unsigned char *data, + const mozilla::gfx::IntSize& aSize, + long stride, + gfxImageFormat format) + : mCGContext(nullptr), mSize(aSize.width, aSize.height) +{ + if (!mozilla::gfx::Factory::CheckSurfaceSize(aSize)) + MakeInvalid(); + + cairo_format_t cformat = GfxFormatToCairoFormat(format); + cairo_surface_t *surf = cairo_quartz_surface_create_for_data + (data, cformat, aSize.width, aSize.height, stride); + + mCGContext = cairo_quartz_surface_get_cg_context (surf); + + CGContextRetain(mCGContext); + + Init(surf); + if (mSurfaceValid) { + RecordMemoryUsed(mSize.height * stride + sizeof(gfxQuartzSurface)); + } +} + +already_AddRefed<gfxASurface> +gfxQuartzSurface::CreateSimilarSurface(gfxContentType aType, + const mozilla::gfx::IntSize& aSize) +{ + cairo_surface_t *surface = + cairo_quartz_surface_create_cg_layer(mSurface, (cairo_content_t)aType, + aSize.width, aSize.height); + if (cairo_surface_status(surface)) { + cairo_surface_destroy(surface); + return nullptr; + } + + RefPtr<gfxASurface> result = Wrap(surface, aSize); + cairo_surface_destroy(surface); + return result.forget(); +} + +already_AddRefed<gfxImageSurface> gfxQuartzSurface::GetAsImageSurface() +{ + cairo_surface_t *surface = cairo_quartz_surface_get_image(mSurface); + if (!surface || cairo_surface_status(surface)) + return nullptr; + + RefPtr<gfxASurface> img = Wrap(surface); + + // cairo_quartz_surface_get_image returns a referenced image, and thebes + // shares the refcounts of Cairo surfaces. However, Wrap also adds a + // reference to the image. We need to remove one of these references + // explicitly so we don't leak. + img.get()->Release(); + + img->SetOpaqueRect(GetOpaqueRect()); + + return img.forget().downcast<gfxImageSurface>(); +} + +gfxQuartzSurface::~gfxQuartzSurface() +{ + CGContextRelease(mCGContext); +} diff --git a/gfx/thebes/gfxQuartzSurface.h b/gfx/thebes/gfxQuartzSurface.h new file mode 100644 index 0000000000..50e2bfb2c3 --- /dev/null +++ b/gfx/thebes/gfxQuartzSurface.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef GFX_QUARTZSURFACE_H +#define GFX_QUARTZSURFACE_H + +#include "gfxASurface.h" +#include "nsSize.h" +#include "gfxPoint.h" + +#include <Carbon/Carbon.h> + +class gfxContext; +class gfxImageSurface; + +class gfxQuartzSurface : public gfxASurface { +public: + gfxQuartzSurface(const mozilla::gfx::IntSize&, gfxImageFormat format); + gfxQuartzSurface(CGContextRef context, const mozilla::gfx::IntSize& size); + gfxQuartzSurface(cairo_surface_t *csurf, const mozilla::gfx::IntSize& aSize); + gfxQuartzSurface(unsigned char *data, const mozilla::gfx::IntSize& size, long stride, gfxImageFormat format); + + virtual ~gfxQuartzSurface(); + + virtual already_AddRefed<gfxASurface> CreateSimilarSurface(gfxContentType aType, + const mozilla::gfx::IntSize& aSize); + + virtual const mozilla::gfx::IntSize GetSize() const { return mozilla::gfx::IntSize(mSize.width, mSize.height); } + + CGContextRef GetCGContext() { return mCGContext; } + + already_AddRefed<gfxImageSurface> GetAsImageSurface(); + +protected: + void MakeInvalid(); + + CGContextRef mCGContext; + mozilla::gfx::IntSize mSize; +}; + +#endif /* GFX_QUARTZSURFACE_H */ diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp index ae746b8c5b..db9fc346b4 100644 --- a/gfx/thebes/gfxTextRun.cpp +++ b/gfx/thebes/gfxTextRun.cpp @@ -518,6 +518,11 @@ HasSyntheticBoldOrColor(const gfxTextRun *aRun, gfxTextRun::Range aRange) if (fe->TryGetSVGData(font) || fe->TryGetColorGlyphs()) { return true; } +#if defined(XP_MACOSX) // sbix fonts only supported via Core Text + if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { + return true; + } +#endif } } return false; diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build index be28f4e51c..1d3cf3bc93 100644 --- a/gfx/thebes/moz.build +++ b/gfx/thebes/moz.build @@ -57,7 +57,24 @@ EXPORTS.mozilla.gfx += [ 'PrintTargetThebes.h', ] -if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + EXPORTS += [ + 'gfxPlatformMac.h', + 'gfxQuartzNativeDrawing.h', + 'gfxQuartzSurface.h', + ] + EXPORTS.mozilla.gfx += [ + 'PrintTargetCG.h', + ] + SOURCES += [ + 'gfxCoreTextShaper.cpp', + 'gfxMacFont.cpp', + 'gfxPlatformMac.cpp', + 'gfxQuartzNativeDrawing.cpp', + 'gfxQuartzSurface.cpp', + 'PrintTargetCG.cpp', + ] +elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: EXPORTS += [ 'gfxFontconfigFonts.h', 'gfxFT2FontBase.h', @@ -165,7 +182,11 @@ SOURCES += [ 'VsyncSource.cpp', ] -if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'gfxMacPlatformFontList.mm', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': SOURCES += [ 'D3D11Checks.cpp', 'DeviceManagerDx.cpp', |