/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include "nsStyleUtil.h" #include "nsCRT.h" #include "nsStyleConsts.h" #include "nsGkAtoms.h" #include "nsILinkHandler.h" #include "nsIContent.h" #include "nsIDocument.h" #include "nsINameSpaceManager.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsReadableUtils.h" #include "nsContentUtils.h" #include "nsTextFormatter.h" // XXX This is here because nsCachedStyleData is accessed outside of // the content module; e.g., by nsCSSFrameConstructor. #include "nsRuleNode.h" nsCachedStyleData::StyleStructInfo nsCachedStyleData::gInfo[] = { #define STYLE_STRUCT_INHERITED(name, checkdata_cb, ctor_args) \ { offsetof(nsCachedStyleData, mInheritedData), \ offsetof(nsInheritedStyleData, m##name##Data), \ PR_FALSE }, #define STYLE_STRUCT_RESET(name, checkdata_cb, ctor_args) \ { offsetof(nsCachedStyleData, mResetData), \ offsetof(nsResetStyleData, m##name##Data), \ PR_TRUE }, #include "nsStyleStructList.h" #undef STYLE_STRUCT_INHERITED #undef STYLE_STRUCT_RESET { 0, 0, 0 } }; #define POSITIVE_SCALE_FACTOR 1.10 /* 10% */ #define NEGATIVE_SCALE_FACTOR .90 /* 10% */ //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ /* * Return the scaling percentage given a font scaler * Lifted from layutil.c */ float nsStyleUtil::GetScalingFactor(PRInt32 aScaler) { double scale = 1.0; double mult; PRInt32 count; if(aScaler < 0) { count = -aScaler; mult = NEGATIVE_SCALE_FACTOR; } else { count = aScaler; mult = POSITIVE_SCALE_FACTOR; } /* use the percentage scaling factor to the power of the pref */ while(count--) { scale *= mult; } return (float)scale; } //------------------------------------------------------------------------------ // Font Algorithm Code //------------------------------------------------------------------------------ nscoord nsStyleUtil::CalcFontPointSize(PRInt32 aHTMLSize, PRInt32 aBasePointSize, float aScalingFactor, nsPresContext* aPresContext, nsFontSizeType aFontSizeType) { #define sFontSizeTableMin 9 #define sFontSizeTableMax 16 // This table seems to be the one used by MacIE5. We hope its adoption in Mozilla // and eventually in WinIE5.5 will help to establish a standard rendering across // platforms and browsers. For now, it is used only in Strict mode. More can be read // in the document written by Todd Farhner at: // http://style.verso.com/font_size_intervals/altintervals.html // static PRInt32 sStrictFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] = { { 9, 9, 9, 9, 11, 14, 18, 27}, { 9, 9, 9, 10, 12, 15, 20, 30}, { 9, 9, 10, 11, 13, 17, 22, 33}, { 9, 9, 10, 12, 14, 18, 24, 36}, { 9, 10, 12, 13, 16, 20, 26, 39}, { 9, 10, 12, 14, 17, 21, 28, 42}, { 9, 10, 13, 15, 18, 23, 30, 45}, { 9, 10, 13, 16, 18, 24, 32, 48} }; // HTML 1 2 3 4 5 6 7 // CSS xxs xs s m l xl xxl // | // user pref // //------------------------------------------------------------ // // This table gives us compatibility with WinNav4 for the default fonts only. // In WinNav4, the default fonts were: // // Times/12pt == Times/16px at 96ppi // Courier/10pt == Courier/13px at 96ppi // // The 2 lines below marked "anchored" have the exact pixel sizes used by // WinNav4 for Times/12pt and Courier/10pt at 96ppi. As you can see, the // HTML size 3 (user pref) for those 2 anchored lines is 13px and 16px. // // All values other than the anchored values were filled in by hand, never // going below 9px, and maintaining a "diagonal" relationship. See for // example the 13s -- they follow a diagonal line through the table. // static PRInt32 sQuirksFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] = { { 9, 9, 9, 9, 11, 14, 18, 28 }, { 9, 9, 9, 10, 12, 15, 20, 31 }, { 9, 9, 9, 11, 13, 17, 22, 34 }, { 9, 9, 10, 12, 14, 18, 24, 37 }, { 9, 9, 10, 13, 16, 20, 26, 40 }, // anchored (13) { 9, 9, 11, 14, 17, 21, 28, 42 }, { 9, 10, 12, 15, 17, 23, 30, 45 }, { 9, 10, 13, 16, 18, 24, 32, 48 } // anchored (16) }; // HTML 1 2 3 4 5 6 7 // CSS xxs xs s m l xl xxl // | // user pref #if 0 // // These are the exact pixel values used by WinIE5 at 96ppi. // { ?, 8, 11, 12, 13, 16, 21, 32 }, // smallest { ?, 9, 12, 13, 16, 21, 27, 40 }, // smaller { ?, 10, 13, 16, 18, 24, 32, 48 }, // medium { ?, 13, 16, 19, 21, 27, 37, ?? }, // larger { ?, 16, 19, 21, 24, 32, 43, ?? } // largest // // HTML 1 2 3 4 5 6 7 // CSS ? ? ? ? ? ? ? ? // // (CSS not tested yet.) // #endif static PRInt32 sFontSizeFactors[8] = { 60,75,89,100,120,150,200,300 }; static PRInt32 sCSSColumns[7] = {0, 1, 2, 3, 4, 5, 6}; // xxs...xxl static PRInt32 sHTMLColumns[7] = {1, 2, 3, 4, 5, 6, 7}; // 1...7 double dFontSize; if (aFontSizeType == eFontSize_HTML) { aHTMLSize--; // input as 1-7 } if (aHTMLSize < 0) aHTMLSize = 0; else if (aHTMLSize > 6) aHTMLSize = 6; PRInt32* column; switch (aFontSizeType) { case eFontSize_HTML: column = sHTMLColumns; break; case eFontSize_CSS: column = sCSSColumns; break; } // Make special call specifically for fonts (needed PrintPreview) PRInt32 fontSize = nsPresContext::AppUnitsToIntCSSPixels(aBasePointSize); if ((fontSize >= sFontSizeTableMin) && (fontSize <= sFontSizeTableMax)) { PRInt32 row = fontSize - sFontSizeTableMin; if (aPresContext->CompatibilityMode() == eCompatibility_NavQuirks) { dFontSize = nsPresContext::CSSPixelsToAppUnits(sQuirksFontSizeTable[row][column[aHTMLSize]]); } else { dFontSize = nsPresContext::CSSPixelsToAppUnits(sStrictFontSizeTable[row][column[aHTMLSize]]); } } else { PRInt32 factor = sFontSizeFactors[column[aHTMLSize]]; dFontSize = (factor * aBasePointSize) / 100; } dFontSize *= aScalingFactor; if (1.0 < dFontSize) { return (nscoord)dFontSize; } return (nscoord)1; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ nscoord nsStyleUtil::FindNextSmallerFontSize(nscoord aFontSize, PRInt32 aBasePointSize, float aScalingFactor, nsPresContext* aPresContext, nsFontSizeType aFontSizeType) { PRInt32 index; PRInt32 indexMin; PRInt32 indexMax; float relativePosition; nscoord smallerSize; nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning nscoord smallestIndexFontSize; nscoord largestIndexFontSize; nscoord smallerIndexFontSize; nscoord largerIndexFontSize; nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1); if (aFontSizeType == eFontSize_HTML) { indexMin = 1; indexMax = 7; } else { indexMin = 0; indexMax = 6; } smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); if (aFontSize > smallestIndexFontSize) { if (aFontSize < NSToCoordRound(float(largestIndexFontSize) * 1.5)) { // smaller will be in HTML table // find largest index smaller than current for (index = indexMax; index >= indexMin; index--) { indexFontSize = CalcFontPointSize(index, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); if (indexFontSize < aFontSize) break; } // set up points beyond table for interpolation purposes if (indexFontSize == smallestIndexFontSize) { smallerIndexFontSize = indexFontSize - onePx; largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); } else if (indexFontSize == largestIndexFontSize) { smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); largerIndexFontSize = NSToCoordRound(float(largestIndexFontSize) * 1.5); } else { smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); } // compute the relative position of the parent size between the two closest indexed sizes relativePosition = float(aFontSize - indexFontSize) / float(largerIndexFontSize - indexFontSize); // set the new size to have the same relative position between the next smallest two indexed sizes smallerSize = smallerIndexFontSize + NSToCoordRound(relativePosition * (indexFontSize - smallerIndexFontSize)); } else { // larger than HTML table, drop by 33% smallerSize = NSToCoordRound(float(aFontSize) / 1.5); } } else { // smaller than HTML table, drop by 1px smallerSize = NS_MAX(aFontSize - onePx, onePx); } return smallerSize; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ nscoord nsStyleUtil::FindNextLargerFontSize(nscoord aFontSize, PRInt32 aBasePointSize, float aScalingFactor, nsPresContext* aPresContext, nsFontSizeType aFontSizeType) { PRInt32 index; PRInt32 indexMin; PRInt32 indexMax; float relativePosition; nscoord largerSize; nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning nscoord smallestIndexFontSize; nscoord largestIndexFontSize; nscoord smallerIndexFontSize; nscoord largerIndexFontSize; nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1); if (aFontSizeType == eFontSize_HTML) { indexMin = 1; indexMax = 7; } else { indexMin = 0; indexMax = 6; } smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); if (aFontSize > (smallestIndexFontSize - onePx)) { if (aFontSize < largestIndexFontSize) { // larger will be in HTML table // find smallest index larger than current for (index = indexMin; index <= indexMax; index++) { indexFontSize = CalcFontPointSize(index, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); if (indexFontSize > aFontSize) break; } // set up points beyond table for interpolation purposes if (indexFontSize == smallestIndexFontSize) { smallerIndexFontSize = indexFontSize - onePx; largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); } else if (indexFontSize == largestIndexFontSize) { smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); largerIndexFontSize = NSToCoordRound(float(largestIndexFontSize) * 1.5); } else { smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aScalingFactor, aPresContext, aFontSizeType); } // compute the relative position of the parent size between the two closest indexed sizes relativePosition = float(aFontSize - smallerIndexFontSize) / float(indexFontSize - smallerIndexFontSize); // set the new size to have the same relative position between the next largest two indexed sizes largerSize = indexFontSize + NSToCoordRound(relativePosition * (largerIndexFontSize - indexFontSize)); } else { // larger than HTML table, increase by 50% largerSize = NSToCoordRound(float(aFontSize) * 1.5); } } else { // smaller than HTML table, increase by 1px largerSize = aFontSize + onePx; } return largerSize; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ PRInt32 nsStyleUtil::ConstrainFontWeight(PRInt32 aWeight) { aWeight = ((aWeight < 100) ? 100 : ((aWeight > 900) ? 900 : aWeight)); PRInt32 base = ((aWeight / 100) * 100); PRInt32 step = (aWeight % 100); PRBool negativeStep = PRBool(50 < step); PRInt32 maxStep; if (negativeStep) { step = 100 - step; maxStep = (base / 100); base += 100; } else { maxStep = ((900 - base) / 100); } if (maxStep < step) { step = maxStep; } return (base + ((negativeStep) ? -step : step)); } static nsLinkState GetLinkStateFromURI(nsIURI* aURI, nsIContent* aContent, nsILinkHandler* aLinkHandler) { NS_PRECONDITION(aURI, "Must have URI"); nsLinkState state; if (NS_LIKELY(aLinkHandler)) { aLinkHandler->GetLinkState(aURI, state); } else { // no link handler? Try to get one off the content NS_ASSERTION(aContent->GetOwnerDoc(), "Shouldn't happen"); nsCOMPtr supp = aContent->GetOwnerDoc()->GetContainer(); nsCOMPtr handler = do_QueryInterface(supp); if (handler) { handler->GetLinkState(aURI, state); } else { // no link handler? then all links are unvisited state = eLinkState_Unvisited; } } return state; } /*static*/ PRBool nsStyleUtil::IsHTMLLink(nsIContent *aContent, nsILinkHandler *aLinkHandler, nsLinkState *aState) { NS_ASSERTION(aContent->IsHTML(), "Only use this function with HTML elements"); NS_ASSERTION(aState, "null arg in IsHTMLLink"); nsLinkState linkState = aContent->GetLinkState(); if (linkState == eLinkState_Unknown) { // if it is an anchor, area or link then check the href attribute // make sure this anchor has a link even if we are not testing state // if there is no link, then this anchor is not really a linkpseudo. // bug=23209 nsCOMPtr hrefURI = aContent->GetHrefURI(); if (hrefURI) { linkState = GetLinkStateFromURI(hrefURI, aContent, aLinkHandler); } else { linkState = eLinkState_NotLink; } if (linkState != eLinkState_NotLink && aContent->IsInDoc()) { aContent->GetCurrentDoc()->AddStyleRelevantLink(aContent, hrefURI); } aContent->SetLinkState(linkState); } if (linkState == eLinkState_NotLink) { return PR_FALSE; } *aState = linkState; return PR_TRUE; } /*static*/ PRBool nsStyleUtil::IsLink(nsIContent *aContent, nsILinkHandler *aLinkHandler, nsLinkState *aState) { // XXX PERF This function will cause serious performance problems on // pages with lots of XLinks. We should be caching the visited // state of the XLinks. Where??? NS_ASSERTION(aContent && aState, "invalid call to IsLink with null content"); PRBool rv = PR_FALSE; if (aContent && aState) { nsCOMPtr absURI; if (aContent->IsLink(getter_AddRefs(absURI))) { *aState = GetLinkStateFromURI(absURI, aContent, aLinkHandler); if (aContent->IsInDoc()) { aContent->GetCurrentDoc()->AddStyleRelevantLink(aContent, absURI); } rv = PR_TRUE; } } return rv; } // Compare two language strings PRBool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue, const nsAString& aSelectorValue, const nsStringComparator& aComparator) { PRBool result; PRUint32 selectorLen = aSelectorValue.Length(); PRUint32 attributeLen = aAttributeValue.Length(); if (selectorLen > attributeLen) { result = PR_FALSE; } else { nsAString::const_iterator iter; if (selectorLen != attributeLen && *aAttributeValue.BeginReading(iter).advance(selectorLen) != PRUnichar('-')) { // to match, the aAttributeValue must have a dash after the end of // the aSelectorValue's text (unless the aSelectorValue and the // aAttributeValue have the same text) result = PR_FALSE; } else { result = StringBeginsWith(aAttributeValue, aSelectorValue, aComparator); } } return result; } void nsStyleUtil::AppendEscapedCSSString(const nsString& aString, nsAString& aReturn) { aReturn.Append(PRUnichar('"')); const nsString::char_type* in = aString.get(); const nsString::char_type* const end = in + aString.Length(); for (; in != end; in++) { if (*in < 0x20) { // Escape all characters below 0x20 numerically. /* This is the buffer into which snprintf should write. As the hex. value is, for numbers below 0x20, max. 2 characters long, we don't need more than 5 characters ("\XX "+NUL). */ PRUnichar buf[5]; nsTextFormatter::snprintf(buf, NS_ARRAY_LENGTH(buf), NS_LITERAL_STRING("\\%hX ").get(), *in); aReturn.Append(buf); } else switch (*in) { // Special characters which should be escaped: Quotes and backslash case '\\': case '\"': case '\'': aReturn.Append(PRUnichar('\\')); // And now, after the eventual escaping character, the actual one. default: aReturn.Append(PRUnichar(*in)); } } aReturn.Append(PRUnichar('"')); } /* static */ float nsStyleUtil::ColorComponentToFloat(PRUint8 aAlpha) { // Alpha values are expressed as decimals, so we should convert // back, using as few decimal places as possible for // round-tripping. // First try two decimal places: float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f; if (FloatToColorComponent(rounded) != aAlpha) { // Use three decimal places. rounded = NS_roundf(float(aAlpha) * 1000.0f / 255.0f) / 1000.0f; } return rounded; } /* static */ PRBool nsStyleUtil::IsSignificantChild(nsIContent* aChild, PRBool aTextIsSignificant, PRBool aWhitespaceIsSignificant) { NS_ASSERTION(!aWhitespaceIsSignificant || aTextIsSignificant, "Nonsensical arguments"); PRBool isText = aChild->IsNodeOfType(nsINode::eTEXT); if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) && !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) { return PR_TRUE; } return aTextIsSignificant && isText && aChild->TextLength() != 0 && (aWhitespaceIsSignificant || !aChild->TextIsOnlyWhitespace()); }