michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsFont.h" michael@0: #include "gfxFont.h" // for gfxFontStyle michael@0: #include "gfxFontConstants.h" // for NS_FONT_KERNING_AUTO, etc michael@0: #include "gfxFontFeatures.h" // for gfxFontFeature, etc michael@0: #include "gfxFontUtils.h" // for TRUETYPE_TAG michael@0: #include "nsCRT.h" // for nsCRT michael@0: #include "nsDebug.h" // for NS_ASSERTION michael@0: #include "nsISupports.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nscore.h" // for char16_t michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: michael@0: nsFont::nsFont(const char* aName, uint8_t aStyle, uint8_t aVariant, michael@0: uint16_t aWeight, int16_t aStretch, uint8_t aDecoration, michael@0: nscoord aSize) michael@0: { michael@0: NS_ASSERTION(aName && IsASCII(nsDependentCString(aName)), michael@0: "Must only pass ASCII names here"); michael@0: name.AssignASCII(aName); michael@0: style = aStyle; michael@0: systemFont = false; michael@0: variant = aVariant; michael@0: weight = aWeight; michael@0: stretch = aStretch; michael@0: decorations = aDecoration; michael@0: smoothing = NS_FONT_SMOOTHING_AUTO; michael@0: size = aSize; michael@0: sizeAdjust = 0.0; michael@0: kerning = NS_FONT_KERNING_AUTO; michael@0: synthesis = NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE; michael@0: michael@0: variantAlternates = 0; michael@0: variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; michael@0: variantEastAsian = 0; michael@0: variantLigatures = 0; michael@0: variantNumeric = 0; michael@0: variantPosition = NS_FONT_VARIANT_POSITION_NORMAL; michael@0: } michael@0: michael@0: nsFont::nsFont(const nsSubstring& aName, uint8_t aStyle, uint8_t aVariant, michael@0: uint16_t aWeight, int16_t aStretch, uint8_t aDecoration, michael@0: nscoord aSize) michael@0: : name(aName) michael@0: { michael@0: style = aStyle; michael@0: systemFont = false; michael@0: variant = aVariant; michael@0: weight = aWeight; michael@0: stretch = aStretch; michael@0: decorations = aDecoration; michael@0: smoothing = NS_FONT_SMOOTHING_AUTO; michael@0: size = aSize; michael@0: sizeAdjust = 0.0; michael@0: kerning = NS_FONT_KERNING_AUTO; michael@0: synthesis = NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE; michael@0: michael@0: variantAlternates = 0; michael@0: variantCaps = NS_FONT_VARIANT_CAPS_NORMAL; michael@0: variantEastAsian = 0; michael@0: variantLigatures = 0; michael@0: variantNumeric = 0; michael@0: variantPosition = NS_FONT_VARIANT_POSITION_NORMAL; michael@0: } michael@0: michael@0: nsFont::nsFont(const nsFont& aOther) michael@0: : name(aOther.name) michael@0: { michael@0: style = aOther.style; michael@0: systemFont = aOther.systemFont; michael@0: variant = aOther.variant; michael@0: weight = aOther.weight; michael@0: stretch = aOther.stretch; michael@0: decorations = aOther.decorations; michael@0: smoothing = aOther.smoothing; michael@0: size = aOther.size; michael@0: sizeAdjust = aOther.sizeAdjust; michael@0: kerning = aOther.kerning; michael@0: synthesis = aOther.synthesis; michael@0: fontFeatureSettings = aOther.fontFeatureSettings; michael@0: languageOverride = aOther.languageOverride; michael@0: variantAlternates = aOther.variantAlternates; michael@0: variantCaps = aOther.variantCaps; michael@0: variantEastAsian = aOther.variantEastAsian; michael@0: variantLigatures = aOther.variantLigatures; michael@0: variantNumeric = aOther.variantNumeric; michael@0: variantPosition = aOther.variantPosition; michael@0: alternateValues = aOther.alternateValues; michael@0: featureValueLookup = aOther.featureValueLookup; michael@0: } michael@0: michael@0: nsFont::nsFont() michael@0: { michael@0: } michael@0: michael@0: nsFont::~nsFont() michael@0: { michael@0: } michael@0: michael@0: bool nsFont::BaseEquals(const nsFont& aOther) const michael@0: { michael@0: if ((style == aOther.style) && michael@0: (systemFont == aOther.systemFont) && michael@0: (weight == aOther.weight) && michael@0: (stretch == aOther.stretch) && michael@0: (size == aOther.size) && michael@0: (sizeAdjust == aOther.sizeAdjust) && michael@0: name.Equals(aOther.name, nsCaseInsensitiveStringComparator()) && michael@0: (kerning == aOther.kerning) && michael@0: (synthesis == aOther.synthesis) && michael@0: (fontFeatureSettings == aOther.fontFeatureSettings) && michael@0: (languageOverride == aOther.languageOverride) && michael@0: (variantAlternates == aOther.variantAlternates) && michael@0: (variantCaps == aOther.variantCaps) && michael@0: (variantEastAsian == aOther.variantEastAsian) && michael@0: (variantLigatures == aOther.variantLigatures) && michael@0: (variantNumeric == aOther.variantNumeric) && michael@0: (variantPosition == aOther.variantPosition) && michael@0: (alternateValues == aOther.alternateValues) && michael@0: (featureValueLookup == aOther.featureValueLookup) && michael@0: (smoothing == aOther.smoothing)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool nsFont::Equals(const nsFont& aOther) const michael@0: { michael@0: if (BaseEquals(aOther) && michael@0: (variant == aOther.variant) && michael@0: (decorations == aOther.decorations)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsFont& nsFont::operator=(const nsFont& aOther) michael@0: { michael@0: name = aOther.name; michael@0: style = aOther.style; michael@0: systemFont = aOther.systemFont; michael@0: variant = aOther.variant; michael@0: weight = aOther.weight; michael@0: stretch = aOther.stretch; michael@0: decorations = aOther.decorations; michael@0: smoothing = aOther.smoothing; michael@0: size = aOther.size; michael@0: sizeAdjust = aOther.sizeAdjust; michael@0: kerning = aOther.kerning; michael@0: synthesis = aOther.synthesis; michael@0: fontFeatureSettings = aOther.fontFeatureSettings; michael@0: languageOverride = aOther.languageOverride; michael@0: variantAlternates = aOther.variantAlternates; michael@0: variantCaps = aOther.variantCaps; michael@0: variantEastAsian = aOther.variantEastAsian; michael@0: variantLigatures = aOther.variantLigatures; michael@0: variantNumeric = aOther.variantNumeric; michael@0: variantPosition = aOther.variantPosition; michael@0: alternateValues = aOther.alternateValues; michael@0: featureValueLookup = aOther.featureValueLookup; michael@0: return *this; michael@0: } michael@0: michael@0: void michael@0: nsFont::CopyAlternates(const nsFont& aOther) michael@0: { michael@0: variantAlternates = aOther.variantAlternates; michael@0: alternateValues = aOther.alternateValues; michael@0: featureValueLookup = aOther.featureValueLookup; michael@0: } michael@0: michael@0: static bool IsGenericFontFamily(const nsString& aFamily) michael@0: { michael@0: uint8_t generic; michael@0: nsFont::GetGenericID(aFamily, &generic); michael@0: return generic != kGenericFont_NONE; michael@0: } michael@0: michael@0: const char16_t kSingleQuote = char16_t('\''); michael@0: const char16_t kDoubleQuote = char16_t('\"'); michael@0: const char16_t kComma = char16_t(','); michael@0: michael@0: bool nsFont::EnumerateFamilies(nsFontFamilyEnumFunc aFunc, void* aData) const michael@0: { michael@0: const char16_t *p, *p_end; michael@0: name.BeginReading(p); michael@0: name.EndReading(p_end); michael@0: nsAutoString family; michael@0: michael@0: while (p < p_end) { michael@0: while (nsCRT::IsAsciiSpace(*p)) michael@0: if (++p == p_end) michael@0: return true; michael@0: michael@0: bool generic; michael@0: if (*p == kSingleQuote || *p == kDoubleQuote) { michael@0: // quoted font family michael@0: char16_t quoteMark = *p; michael@0: if (++p == p_end) michael@0: return true; michael@0: const char16_t *nameStart = p; michael@0: michael@0: // XXX What about CSS character escapes? michael@0: while (*p != quoteMark) michael@0: if (++p == p_end) michael@0: return true; michael@0: michael@0: family = Substring(nameStart, p); michael@0: generic = false; michael@0: michael@0: while (++p != p_end && *p != kComma) michael@0: /* nothing */ ; michael@0: michael@0: } else { michael@0: // unquoted font family michael@0: const char16_t *nameStart = p; michael@0: while (++p != p_end && *p != kComma) michael@0: /* nothing */ ; michael@0: michael@0: family = Substring(nameStart, p); michael@0: family.CompressWhitespace(false, true); michael@0: generic = IsGenericFontFamily(family); michael@0: } michael@0: michael@0: if (!family.IsEmpty() && !(*aFunc)(family, generic, aData)) michael@0: return false; michael@0: michael@0: ++p; // may advance past p_end michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // mapping from bitflag to font feature tag/value pair michael@0: // michael@0: // these need to be kept in sync with the constants listed michael@0: // in gfxFontConstants.h (e.g. NS_FONT_VARIANT_EAST_ASIAN_JIS78) michael@0: michael@0: // NS_FONT_VARIANT_EAST_ASIAN_xxx values michael@0: const gfxFontFeature eastAsianDefaults[] = { michael@0: { TRUETYPE_TAG('j','p','7','8'), 1 }, michael@0: { TRUETYPE_TAG('j','p','8','3'), 1 }, michael@0: { TRUETYPE_TAG('j','p','9','0'), 1 }, michael@0: { TRUETYPE_TAG('j','p','0','4'), 1 }, michael@0: { TRUETYPE_TAG('s','m','p','l'), 1 }, michael@0: { TRUETYPE_TAG('t','r','a','d'), 1 }, michael@0: { TRUETYPE_TAG('f','w','i','d'), 1 }, michael@0: { TRUETYPE_TAG('p','w','i','d'), 1 }, michael@0: { TRUETYPE_TAG('r','u','b','y'), 1 } michael@0: }; michael@0: michael@0: static_assert(MOZ_ARRAY_LENGTH(eastAsianDefaults) == michael@0: eFeatureEastAsian_numFeatures, michael@0: "eFeatureEastAsian_numFeatures should be correct"); michael@0: michael@0: // NS_FONT_VARIANT_LIGATURES_xxx values michael@0: const gfxFontFeature ligDefaults[] = { michael@0: { TRUETYPE_TAG('l','i','g','a'), 0 }, // none value means all off michael@0: { TRUETYPE_TAG('l','i','g','a'), 1 }, michael@0: { TRUETYPE_TAG('l','i','g','a'), 0 }, michael@0: { TRUETYPE_TAG('d','l','i','g'), 1 }, michael@0: { TRUETYPE_TAG('d','l','i','g'), 0 }, michael@0: { TRUETYPE_TAG('h','l','i','g'), 1 }, michael@0: { TRUETYPE_TAG('h','l','i','g'), 0 }, michael@0: { TRUETYPE_TAG('c','a','l','t'), 1 }, michael@0: { TRUETYPE_TAG('c','a','l','t'), 0 } michael@0: }; michael@0: michael@0: static_assert(MOZ_ARRAY_LENGTH(ligDefaults) == michael@0: eFeatureLigatures_numFeatures, michael@0: "eFeatureLigatures_numFeatures should be correct"); michael@0: michael@0: // NS_FONT_VARIANT_NUMERIC_xxx values michael@0: const gfxFontFeature numericDefaults[] = { michael@0: { TRUETYPE_TAG('l','n','u','m'), 1 }, michael@0: { TRUETYPE_TAG('o','n','u','m'), 1 }, michael@0: { TRUETYPE_TAG('p','n','u','m'), 1 }, michael@0: { TRUETYPE_TAG('t','n','u','m'), 1 }, michael@0: { TRUETYPE_TAG('f','r','a','c'), 1 }, michael@0: { TRUETYPE_TAG('a','f','r','c'), 1 }, michael@0: { TRUETYPE_TAG('z','e','r','o'), 1 }, michael@0: { TRUETYPE_TAG('o','r','d','n'), 1 } michael@0: }; michael@0: michael@0: static_assert(MOZ_ARRAY_LENGTH(numericDefaults) == michael@0: eFeatureNumeric_numFeatures, michael@0: "eFeatureNumeric_numFeatures should be correct"); michael@0: michael@0: static void michael@0: AddFontFeaturesBitmask(uint32_t aValue, uint32_t aMin, uint32_t aMax, michael@0: const gfxFontFeature aFeatureDefaults[], michael@0: nsTArray& aFeaturesOut) michael@0: michael@0: { michael@0: uint32_t i, m; michael@0: michael@0: for (i = 0, m = aMin; m <= aMax; i++, m <<= 1) { michael@0: if (m & aValue) { michael@0: const gfxFontFeature& feature = aFeatureDefaults[i]; michael@0: aFeaturesOut.AppendElement(feature); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void nsFont::AddFontFeaturesToStyle(gfxFontStyle *aStyle) const michael@0: { michael@0: // add in font-variant features michael@0: gfxFontFeature setting; michael@0: michael@0: // -- kerning michael@0: setting.mTag = TRUETYPE_TAG('k','e','r','n'); michael@0: switch (kerning) { michael@0: case NS_FONT_KERNING_NONE: michael@0: setting.mValue = 0; michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: case NS_FONT_KERNING_NORMAL: michael@0: setting.mValue = 1; michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: default: michael@0: // auto case implies use user agent default michael@0: break; michael@0: } michael@0: michael@0: // -- alternates michael@0: if (variantAlternates & NS_FONT_VARIANT_ALTERNATES_HISTORICAL) { michael@0: setting.mValue = 1; michael@0: setting.mTag = TRUETYPE_TAG('h','i','s','t'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: } michael@0: michael@0: michael@0: // -- copy font-specific alternate info into style michael@0: // (this will be resolved after font-matching occurs) michael@0: aStyle->alternateValues.AppendElements(alternateValues); michael@0: aStyle->featureValueLookup = featureValueLookup; michael@0: michael@0: // -- caps michael@0: setting.mValue = 1; michael@0: switch (variantCaps) { michael@0: case NS_FONT_VARIANT_CAPS_ALLSMALL: michael@0: setting.mTag = TRUETYPE_TAG('c','2','s','c'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: // fall through to the small-caps case michael@0: case NS_FONT_VARIANT_CAPS_SMALLCAPS: michael@0: setting.mTag = TRUETYPE_TAG('s','m','c','p'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: michael@0: case NS_FONT_VARIANT_CAPS_ALLPETITE: michael@0: setting.mTag = TRUETYPE_TAG('c','2','p','c'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: // fall through to the petite-caps case michael@0: case NS_FONT_VARIANT_CAPS_PETITECAPS: michael@0: setting.mTag = TRUETYPE_TAG('p','c','a','p'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: michael@0: case NS_FONT_VARIANT_CAPS_TITLING: michael@0: setting.mTag = TRUETYPE_TAG('t','i','t','l'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: michael@0: case NS_FONT_VARIANT_CAPS_UNICASE: michael@0: setting.mTag = TRUETYPE_TAG('u','n','i','c'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // -- east-asian michael@0: if (variantEastAsian) { michael@0: AddFontFeaturesBitmask(variantEastAsian, michael@0: NS_FONT_VARIANT_EAST_ASIAN_JIS78, michael@0: NS_FONT_VARIANT_EAST_ASIAN_RUBY, michael@0: eastAsianDefaults, aStyle->featureSettings); michael@0: } michael@0: michael@0: // -- ligatures michael@0: if (variantLigatures) { michael@0: AddFontFeaturesBitmask(variantLigatures, michael@0: NS_FONT_VARIANT_LIGATURES_NONE, michael@0: NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL, michael@0: ligDefaults, aStyle->featureSettings); michael@0: michael@0: if (variantLigatures & NS_FONT_VARIANT_LIGATURES_COMMON) { michael@0: // liga already enabled, need to enable clig also michael@0: setting.mTag = TRUETYPE_TAG('c','l','i','g'); michael@0: setting.mValue = 1; michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: } else if (variantLigatures & NS_FONT_VARIANT_LIGATURES_NO_COMMON) { michael@0: // liga already disabled, need to disable clig also michael@0: setting.mTag = TRUETYPE_TAG('c','l','i','g'); michael@0: setting.mValue = 0; michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: } else if (variantLigatures & NS_FONT_VARIANT_LIGATURES_NONE) { michael@0: // liga already disabled, need to disable dlig, hlig, calt, clig michael@0: setting.mValue = 0; michael@0: setting.mTag = TRUETYPE_TAG('d','l','i','g'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: setting.mTag = TRUETYPE_TAG('h','l','i','g'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: setting.mTag = TRUETYPE_TAG('c','a','l','t'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: setting.mTag = TRUETYPE_TAG('c','l','i','g'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: } michael@0: } michael@0: michael@0: // -- numeric michael@0: if (variantNumeric) { michael@0: AddFontFeaturesBitmask(variantNumeric, michael@0: NS_FONT_VARIANT_NUMERIC_LINING, michael@0: NS_FONT_VARIANT_NUMERIC_ORDINAL, michael@0: numericDefaults, aStyle->featureSettings); michael@0: } michael@0: michael@0: // -- position michael@0: setting.mTag = 0; michael@0: setting.mValue = 1; michael@0: switch (variantPosition) { michael@0: case NS_FONT_VARIANT_POSITION_SUPER: michael@0: setting.mTag = TRUETYPE_TAG('s','u','p','s'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: michael@0: case NS_FONT_VARIANT_POSITION_SUB: michael@0: setting.mTag = TRUETYPE_TAG('s','u','b','s'); michael@0: aStyle->featureSettings.AppendElement(setting); michael@0: break; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // add in features from font-feature-settings michael@0: aStyle->featureSettings.AppendElements(fontFeatureSettings); michael@0: michael@0: // enable grayscale antialiasing for text michael@0: if (smoothing == NS_FONT_SMOOTHING_GRAYSCALE) { michael@0: aStyle->useGrayscaleAntialiasing = true; michael@0: } michael@0: } michael@0: michael@0: static bool FontEnumCallback(const nsString& aFamily, bool aGeneric, void *aData) michael@0: { michael@0: *((nsString*)aData) = aFamily; michael@0: return false; michael@0: } michael@0: michael@0: void nsFont::GetFirstFamily(nsString& aFamily) const michael@0: { michael@0: EnumerateFamilies(FontEnumCallback, &aFamily); michael@0: } michael@0: michael@0: /*static*/ michael@0: void nsFont::GetGenericID(const nsString& aGeneric, uint8_t* aID) michael@0: { michael@0: *aID = kGenericFont_NONE; michael@0: if (aGeneric.LowerCaseEqualsLiteral("-moz-fixed")) *aID = kGenericFont_moz_fixed; michael@0: else if (aGeneric.LowerCaseEqualsLiteral("serif")) *aID = kGenericFont_serif; michael@0: else if (aGeneric.LowerCaseEqualsLiteral("sans-serif")) *aID = kGenericFont_sans_serif; michael@0: else if (aGeneric.LowerCaseEqualsLiteral("cursive")) *aID = kGenericFont_cursive; michael@0: else if (aGeneric.LowerCaseEqualsLiteral("fantasy")) *aID = kGenericFont_fantasy; michael@0: else if (aGeneric.LowerCaseEqualsLiteral("monospace")) *aID = kGenericFont_monospace; michael@0: }