gecko/layout/style/nsStyleUtil.cpp

580 lines
21 KiB
C++

/* -*- 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 <math.h>
#include "nsStyleUtil.h"
#include "nsCRT.h"
#include "nsStyleConsts.h"
#include "nsGkAtoms.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"
#include "nsCSSProps.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));
}
// 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 */ void
nsStyleUtil::AppendEscapedCSSIdent(const nsString& aIdent, nsAString& aReturn)
{
// The relevant parts of the CSS grammar are:
// ident [-]?{nmstart}{nmchar}*
// nmstart [_a-z]|{nonascii}|{escape}
// nmchar [_a-z0-9-]|{nonascii}|{escape}
// nonascii [^\0-\177]
// escape {unicode}|\\[^\n\r\f0-9a-f]
// unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
// from http://www.w3.org/TR/CSS21/syndata.html#tokenization
const nsString::char_type* in = aIdent.get();
const nsString::char_type* const end = in + aIdent.Length();
// Deal with the leading dash separately so we don't need to
// unnecessarily escape digits.
if (in != end && *in == '-') {
aReturn.Append(PRUnichar('-'));
++in;
}
PRBool first = PR_TRUE;
for (; in != end; ++in, first = PR_FALSE)
{
if (*in < 0x20 || (first && '0' <= *in && *in <= '9'))
{
// Escape all characters below 0x20, and digits at the start
// (including after a dash), numerically. If we didn't escape
// digits numerically, they'd get interpreted as a numeric escape
// for the wrong character.
/*
This is the buffer into which snprintf should write. As the hex.
value is, for numbers below 0x7F, 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 {
PRUnichar ch = *in;
if (!((ch == PRUnichar('_')) ||
(PRUnichar('A') <= ch && ch <= PRUnichar('Z')) ||
(PRUnichar('a') <= ch && ch <= PRUnichar('z')) ||
PRUnichar(0x80) <= ch ||
(!first && ch == PRUnichar('-')) ||
(PRUnichar('0') <= ch && ch <= PRUnichar('9')))) {
// Character needs to be escaped
aReturn.Append(PRUnichar('\\'));
}
aReturn.Append(ch);
}
}
}
/* static */ void
nsStyleUtil::AppendBitmaskCSSValue(nsCSSProperty aProperty,
PRInt32 aMaskedValue,
PRInt32 aFirstMask,
PRInt32 aLastMask,
nsAString& aResult)
{
for (PRInt32 mask = aFirstMask; mask <= aLastMask; mask <<= 1) {
if (mask & aMaskedValue) {
AppendASCIItoUTF16(nsCSSProps::LookupPropertyValue(aProperty, mask),
aResult);
aMaskedValue &= ~mask;
if (aMaskedValue) { // more left
aResult.Append(PRUnichar(' '));
}
}
}
NS_ABORT_IF_FALSE(aMaskedValue == 0, "unexpected bit remaining in bitfield");
}
/* 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());
}