diff options
Diffstat (limited to 'gfx/thebes/gfxFcPlatformFontList.cpp')
-rw-r--r-- | gfx/thebes/gfxFcPlatformFontList.cpp | 1866 |
1 files changed, 1866 insertions, 0 deletions
diff --git a/gfx/thebes/gfxFcPlatformFontList.cpp b/gfx/thebes/gfxFcPlatformFontList.cpp new file mode 100644 index 000000000..601e7a90c --- /dev/null +++ b/gfx/thebes/gfxFcPlatformFontList.cpp @@ -0,0 +1,1866 @@ +/* -*- 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/Logging.h" + +#include "gfxFcPlatformFontList.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "gfxFontFamilyList.h" +#include "gfxFT2Utils.h" +#include "gfxPlatform.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Sprintf.h" +#include "mozilla/TimeStamp.h" +#include "nsGkAtoms.h" +#include "nsILanguageAtomService.h" +#include "nsUnicodeProperties.h" +#include "nsUnicodeRange.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCharSeparatedTokenizer.h" + +#include "mozilla/gfx/HelpersCairo.h" + +#include <fontconfig/fcfreetype.h> + +#ifdef MOZ_WIDGET_GTK +#include <gdk/gdk.h> +#include "gfxPlatformGtk.h" +#endif + +#ifdef MOZ_X11 +#include "mozilla/X11Util.h" +#endif + +using namespace mozilla; +using namespace mozilla::unicode; + +#ifndef FC_POSTSCRIPT_NAME +#define FC_POSTSCRIPT_NAME "postscriptname" /* String */ +#endif + +#define PRINTING_FC_PROPERTY "gfx.printing" + +#define LOG_FONTLIST(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \ + LogLevel::Debug, args) +#define LOG_FONTLIST_ENABLED() MOZ_LOG_TEST( \ + gfxPlatform::GetLog(eGfxLog_fontlist), \ + LogLevel::Debug) +#define LOG_CMAPDATA_ENABLED() MOZ_LOG_TEST( \ + gfxPlatform::GetLog(eGfxLog_cmapdata), \ + LogLevel::Debug) + +static const FcChar8* +ToFcChar8Ptr(const char* aStr) +{ + return reinterpret_cast<const FcChar8*>(aStr); +} + +static const char* +ToCharPtr(const FcChar8 *aStr) +{ + return reinterpret_cast<const char*>(aStr); +} + +FT_Library gfxFcPlatformFontList::sCairoFTLibrary = nullptr; + +static cairo_user_data_key_t sFcFontlistUserFontDataKey; + +// canonical name ==> first en name or first name if no en name +// This is the required logic for fullname lookups as per CSS3 Fonts spec. +static uint32_t +FindCanonicalNameIndex(FcPattern* aFont, const char* aLangField) +{ + uint32_t n = 0, en = 0; + FcChar8* lang; + while (FcPatternGetString(aFont, aLangField, n, &lang) == FcResultMatch) { + // look for 'en' or variants, en-US, en-JP etc. + uint32_t len = strlen(ToCharPtr(lang)); + bool enPrefix = (strncmp(ToCharPtr(lang), "en", 2) == 0); + if (enPrefix && (len == 2 || (len > 2 && aLangField[2] == '-'))) { + en = n; + break; + } + n++; + } + return en; +} + +static void +GetFaceNames(FcPattern* aFont, const nsAString& aFamilyName, + nsAString& aPostscriptName, nsAString& aFullname) +{ + // get the Postscript name + FcChar8* psname; + if (FcPatternGetString(aFont, FC_POSTSCRIPT_NAME, 0, &psname) == FcResultMatch) { + AppendUTF8toUTF16(ToCharPtr(psname), aPostscriptName); + } + + // get the canonical fullname (i.e. en name or first name) + uint32_t en = FindCanonicalNameIndex(aFont, FC_FULLNAMELANG); + FcChar8* fullname; + if (FcPatternGetString(aFont, FC_FULLNAME, en, &fullname) == FcResultMatch) { + AppendUTF8toUTF16(ToCharPtr(fullname), aFullname); + } + + // if have fullname, done + if (!aFullname.IsEmpty()) { + return; + } + + // otherwise, set the fullname to family + style name [en] and use that + aFullname.Append(aFamilyName); + + // figure out the en style name + en = FindCanonicalNameIndex(aFont, FC_STYLELANG); + nsAutoString style; + FcChar8* stylename = nullptr; + FcPatternGetString(aFont, FC_STYLE, en, &stylename); + if (stylename) { + AppendUTF8toUTF16(ToCharPtr(stylename), style); + } + + if (!style.IsEmpty() && !style.EqualsLiteral("Regular")) { + aFullname.Append(' '); + aFullname.Append(style); + } +} + +static uint16_t +MapFcWeight(int aFcWeight) +{ + if (aFcWeight <= (FC_WEIGHT_THIN + FC_WEIGHT_EXTRALIGHT) / 2) { + return 100; + } else if (aFcWeight <= (FC_WEIGHT_EXTRALIGHT + FC_WEIGHT_LIGHT) / 2) { + return 200; + } else if (aFcWeight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_BOOK) / 2) { + return 300; + } else if (aFcWeight <= (FC_WEIGHT_REGULAR + FC_WEIGHT_MEDIUM) / 2) { + // This includes FC_WEIGHT_BOOK + return 400; + } else if (aFcWeight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) { + return 500; + } else if (aFcWeight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) { + return 600; + } else if (aFcWeight <= (FC_WEIGHT_BOLD + FC_WEIGHT_EXTRABOLD) / 2) { + return 700; + } else if (aFcWeight <= (FC_WEIGHT_EXTRABOLD + FC_WEIGHT_BLACK) / 2) { + return 800; + } else if (aFcWeight <= FC_WEIGHT_BLACK) { + return 900; + } + + // including FC_WEIGHT_EXTRABLACK + return 901; +} + +static int16_t +MapFcWidth(int aFcWidth) +{ + if (aFcWidth <= (FC_WIDTH_ULTRACONDENSED + FC_WIDTH_EXTRACONDENSED) / 2) { + return NS_FONT_STRETCH_ULTRA_CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_EXTRACONDENSED + FC_WIDTH_CONDENSED) / 2) { + return NS_FONT_STRETCH_EXTRA_CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_CONDENSED + FC_WIDTH_SEMICONDENSED) / 2) { + return NS_FONT_STRETCH_CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_SEMICONDENSED + FC_WIDTH_NORMAL) / 2) { + return NS_FONT_STRETCH_SEMI_CONDENSED; + } + if (aFcWidth <= (FC_WIDTH_NORMAL + FC_WIDTH_SEMIEXPANDED) / 2) { + return NS_FONT_STRETCH_NORMAL; + } + if (aFcWidth <= (FC_WIDTH_SEMIEXPANDED + FC_WIDTH_EXPANDED) / 2) { + return NS_FONT_STRETCH_SEMI_EXPANDED; + } + if (aFcWidth <= (FC_WIDTH_EXPANDED + FC_WIDTH_EXTRAEXPANDED) / 2) { + return NS_FONT_STRETCH_EXPANDED; + } + if (aFcWidth <= (FC_WIDTH_EXTRAEXPANDED + FC_WIDTH_ULTRAEXPANDED) / 2) { + return NS_FONT_STRETCH_EXTRA_EXPANDED; + } + return NS_FONT_STRETCH_ULTRA_EXPANDED; +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName, + FcPattern* aFontPattern, + bool aIgnoreFcCharmap) + : gfxFontEntry(aFaceName), mFontPattern(aFontPattern), + mFTFace(nullptr), mFTFaceInitialized(false), + mIgnoreFcCharmap(aIgnoreFcCharmap), + mAspect(0.0), mFontData(nullptr) +{ + // italic + int slant; + if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) { + slant = FC_SLANT_ROMAN; + } + if (slant == FC_SLANT_OBLIQUE) { + mStyle = NS_FONT_STYLE_OBLIQUE; + } else if (slant > 0) { + mStyle = NS_FONT_STYLE_ITALIC; + } + + // weight + int weight; + if (FcPatternGetInteger(aFontPattern, FC_WEIGHT, 0, &weight) != FcResultMatch) { + weight = FC_WEIGHT_REGULAR; + } + mWeight = MapFcWeight(weight); + + // width + int width; + if (FcPatternGetInteger(aFontPattern, FC_WIDTH, 0, &width) != FcResultMatch) { + width = FC_WIDTH_NORMAL; + } + mStretch = MapFcWidth(width); +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle, + const uint8_t *aData, + FT_Face aFace) + : gfxFontEntry(aFaceName), + mFTFace(aFace), mFTFaceInitialized(true), + mIgnoreFcCharmap(true), + mAspect(0.0), mFontData(aData) +{ + mWeight = aWeight; + mStyle = aStyle; + mStretch = aStretch; + mIsDataUserFont = true; + + // Use fontconfig to fill out the pattern from the FTFace. + // The "file" argument cannot be nullptr (in fontconfig-2.6.0 at + // least). The dummy file passed here is removed below. + // + // When fontconfig scans the system fonts, FcConfigGetBlanks(nullptr) + // is passed as the "blanks" argument, which provides that unexpectedly + // blank glyphs are elided. Here, however, we pass nullptr for + // "blanks", effectively assuming that, if the font has a blank glyph, + // then the author intends any associated character to be rendered + // blank. + mFontPattern = FcFreeTypeQueryFace(mFTFace, ToFcChar8Ptr(""), 0, nullptr); + // given that we have a FT_Face, not really sure this is possible... + if (!mFontPattern) { + mFontPattern = FcPatternCreate(); + } + FcPatternDel(mFontPattern, FC_FILE); + FcPatternDel(mFontPattern, FC_INDEX); + + // Make a new pattern and store the face in it so that cairo uses + // that when creating a cairo font face. + FcPatternAddFTFace(mFontPattern, FC_FT_FACE, mFTFace); + + mUserFontData = new FTUserFontData(mFTFace, mFontData); +} + +gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName, + FcPattern* aFontPattern, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle) + : gfxFontEntry(aFaceName), mFontPattern(aFontPattern), + mFTFace(nullptr), mFTFaceInitialized(false), + mAspect(0.0), mFontData(nullptr) +{ + mWeight = aWeight; + mStyle = aStyle; + mStretch = aStretch; + mIsLocalUserFont = true; + + // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded + // via src:local()... + // If the local font happens to come from the application fontset, + // we want to set it to true so that color/svg fonts will work even + // if the default glyphs are blank; but if the local font is a non- + // sfnt face (e.g. legacy type 1) then we need to set it to false + // because our cmap-reading code will fail and we depend on FT+Fc to + // determine the coverage. + // We set the flag here, but may flip it the first time TestCharacterMap + // is called, at which point we'll look to see whether a 'cmap' is + // actually present in the font. + mIgnoreFcCharmap = true; +} + +gfxFontconfigFontEntry::~gfxFontconfigFontEntry() +{ +} + +static bool +PatternHasLang(const FcPattern *aPattern, const FcChar8 *aLang) +{ + FcLangSet *langset; + + if (FcPatternGetLangSet(aPattern, FC_LANG, 0, &langset) != FcResultMatch) { + return false; + } + + if (FcLangSetHasLang(langset, aLang) != FcLangDifferentLang) { + return true; + } + return false; +} + +bool +gfxFontconfigFontEntry::SupportsLangGroup(nsIAtom *aLangGroup) const +{ + if (!aLangGroup || aLangGroup == nsGkAtoms::Unicode) { + return true; + } + + nsAutoCString fcLang; + gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); + pfl->GetSampleLangForGroup(aLangGroup, fcLang); + if (fcLang.IsEmpty()) { + return true; + } + + // is lang included in the underlying pattern? + return PatternHasLang(mFontPattern, ToFcChar8Ptr(fcLang.get())); +} + +nsresult +gfxFontconfigFontEntry::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; + } + } + + 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; +} + +static bool +HasChar(FcPattern *aFont, FcChar32 aCh) +{ + FcCharSet *charset = nullptr; + FcPatternGetCharSet(aFont, FC_CHARSET, 0, &charset); + return charset && FcCharSetHasChar(charset, aCh); +} + +bool +gfxFontconfigFontEntry::TestCharacterMap(uint32_t aCh) +{ + // For user fonts, or for fonts bundled with the app (which might include + // color/svg glyphs where the default glyphs may be blank, and thus confuse + // fontconfig/freetype's char map checking), we instead check the cmap + // directly for character coverage. + if (mIgnoreFcCharmap) { + // If it does not actually have a cmap, switch our strategy to use + // fontconfig's charmap after all (except for data fonts, which must + // always have a cmap to have passed OTS validation). + if (!mIsDataUserFont && !HasFontTable(TRUETYPE_TAG('c','m','a','p'))) { + mIgnoreFcCharmap = false; + // ...and continue with HasChar() below. + } else { + return gfxFontEntry::TestCharacterMap(aCh); + } + } + // otherwise (for system fonts), use the charmap in the pattern + return HasChar(mFontPattern, aCh); +} + +hb_blob_t* +gfxFontconfigFontEntry::GetFontTable(uint32_t aTableTag) +{ + // for data fonts, read directly from the font data + if (mFontData) { + return gfxFontUtils::GetTableFromFontData(mFontData, aTableTag); + } + + return gfxFontEntry::GetFontTable(aTableTag); +} + +void +gfxFontconfigFontEntry::MaybeReleaseFTFace() +{ + // don't release if either HB or Gr face still exists + if (mHBFace || mGrFace) { + return; + } + // only close out FT_Face for system fonts, not for data fonts + if (!mIsDataUserFont) { + if (mFTFace) { + FT_Done_Face(mFTFace); + mFTFace = nullptr; + } + mFTFaceInitialized = false; + } +} + +void +gfxFontconfigFontEntry::ForgetHBFace() +{ + gfxFontEntry::ForgetHBFace(); + MaybeReleaseFTFace(); +} + +void +gfxFontconfigFontEntry::ReleaseGrFace(gr_face* aFace) +{ + gfxFontEntry::ReleaseGrFace(aFace); + MaybeReleaseFTFace(); +} + +double +gfxFontconfigFontEntry::GetAspect() +{ + if (mAspect == 0.0) { + // default to aspect = 0.5 + mAspect = 0.5; + + // create a font to calculate x-height / em-height + gfxFontStyle s; + s.size = 100.0; // pick large size to avoid possible hinting artifacts + RefPtr<gfxFont> font = FindOrMakeFont(&s, false); + if (font) { + const gfxFont::Metrics& metrics = + font->GetMetrics(gfxFont::eHorizontal); + + // The factor of 0.1 ensures that xHeight is sane so fonts don't + // become huge. Strictly ">" ensures that xHeight and emHeight are + // not both zero. + if (metrics.xHeight > 0.1 * metrics.emHeight) { + mAspect = metrics.xHeight / metrics.emHeight; + } + } + } + return mAspect; +} + +static void +PrepareFontOptions(FcPattern* aPattern, + cairo_font_options_t* aFontOptions) +{ + NS_ASSERTION(aFontOptions, "null font options passed to PrepareFontOptions"); + + // xxx - taken from the gfxFontconfigFonts code, needs to be reviewed + + FcBool printing; + if (FcPatternGetBool(aPattern, PRINTING_FC_PROPERTY, 0, &printing) != + FcResultMatch) { + printing = FcFalse; + } + + // Font options are set explicitly here to improve cairo's caching + // behavior and to record the relevant parts of the pattern for + // SetupCairoFont (so that the pattern can be released). + // + // Most font_options have already been set as defaults on the FcPattern + // with cairo_ft_font_options_substitute(), then user and system + // fontconfig configurations were applied. The resulting font_options + // have been recorded on the face during + // cairo_ft_font_face_create_for_pattern(). + // + // None of the settings here cause this scaled_font to behave any + // differently from how it would behave if it were created from the same + // face with default font_options. + // + // We set options explicitly so that the same scaled_font will be found in + // the cairo_scaled_font_map when cairo loads glyphs from a context with + // the same font_face, font_matrix, ctm, and surface font_options. + // + // Unfortunately, _cairo_scaled_font_keys_equal doesn't know about the + // font_options on the cairo_ft_font_face, and doesn't consider default + // option values to not match any explicit values. + // + // Even after cairo_set_scaled_font is used to set font_options for the + // cairo context, when cairo looks for a scaled_font for the context, it + // will look for a font with some option values from the target surface if + // any values are left default on the context font_options. If this + // scaled_font is created with default font_options, cairo will not find + // it. + // + // The one option not recorded in the pattern is hint_metrics, which will + // affect glyph metrics. The default behaves as CAIRO_HINT_METRICS_ON. + // We should be considering the font_options of the surface on which this + // font will be used, but currently we don't have different gfxFonts for + // different surface font_options, so we'll create a font suitable for the + // Screen. Image and xlib surfaces default to CAIRO_HINT_METRICS_ON. + if (printing) { + cairo_font_options_set_hint_metrics(aFontOptions, CAIRO_HINT_METRICS_OFF); + } else { + cairo_font_options_set_hint_metrics(aFontOptions, CAIRO_HINT_METRICS_ON); + } + + // The remaining options have been recorded on the pattern and the face. + // _cairo_ft_options_merge has some logic to decide which options from the + // scaled_font or from the cairo_ft_font_face take priority in the way the + // font behaves. + // + // In the majority of cases, _cairo_ft_options_merge uses the options from + // the cairo_ft_font_face, so sometimes it is not so important which + // values are set here so long as they are not defaults, but we'll set + // them to the exact values that we expect from the font, to be consistent + // and to protect against changes in cairo. + // + // In some cases, _cairo_ft_options_merge uses some options from the + // scaled_font's font_options rather than options on the + // cairo_ft_font_face (from fontconfig). + // https://bugs.freedesktop.org/show_bug.cgi?id=11838 + // + // Surface font options were set on the pattern in + // cairo_ft_font_options_substitute. If fontconfig has changed the + // hint_style then that is what the user (or distribution) wants, so we + // use the setting from the FcPattern. + // + // Fallback values here mirror treatment of defaults in cairo-ft-font.c. + FcBool hinting = FcFalse; + if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch) { + hinting = FcTrue; + } + + cairo_hint_style_t hint_style; + if (printing || !hinting) { + hint_style = CAIRO_HINT_STYLE_NONE; + } else { + int fc_hintstyle; + if (FcPatternGetInteger(aPattern, FC_HINT_STYLE, + 0, &fc_hintstyle) != FcResultMatch) { + fc_hintstyle = FC_HINT_FULL; + } + switch (fc_hintstyle) { + case FC_HINT_NONE: + hint_style = CAIRO_HINT_STYLE_NONE; + break; + case FC_HINT_SLIGHT: + hint_style = CAIRO_HINT_STYLE_SLIGHT; + break; + case FC_HINT_MEDIUM: + default: // This fallback mirrors _get_pattern_ft_options in cairo. + hint_style = CAIRO_HINT_STYLE_MEDIUM; + break; + case FC_HINT_FULL: + hint_style = CAIRO_HINT_STYLE_FULL; + break; + } + } + cairo_font_options_set_hint_style(aFontOptions, hint_style); + + int rgba; + if (FcPatternGetInteger(aPattern, + FC_RGBA, 0, &rgba) != FcResultMatch) { + rgba = FC_RGBA_UNKNOWN; + } + cairo_subpixel_order_t subpixel_order = CAIRO_SUBPIXEL_ORDER_DEFAULT; + switch (rgba) { + case FC_RGBA_UNKNOWN: + case FC_RGBA_NONE: + default: + // There is no CAIRO_SUBPIXEL_ORDER_NONE. Subpixel antialiasing + // is disabled through cairo_antialias_t. + rgba = FC_RGBA_NONE; + // subpixel_order won't be used by the font as we won't use + // CAIRO_ANTIALIAS_SUBPIXEL, but don't leave it at default for + // caching reasons described above. Fall through: + MOZ_FALLTHROUGH; + case FC_RGBA_RGB: + subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB; + break; + case FC_RGBA_BGR: + subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR; + break; + case FC_RGBA_VRGB: + subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB; + break; + case FC_RGBA_VBGR: + subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR; + break; + } + cairo_font_options_set_subpixel_order(aFontOptions, subpixel_order); + + FcBool fc_antialias; + if (FcPatternGetBool(aPattern, + FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch) { + fc_antialias = FcTrue; + } + cairo_antialias_t antialias; + if (!fc_antialias) { + antialias = CAIRO_ANTIALIAS_NONE; + } else if (rgba == FC_RGBA_NONE) { + antialias = CAIRO_ANTIALIAS_GRAY; + } else { + antialias = CAIRO_ANTIALIAS_SUBPIXEL; + } + cairo_font_options_set_antialias(aFontOptions, antialias); +} + +cairo_scaled_font_t* +gfxFontconfigFontEntry::CreateScaledFont(FcPattern* aRenderPattern, + gfxFloat aAdjustedSize, + const gfxFontStyle *aStyle, + bool aNeedsBold) +{ + if (aNeedsBold) { + FcPatternAddBool(aRenderPattern, FC_EMBOLDEN, FcTrue); + } + + // synthetic oblique by skewing via the font matrix + bool needsOblique = IsUpright() && + aStyle->style != NS_FONT_STYLE_NORMAL && + aStyle->allowSyntheticStyle; + + if (needsOblique) { + // disable embedded bitmaps (mimics behavior in 90-synthetic.conf) + FcPatternDel(aRenderPattern, FC_EMBEDDED_BITMAP); + FcPatternAddBool(aRenderPattern, FC_EMBEDDED_BITMAP, FcFalse); + } + + cairo_font_face_t *face = + cairo_ft_font_face_create_for_pattern(aRenderPattern); + + if (mFontData) { + // for data fonts, add the face/data pointer to the cairo font face + // so that it gets deleted whenever cairo decides + NS_ASSERTION(mFTFace, "FT_Face is null when setting user data"); + NS_ASSERTION(mUserFontData, "user font data is null when setting user data"); + cairo_font_face_set_user_data(face, + &sFcFontlistUserFontDataKey, + new FTUserFontDataRef(mUserFontData), + FTUserFontDataRef::Destroy); + } + + cairo_scaled_font_t *scaledFont = nullptr; + + cairo_matrix_t sizeMatrix; + cairo_matrix_t identityMatrix; + + cairo_matrix_init_scale(&sizeMatrix, aAdjustedSize, aAdjustedSize); + cairo_matrix_init_identity(&identityMatrix); + + if (needsOblique) { + const double kSkewFactor = OBLIQUE_SKEW_FACTOR; + + cairo_matrix_t style; + cairo_matrix_init(&style, + 1, //xx + 0, //yx + -1 * kSkewFactor, //xy + 1, //yy + 0, //x0 + 0); //y0 + cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style); + } + + cairo_font_options_t *fontOptions = cairo_font_options_create(); + PrepareFontOptions(aRenderPattern, fontOptions); + + scaledFont = cairo_scaled_font_create(face, &sizeMatrix, + &identityMatrix, fontOptions); + cairo_font_options_destroy(fontOptions); + + NS_ASSERTION(cairo_scaled_font_status(scaledFont) == CAIRO_STATUS_SUCCESS, + "Failed to make scaled font"); + + cairo_font_face_destroy(face); + + return scaledFont; +} + +#ifdef MOZ_WIDGET_GTK +// defintion included below +static void ApplyGdkScreenFontOptions(FcPattern *aPattern); +#endif + +#ifdef MOZ_X11 +static bool +GetXftInt(Display* aDisplay, const char* aName, int* aResult) +{ + if (!aDisplay) { + return false; + } + char* value = XGetDefault(aDisplay, "Xft", aName); + if (!value) { + return false; + } + if (FcNameConstant(const_cast<FcChar8*>(ToFcChar8Ptr(value)), aResult)) { + return true; + } + char* end; + *aResult = strtol(value, &end, 0); + if (end != value) { + return true; + } + return false; +} +#endif + +static void +PreparePattern(FcPattern* aPattern, bool aIsPrinterFont) +{ + FcConfigSubstitute(nullptr, aPattern, FcMatchPattern); + + // This gets cairo_font_options_t for the Screen. We should have + // different font options for printing (no hinting) but we are not told + // what we are measuring for. + // + // If cairo adds support for lcd_filter, gdk will not provide the default + // setting for that option. We could get the default setting by creating + // an xlib surface once, recording its font_options, and then merging the + // gdk options. + // + // Using an xlib surface would also be an option to get Screen font + // options for non-GTK X11 toolkits, but less efficient than using GDK to + // pick up dynamic changes. + if(aIsPrinterFont) { + cairo_font_options_t *options = cairo_font_options_create(); + cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE); + cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_GRAY); + cairo_ft_font_options_substitute(options, aPattern); + cairo_font_options_destroy(options); + FcPatternAddBool(aPattern, PRINTING_FC_PROPERTY, FcTrue); + } else { +#ifdef MOZ_WIDGET_GTK + ApplyGdkScreenFontOptions(aPattern); +#endif + +#ifdef MOZ_X11 + FcValue value; + int lcdfilter; + if (FcPatternGet(aPattern, FC_LCD_FILTER, 0, &value) + == FcResultNoMatch && + GetXftInt(DefaultXDisplay(), "lcdfilter", &lcdfilter)) { + FcPatternAddInteger(aPattern, FC_LCD_FILTER, lcdfilter); + } +#endif + } + + FcDefaultSubstitute(aPattern); +} + +static inline gfxFloat +SizeForStyle(gfxFontconfigFontEntry* aEntry, const gfxFontStyle& aStyle) +{ + return aStyle.sizeAdjust >= 0.0 ? + aStyle.GetAdjustedSize(aEntry->GetAspect()) : + aStyle.size; +} + +static double +ChooseFontSize(gfxFontconfigFontEntry* aEntry, + const gfxFontStyle& aStyle) +{ + double requestedSize = SizeForStyle(aEntry, aStyle); + double bestDist = -1.0; + double bestSize = requestedSize; + double size; + int v = 0; + while (FcPatternGetDouble(aEntry->GetPattern(), + FC_PIXEL_SIZE, v, &size) == FcResultMatch) { + ++v; + double dist = fabs(size - requestedSize); + if (bestDist < 0.0 || dist < bestDist) { + bestDist = dist; + bestSize = size; + } + } + return bestSize; +} + +gfxFont* +gfxFontconfigFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, + bool aNeedsBold) +{ + nsAutoRef<FcPattern> pattern(FcPatternCreate()); + if (!pattern) { + NS_WARNING("Failed to create Fontconfig pattern for font instance"); + return nullptr; + } + + double size = ChooseFontSize(this, *aFontStyle); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size); + + PreparePattern(pattern, aFontStyle->printerFont); + nsAutoRef<FcPattern> renderPattern + (FcFontRenderPrepare(nullptr, pattern, mFontPattern)); + if (!renderPattern) { + NS_WARNING("Failed to prepare Fontconfig pattern for font instance"); + return nullptr; + } + + cairo_scaled_font_t* scaledFont = + CreateScaledFont(renderPattern, size, aFontStyle, aNeedsBold); + gfxFont* newFont = + new gfxFontconfigFont(scaledFont, renderPattern, size, + this, aFontStyle, aNeedsBold); + cairo_scaled_font_destroy(scaledFont); + + return newFont; +} + +nsresult +gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag, + nsTArray<uint8_t>& aBuffer) +{ + NS_ASSERTION(!mIsDataUserFont, + "data fonts should be reading tables directly from memory"); + + if (!mFTFaceInitialized) { + mFTFaceInitialized = true; + FcChar8 *filename; + if (FcPatternGetString(mFontPattern, FC_FILE, 0, &filename) != FcResultMatch) { + return NS_ERROR_FAILURE; + } + int index; + if (FcPatternGetInteger(mFontPattern, FC_INDEX, 0, &index) != FcResultMatch) { + index = 0; // default to 0 if not found in pattern + } + if (FT_New_Face(gfxFcPlatformFontList::GetFTLibrary(), + (const char*)filename, index, &mFTFace) != 0) { + return NS_ERROR_FAILURE; + } + } + + if (!mFTFace) { + return NS_ERROR_NOT_AVAILABLE; + } + + FT_ULong length = 0; + if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, nullptr, &length) != 0) { + return NS_ERROR_NOT_AVAILABLE; + } + if (!aBuffer.SetLength(length, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, aBuffer.Elements(), &length) != 0) { + aBuffer.Clear(); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +gfxFontconfigFontFamily::FindStyleVariations(FontInfoData *aFontInfoData) +{ + if (mHasStyles) { + return; + } + + // add font entries for each of the faces + uint32_t numFonts = mFontPatterns.Length(); + NS_ASSERTION(numFonts, "font family containing no faces!!"); + uint32_t numRegularFaces = 0; + for (uint32_t i = 0; i < numFonts; i++) { + FcPattern* face = mFontPatterns[i]; + + // figure out the psname/fullname and choose which to use as the facename + nsAutoString psname, fullname; + GetFaceNames(face, mName, psname, fullname); + const nsAutoString& faceName = !psname.IsEmpty() ? psname : fullname; + + gfxFontconfigFontEntry *fontEntry = + new gfxFontconfigFontEntry(faceName, face, mContainsAppFonts); + AddFontEntry(fontEntry); + + if (fontEntry->IsUpright() && + fontEntry->Weight() == NS_FONT_WEIGHT_NORMAL && + fontEntry->Stretch() == NS_FONT_STRETCH_NORMAL) { + numRegularFaces++; + } + + if (LOG_FONTLIST_ENABLED()) { + LOG_FONTLIST(("(fontlist) added (%s) to family (%s)" + " with style: %s weight: %d stretch: %d" + " psname: %s fullname: %s", + NS_ConvertUTF16toUTF8(fontEntry->Name()).get(), + NS_ConvertUTF16toUTF8(Name()).get(), + (fontEntry->IsItalic()) ? + "italic" : (fontEntry->IsOblique() ? "oblique" : "normal"), + fontEntry->Weight(), fontEntry->Stretch(), + NS_ConvertUTF16toUTF8(psname).get(), + NS_ConvertUTF16toUTF8(fullname).get())); + } + } + + // somewhat arbitrary, but define a family with two or more regular + // faces as a family for which intra-family fallback should be used + if (numRegularFaces > 1) { + mCheckForFallbackFaces = true; + } + mFaceNamesInitialized = true; + mFontPatterns.Clear(); + SetHasStyles(true); +} + +void +gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern) +{ + NS_ASSERTION(!mHasStyles, + "font patterns must not be added to already enumerated families"); + + FcBool scalable; + if (FcPatternGetBool(aFontPattern, FC_SCALABLE, 0, &scalable) != FcResultMatch || + !scalable) { + mHasNonScalableFaces = true; + } + + nsCountedRef<FcPattern> pattern(aFontPattern); + mFontPatterns.AppendElement(pattern); +} + +static const double kRejectDistance = 10000.0; + +// Calculate a distance score representing the size disparity between the +// requested style's size and the font entry's size. +static double +SizeDistance(gfxFontconfigFontEntry* aEntry, const gfxFontStyle& aStyle) +{ + double requestedSize = SizeForStyle(aEntry, aStyle); + double bestDist = -1.0; + double size; + int v = 0; + while (FcPatternGetDouble(aEntry->GetPattern(), + FC_PIXEL_SIZE, v, &size) == FcResultMatch) { + ++v; + double dist = fabs(size - requestedSize); + if (bestDist < 0.0 || dist < bestDist) { + bestDist = dist; + } + } + if (bestDist < 0.0) { + // No size means scalable + return -1.0; + } else if (5.0 * bestDist < requestedSize) { + // fontconfig prefers a matching family or lang to pixelsize of bitmap + // fonts. CSS suggests a tolerance of 20% on pixelsize. + return bestDist; + } else { + // Reject any non-scalable fonts that are not within tolerance. + return kRejectDistance; + } +} + +void +gfxFontconfigFontFamily::FindAllFontsForStyle(const gfxFontStyle& aFontStyle, + nsTArray<gfxFontEntry*>& aFontEntryList, + bool& aNeedsSyntheticBold) +{ + gfxFontFamily::FindAllFontsForStyle(aFontStyle, + aFontEntryList, + aNeedsSyntheticBold); + + if (!mHasNonScalableFaces) { + return; + } + + // Iterate over the the available fonts while compacting any groups + // of unscalable fonts with matching styles into a single entry + // corresponding to the closest available size. If the closest + // available size is rejected for being outside tolernace, then the + // entire group will be skipped. + size_t skipped = 0; + gfxFontconfigFontEntry* bestEntry = nullptr; + double bestDist = -1.0; + for (size_t i = 0; i < aFontEntryList.Length(); i++) { + gfxFontconfigFontEntry* entry = + static_cast<gfxFontconfigFontEntry*>(aFontEntryList[i]); + double dist = SizeDistance(entry, aFontStyle); + // If the entry is scalable or has a style that does not match + // the group of unscalable fonts, then start a new group. + if (dist < 0.0 || + !bestEntry || + bestEntry->Stretch() != entry->Stretch() || + bestEntry->Weight() != entry->Weight() || + bestEntry->mStyle != entry->mStyle) { + // If the best entry in this group is still outside the tolerance, + // then skip the entire group. + if (bestDist >= kRejectDistance) { + skipped++; + } + // Remove any compacted entries from the previous group. + if (skipped) { + i -= skipped; + aFontEntryList.RemoveElementsAt(i, skipped); + skipped = 0; + } + // Mark the start of the new group. + bestEntry = entry; + bestDist = dist; + } else { + // If this entry more closely matches the requested size than the + // current best in the group, then take this entry instead. + if (dist < bestDist) { + aFontEntryList[i-1-skipped] = entry; + bestEntry = entry; + bestDist = dist; + } + skipped++; + } + } + // If the best entry in this group is still outside the tolerance, + // then skip the entire group. + if (bestDist >= kRejectDistance) { + skipped++; + } + // Remove any compacted entries from the current group. + if (skipped) { + aFontEntryList.TruncateLength(aFontEntryList.Length() - skipped); + } +} + +gfxFontconfigFont::gfxFontconfigFont(cairo_scaled_font_t *aScaledFont, + FcPattern *aPattern, + gfxFloat aAdjustedSize, + gfxFontEntry *aFontEntry, + const gfxFontStyle *aFontStyle, + bool aNeedsBold) : + gfxFontconfigFontBase(aScaledFont, aPattern, aFontEntry, aFontStyle) +{ + mAdjustedSize = aAdjustedSize; +} + +gfxFontconfigFont::~gfxFontconfigFont() +{ +} + +gfxFcPlatformFontList::gfxFcPlatformFontList() + : mLocalNames(64) + , mGenericMappings(32) + , mFcSubstituteCache(64) + , mLastConfig(nullptr) + , mAlwaysUseFontconfigGenerics(true) +{ + // if the rescan interval is set, start the timer + int rescanInterval = FcConfigGetRescanInterval(nullptr); + if (rescanInterval) { + mLastConfig = FcConfigGetCurrent(); + mCheckFontUpdatesTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mCheckFontUpdatesTimer) { + mCheckFontUpdatesTimer-> + InitWithFuncCallback(CheckFontUpdates, this, + (rescanInterval + 1) * 1000, + nsITimer::TYPE_REPEATING_SLACK); + } else { + NS_WARNING("Failure to create font updates timer"); + } + } + +#ifdef MOZ_BUNDLED_FONTS + mBundledFontsInitialized = false; +#endif +} + +gfxFcPlatformFontList::~gfxFcPlatformFontList() +{ + if (mCheckFontUpdatesTimer) { + mCheckFontUpdatesTimer->Cancel(); + mCheckFontUpdatesTimer = nullptr; + } +} + +void +gfxFcPlatformFontList::AddFontSetFamilies(FcFontSet* aFontSet, bool aAppFonts) +{ + // This iterates over the fonts in a font set and adds in gfxFontFamily + // objects for each family. The patterns for individual fonts are not + // copied here. When a family is actually used, the fonts in the family + // are enumerated and the patterns copied. Note that we're explicitly + // excluding non-scalable fonts such as X11 bitmap fonts, which + // Chrome Skia/Webkit code does also. + + if (!aFontSet) { + NS_WARNING("AddFontSetFamilies called with a null font set."); + return; + } + + FcChar8* lastFamilyName = (FcChar8*)""; + RefPtr<gfxFontconfigFontFamily> fontFamily; + nsAutoString familyName; + for (int f = 0; f < aFontSet->nfont; f++) { + FcPattern* font = aFontSet->fonts[f]; + + // get canonical name + uint32_t cIndex = FindCanonicalNameIndex(font, FC_FAMILYLANG); + FcChar8* canonical = nullptr; + FcPatternGetString(font, FC_FAMILY, cIndex, &canonical); + if (!canonical) { + continue; + } + + // same as the last one? no need to add a new family, skip + if (FcStrCmp(canonical, lastFamilyName) != 0) { + lastFamilyName = canonical; + + // add new family if one doesn't already exist + familyName.Truncate(); + AppendUTF8toUTF16(ToCharPtr(canonical), familyName); + nsAutoString keyName(familyName); + ToLowerCase(keyName); + + fontFamily = static_cast<gfxFontconfigFontFamily*> + (mFontFamilies.GetWeak(keyName)); + if (!fontFamily) { + fontFamily = new gfxFontconfigFontFamily(familyName); + mFontFamilies.Put(keyName, fontFamily); + } + // Record if the family contains fonts from the app font set + // (in which case we won't rely on fontconfig's charmap, due to + // bug 1276594). + if (aAppFonts) { + fontFamily->SetFamilyContainsAppFonts(true); + } + + // Add pointers to other localized family names. Most fonts + // only have a single name, so the first call to GetString + // will usually not match + FcChar8* otherName; + int n = (cIndex == 0 ? 1 : 0); + while (FcPatternGetString(font, FC_FAMILY, n, &otherName) == FcResultMatch) { + NS_ConvertUTF8toUTF16 otherFamilyName(ToCharPtr(otherName)); + AddOtherFamilyName(fontFamily, otherFamilyName); + n++; + if (n == int(cIndex)) { + n++; // skip over canonical name + } + } + } + + NS_ASSERTION(fontFamily, "font must belong to a font family"); + fontFamily->AddFontPattern(font); + + // map the psname, fullname ==> font family for local font lookups + nsAutoString psname, fullname; + GetFaceNames(font, familyName, psname, fullname); + if (!psname.IsEmpty()) { + ToLowerCase(psname); + mLocalNames.Put(psname, font); + } + if (!fullname.IsEmpty()) { + ToLowerCase(fullname); + mLocalNames.Put(fullname, font); + } + } +} + +nsresult +gfxFcPlatformFontList::InitFontListForPlatform() +{ + mLastConfig = FcConfigGetCurrent(); + + mLocalNames.Clear(); + mFcSubstituteCache.Clear(); + + // iterate over available fonts + FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem); + AddFontSetFamilies(systemFonts, /* aAppFonts = */ false); + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); + +#ifdef MOZ_BUNDLED_FONTS + ActivateBundledFonts(); + FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication); + AddFontSetFamilies(appFonts, /* aAppFonts = */ true); +#endif + + mOtherFamilyNamesInitialized = true; + + return NS_OK; +} + +// For displaying the fontlist in UI, use explicit call to FcFontList. Using +// FcFontList results in the list containing the localized names as dictated +// by system defaults. +static void +GetSystemFontList(nsTArray<nsString>& aListOfFonts, nsIAtom *aLangGroup) +{ + aListOfFonts.Clear(); + + nsAutoRef<FcPattern> pat(FcPatternCreate()); + if (!pat) { + return; + } + + nsAutoRef<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr)); + if (!os) { + return; + } + + // add the lang to the pattern + nsAutoCString fcLang; + gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList(); + pfl->GetSampleLangForGroup(aLangGroup, fcLang); + if (!fcLang.IsEmpty()) { + FcPatternAddString(pat, FC_LANG, ToFcChar8Ptr(fcLang.get())); + } + + nsAutoRef<FcFontSet> fs(FcFontList(nullptr, pat, os)); + if (!fs) { + return; + } + + for (int i = 0; i < fs->nfont; i++) { + char *family; + + if (FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, + (FcChar8 **) &family) != FcResultMatch) + { + continue; + } + + // Remove duplicates... + nsAutoString strFamily; + AppendUTF8toUTF16(family, strFamily); + if (aListOfFonts.Contains(strFamily)) { + continue; + } + + aListOfFonts.AppendElement(strFamily); + } + + aListOfFonts.Sort(); +} + +void +gfxFcPlatformFontList::GetFontList(nsIAtom *aLangGroup, + const nsACString& aGenericFamily, + nsTArray<nsString>& aListOfFonts) +{ + // Get the list of font family names using fontconfig + GetSystemFontList(aListOfFonts, aLangGroup); + + // Under Linux, the generics "serif", "sans-serif" and "monospace" + // are included in the pref fontlist. These map to whatever fontconfig + // decides they should be for a given language, rather than one of the + // fonts listed in the prefs font lists (e.g. font.name.*, font.name-list.*) + bool serif = false, sansSerif = false, monospace = false; + if (aGenericFamily.IsEmpty()) + serif = sansSerif = monospace = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("serif")) + serif = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("sans-serif")) + sansSerif = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("monospace")) + monospace = true; + else if (aGenericFamily.LowerCaseEqualsLiteral("cursive") || + aGenericFamily.LowerCaseEqualsLiteral("fantasy")) + serif = sansSerif = true; + else + NS_NOTREACHED("unexpected CSS generic font family"); + + // The first in the list becomes the default in + // FontBuilder.readFontSelection() if the preference-selected font is not + // available, so put system configured defaults first. + if (monospace) + aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("monospace")); + if (sansSerif) + aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("sans-serif")); + if (serif) + aListOfFonts.InsertElementAt(0, NS_LITERAL_STRING("serif")); +} + +gfxFontFamily* +gfxFcPlatformFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle) +{ + // Get the default font by using a fake name to retrieve the first + // scalable font that fontconfig suggests for the given language. + PrefFontList* prefFonts = + FindGenericFamilies(NS_LITERAL_STRING("-moz-default"), aStyle->language); + NS_ASSERTION(prefFonts, "null list of generic fonts"); + if (prefFonts && !prefFonts->IsEmpty()) { + return (*prefFonts)[0]; + } + return nullptr; +} + +gfxFontEntry* +gfxFcPlatformFontList::LookupLocalFont(const nsAString& aFontName, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle) +{ + nsAutoString keyName(aFontName); + ToLowerCase(keyName); + + // if name is not in the global list, done + FcPattern* fontPattern = mLocalNames.Get(keyName); + if (!fontPattern) { + return nullptr; + } + + return new gfxFontconfigFontEntry(aFontName, fontPattern, + aWeight, aStretch, aStyle); +} + +gfxFontEntry* +gfxFcPlatformFontList::MakePlatformFont(const nsAString& aFontName, + uint16_t aWeight, + int16_t aStretch, + uint8_t aStyle, + const uint8_t* aFontData, + uint32_t aLength) +{ + FT_Face face; + FT_Error error = + FT_New_Memory_Face(gfxFcPlatformFontList::GetFTLibrary(), + aFontData, aLength, 0, &face); + if (error != FT_Err_Ok) { + NS_Free((void*)aFontData); + return nullptr; + } + if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) { + FT_Done_Face(face); + NS_Free((void*)aFontData); + return nullptr; + } + + return new gfxFontconfigFontEntry(aFontName, aWeight, aStretch, + aStyle, aFontData, face); +} + +bool +gfxFcPlatformFontList::FindAndAddFamilies(const nsAString& aFamily, + nsTArray<gfxFontFamily*>* aOutput, + gfxFontStyle* aStyle, + gfxFloat aDevToCssSize) +{ + nsAutoString familyName(aFamily); + ToLowerCase(familyName); + nsIAtom* language = (aStyle ? aStyle->language.get() : nullptr); + + // deprecated generic names are explicitly converted to standard generics + bool isDeprecatedGeneric = false; + if (familyName.EqualsLiteral("sans") || + familyName.EqualsLiteral("sans serif")) { + familyName.AssignLiteral("sans-serif"); + isDeprecatedGeneric = true; + } else if (familyName.EqualsLiteral("mono")) { + familyName.AssignLiteral("monospace"); + isDeprecatedGeneric = true; + } + + // fontconfig generics? use fontconfig to determine the family for lang + if (isDeprecatedGeneric || + mozilla::FontFamilyName::Convert(familyName).IsGeneric()) { + PrefFontList* prefFonts = FindGenericFamilies(familyName, language); + if (prefFonts && !prefFonts->IsEmpty()) { + aOutput->AppendElements(*prefFonts); + return true; + } + return false; + } + + // fontconfig allows conditional substitutions in such a way that it's + // difficult to distinguish an explicit substitution from other suggested + // choices. To sniff out explicit substitutions, compare the substitutions + // for "font, -moz-sentinel" to "-moz-sentinel" to sniff out the + // substitutions + // + // Example: + // + // serif ==> DejaVu Serif, ... + // Helvetica, serif ==> Helvetica, TeX Gyre Heros, Nimbus Sans L, DejaVu Serif + // + // In this case fontconfig is including Tex Gyre Heros and + // Nimbus Sans L as alternatives for Helvetica. + + // Because the FcConfigSubstitute call is quite expensive, we cache the + // actual font families found via this process. So check the cache first: + NS_ConvertUTF16toUTF8 familyToFind(familyName); + AutoTArray<gfxFontFamily*,10> cachedFamilies; + if (mFcSubstituteCache.Get(familyToFind, &cachedFamilies)) { + if (cachedFamilies.IsEmpty()) { + return false; + } + aOutput->AppendElements(cachedFamilies); + return true; + } + + // It wasn't in the cache, so we need to ask fontconfig... + const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel"); + FcChar8* sentinelFirstFamily = nullptr; + nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate()); + FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName); + FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern); + FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily); + + // substitutions for font, -moz-sentinel pattern + nsAutoRef<FcPattern> fontWithSentinel(FcPatternCreate()); + FcPatternAddString(fontWithSentinel, FC_FAMILY, + ToFcChar8Ptr(familyToFind.get())); + FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName); + FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern); + + // Add all font family matches until reaching the sentinel. + FcChar8* substName = nullptr; + for (int i = 0; + FcPatternGetString(fontWithSentinel, FC_FAMILY, + i, &substName) == FcResultMatch; + i++) + { + NS_ConvertUTF8toUTF16 subst(ToCharPtr(substName)); + if (sentinelFirstFamily && + FcStrCmp(substName, sentinelFirstFamily) == 0) { + break; + } + gfxPlatformFontList::FindAndAddFamilies(subst, &cachedFamilies); + } + + // Cache the resulting list, so we don't have to do this again. + mFcSubstituteCache.Put(familyToFind, cachedFamilies); + + if (cachedFamilies.IsEmpty()) { + return false; + } + aOutput->AppendElements(cachedFamilies); + return true; +} + +bool +gfxFcPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, + nsAString& aFamilyName) +{ + aFamilyName.Truncate(); + + // The fontconfig list of fonts includes generic family names in the + // font list. For these, just use the generic name. + if (aFontName.EqualsLiteral("serif") || + aFontName.EqualsLiteral("sans-serif") || + aFontName.EqualsLiteral("monospace")) { + aFamilyName.Assign(aFontName); + return true; + } + + nsAutoRef<FcPattern> pat(FcPatternCreate()); + if (!pat) { + return true; + } + + nsAutoRef<FcObjectSet> os(FcObjectSetBuild(FC_FAMILY, nullptr)); + if (!os) { + return true; + } + + // add the family name to the pattern + NS_ConvertUTF16toUTF8 familyName(aFontName); + FcPatternAddString(pat, FC_FAMILY, ToFcChar8Ptr(familyName.get())); + + nsAutoRef<FcFontSet> givenFS(FcFontList(nullptr, pat, os)); + if (!givenFS) { + return true; + } + + // See if there is a font face with first family equal to the given family + // (needs to be in sync with names coming from GetFontList()) + nsTArray<nsCString> candidates; + for (int i = 0; i < givenFS->nfont; i++) { + char* firstFamily; + + if (FcPatternGetString(givenFS->fonts[i], FC_FAMILY, 0, + (FcChar8 **) &firstFamily) != FcResultMatch) + { + continue; + } + + nsDependentCString first(firstFamily); + if (!candidates.Contains(first)) { + candidates.AppendElement(first); + + if (familyName.Equals(first)) { + aFamilyName.Assign(aFontName); + return true; + } + } + } + + // Because fontconfig conflates different family name types, need to + // double check that the candidate name is not simply a different + // name type. For example, if a font with nameID=16 "Minion Pro" and + // nameID=21 "Minion Pro Caption" exists, calling FcFontList with + // family="Minion Pro" will return a set of patterns some of which + // will have a first family of "Minion Pro Caption". Ignore these + // patterns and use the first candidate that maps to a font set with + // the same number of faces and an identical set of patterns. + for (uint32_t j = 0; j < candidates.Length(); ++j) { + FcPatternDel(pat, FC_FAMILY); + FcPatternAddString(pat, FC_FAMILY, (FcChar8 *)candidates[j].get()); + + nsAutoRef<FcFontSet> candidateFS(FcFontList(nullptr, pat, os)); + if (!candidateFS) { + return true; + } + + if (candidateFS->nfont != givenFS->nfont) { + continue; + } + + bool equal = true; + for (int i = 0; i < givenFS->nfont; ++i) { + if (!FcPatternEqual(candidateFS->fonts[i], givenFS->fonts[i])) { + equal = false; + break; + } + } + if (equal) { + AppendUTF8toUTF16(candidates[j], aFamilyName); + return true; + } + } + + // didn't find localized name, leave family name blank + return true; +} + +static const char kFontNamePrefix[] = "font.name."; + +void +gfxFcPlatformFontList::AddGenericFonts(mozilla::FontFamilyType aGenericType, + nsIAtom* aLanguage, + nsTArray<gfxFontFamily*>& aFamilyList) +{ + bool usePrefFontList = false; + + // treat -moz-fixed as monospace + if (aGenericType == eFamily_moz_fixed) { + aGenericType = eFamily_monospace; + } + + const char* generic = GetGenericName(aGenericType); + NS_ASSERTION(generic, "weird generic font type"); + if (!generic) { + return; + } + + // By default, most font prefs on Linux map to "use fontconfig" + // keywords. So only need to explicitly lookup font pref if + // non-default settings exist + NS_ConvertASCIItoUTF16 genericToLookup(generic); + if ((!mAlwaysUseFontconfigGenerics && aLanguage) || + aLanguage == nsGkAtoms::x_math) { + nsIAtom* langGroup = GetLangGroup(aLanguage); + nsAutoCString langGroupStr; + if (langGroup) { + langGroup->ToUTF8String(langGroupStr); + } + nsAutoCString prefFontName(kFontNamePrefix); + prefFontName.Append(generic); + prefFontName.Append('.'); + prefFontName.Append(langGroupStr); + nsAdoptingString fontlistValue = Preferences::GetString(prefFontName.get()); + if (fontlistValue) { + if (!fontlistValue.EqualsLiteral("serif") && + !fontlistValue.EqualsLiteral("sans-serif") && + !fontlistValue.EqualsLiteral("monospace")) { + usePrefFontList = true; + } else { + // serif, sans-serif or monospace was specified + genericToLookup.Assign(fontlistValue); + } + } + } + + // when pref fonts exist, use standard pref font lookup + if (usePrefFontList) { + return gfxPlatformFontList::AddGenericFonts(aGenericType, + aLanguage, + aFamilyList); + } + + PrefFontList* prefFonts = FindGenericFamilies(genericToLookup, aLanguage); + NS_ASSERTION(prefFonts, "null generic font list"); + aFamilyList.AppendElements(*prefFonts); +} + +void +gfxFcPlatformFontList::ClearLangGroupPrefFonts() +{ + ClearGenericMappings(); + gfxPlatformFontList::ClearLangGroupPrefFonts(); + mAlwaysUseFontconfigGenerics = PrefFontListsUseOnlyGenerics(); +} + +/* static */ FT_Library +gfxFcPlatformFontList::GetFTLibrary() +{ + if (!sCairoFTLibrary) { + // Use cairo's FT_Library so that cairo takes care of shutdown of the + // FT_Library after it has destroyed its font_faces, and FT_Done_Face + // has been called on each FT_Face, at least until this bug is fixed: + // https://bugs.freedesktop.org/show_bug.cgi?id=18857 + // + // Cairo keeps it's own FT_Library object for creating FT_Face + // instances, so use that. There's no simple API for accessing this + // so use the hacky method below of making a font and extracting + // the library pointer from that. + + bool needsBold; + gfxFontStyle style; + gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList(); + gfxFontFamily* family = pfl->GetDefaultFont(&style); + NS_ASSERTION(family, "couldn't find a default font family"); + gfxFontEntry* fe = family->FindFontForStyle(style, needsBold); + if (!fe) { + return nullptr; + } + RefPtr<gfxFont> font = fe->FindOrMakeFont(&style, false); + if (!font) { + return nullptr; + } + + gfxFT2FontBase* ft2Font = reinterpret_cast<gfxFT2FontBase*>(font.get()); + gfxFT2LockedFace face(ft2Font); + if (!face.get()) { + return nullptr; + } + + sCairoFTLibrary = face.get()->glyph->library; + } + + return sCairoFTLibrary; +} + +gfxPlatformFontList::PrefFontList* +gfxFcPlatformFontList::FindGenericFamilies(const nsAString& aGeneric, + nsIAtom* aLanguage) +{ + // set up name + NS_ConvertUTF16toUTF8 generic(aGeneric); + + nsAutoCString fcLang; + GetSampleLangForGroup(aLanguage, fcLang); + ToLowerCase(fcLang); + + nsAutoCString genericLang(generic); + if (fcLang.Length() > 0) { + genericLang.Append('-'); + } + genericLang.Append(fcLang); + + // try to get the family from the cache + PrefFontList* prefFonts = mGenericMappings.Get(genericLang); + if (prefFonts) { + return prefFonts; + } + + // if not found, ask fontconfig to pick the appropriate font + nsAutoRef<FcPattern> genericPattern(FcPatternCreate()); + FcPatternAddString(genericPattern, FC_FAMILY, + ToFcChar8Ptr(generic.get())); + + // -- prefer scalable fonts + FcPatternAddBool(genericPattern, FC_SCALABLE, FcTrue); + + // -- add the lang to the pattern + if (!fcLang.IsEmpty()) { + FcPatternAddString(genericPattern, FC_LANG, + ToFcChar8Ptr(fcLang.get())); + } + + // -- perform substitutions + FcConfigSubstitute(nullptr, genericPattern, FcMatchPattern); + FcDefaultSubstitute(genericPattern); + + // -- sort to get the closest matches + FcResult result; + nsAutoRef<FcFontSet> faces(FcFontSort(nullptr, genericPattern, FcFalse, + nullptr, &result)); + + if (!faces) { + return nullptr; + } + + // -- select the fonts to be used for the generic + prefFonts = new PrefFontList; // can be empty but in practice won't happen + uint32_t limit = gfxPlatformGtk::GetPlatform()->MaxGenericSubstitions(); + bool foundFontWithLang = false; + for (int i = 0; i < faces->nfont; i++) { + FcPattern* font = faces->fonts[i]; + FcChar8* mappedGeneric = nullptr; + + FcPatternGetString(font, FC_FAMILY, 0, &mappedGeneric); + if (mappedGeneric) { + NS_ConvertUTF8toUTF16 mappedGenericName(ToCharPtr(mappedGeneric)); + AutoTArray<gfxFontFamily*,1> genericFamilies; + if (gfxPlatformFontList::FindAndAddFamilies(mappedGenericName, + &genericFamilies)) { + MOZ_ASSERT(genericFamilies.Length() == 1, + "expected a single family"); + if (!prefFonts->Contains(genericFamilies[0])) { + prefFonts->AppendElement(genericFamilies[0]); + bool foundLang = + !fcLang.IsEmpty() && + PatternHasLang(font, ToFcChar8Ptr(fcLang.get())); + foundFontWithLang = foundFontWithLang || foundLang; + // check to see if the list is full + if (prefFonts->Length() >= limit) { + break; + } + } + } + } + } + + // if no font in the list matches the lang, trim all but the first one + if (!prefFonts->IsEmpty() && !foundFontWithLang) { + prefFonts->TruncateLength(1); + } + + mGenericMappings.Put(genericLang, prefFonts); + return prefFonts; +} + +bool +gfxFcPlatformFontList::PrefFontListsUseOnlyGenerics() +{ + bool prefFontsUseOnlyGenerics = true; + uint32_t count; + char** names; + nsresult rv = Preferences::GetRootBranch()-> + GetChildList(kFontNamePrefix, &count, &names); + if (NS_SUCCEEDED(rv) && count) { + for (size_t i = 0; i < count; i++) { + // Check whether all font.name prefs map to generic keywords + // and that the pref name and keyword match. + // Ex: font.name.serif.ar ==> "serif" (ok) + // Ex: font.name.serif.ar ==> "monospace" (return false) + // Ex: font.name.serif.ar ==> "DejaVu Serif" (return false) + + nsDependentCString prefName(names[i] + + ArrayLength(kFontNamePrefix) - 1); + nsCCharSeparatedTokenizer tokenizer(prefName, '.'); + const nsDependentCSubstring& generic = tokenizer.nextToken(); + const nsDependentCSubstring& langGroup = tokenizer.nextToken(); + nsAdoptingCString fontPrefValue = Preferences::GetCString(names[i]); + + if (!langGroup.EqualsLiteral("x-math") && + !generic.Equals(fontPrefValue)) { + prefFontsUseOnlyGenerics = false; + break; + } + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, names); + } + return prefFontsUseOnlyGenerics; +} + +/* static */ void +gfxFcPlatformFontList::CheckFontUpdates(nsITimer *aTimer, void *aThis) +{ + // check for font updates + FcInitBringUptoDate(); + + // update fontlist if current config changed + gfxFcPlatformFontList *pfl = static_cast<gfxFcPlatformFontList*>(aThis); + FcConfig* current = FcConfigGetCurrent(); + if (current != pfl->GetLastConfig()) { + pfl->UpdateFontList(); + pfl->ForceGlobalReflow(); + } +} + +#ifdef MOZ_BUNDLED_FONTS +void +gfxFcPlatformFontList::ActivateBundledFonts() +{ + if (!mBundledFontsInitialized) { + mBundledFontsInitialized = true; + 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; + } + if (NS_FAILED(localDir->GetNativePath(mBundledFontsPath))) { + return; + } + } + if (!mBundledFontsPath.IsEmpty()) { + FcConfigAppFontAddDir(nullptr, ToFcChar8Ptr(mBundledFontsPath.get())); + } +} +#endif + +#ifdef MOZ_WIDGET_GTK +/*************************************************************************** + * + * This function must be last in the file because it uses the system cairo + * library. Above this point the cairo library used is the tree cairo if + * MOZ_TREE_CAIRO. + */ + +#if MOZ_TREE_CAIRO +// Tree cairo symbols have different names. Disable their activation through +// preprocessor macros. +#undef cairo_ft_font_options_substitute + +// The system cairo functions are not declared because the include paths cause +// the gdk headers to pick up the tree cairo.h. +extern "C" { +NS_VISIBILITY_DEFAULT void +cairo_ft_font_options_substitute (const cairo_font_options_t *options, + FcPattern *pattern); +} +#endif + +static void +ApplyGdkScreenFontOptions(FcPattern *aPattern) +{ + const cairo_font_options_t *options = + gdk_screen_get_font_options(gdk_screen_get_default()); + + cairo_ft_font_options_substitute(options, aPattern); +} + +#endif // MOZ_WIDGET_GTK |