WebKit: fontショートハンドプロパティのパース
今回はショートハンドプロパティであるfontプロパティのパースについて見ていきます。
ここだけ読んでも、CSS2.1の仕様とWebKitの実装の対応関係がとれているのがなんとなく見えてきます。
15.8 Shorthand font property: the 'font' property
'font'
Value: [ [ <'font-style'> || <'font-variant'> || <'font-weight'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] | caption | icon | menu | message-box | small-caption | status-bar | inherit
Initial: see individual properties
Applies to: all elements
Inherited: yes
Percentages: see individual properties
Media: visual
Computed value: see individual properties
前回見た通り、プロパティ値のパースはCSSParser::parseValue()が入り口になっている。
fontプロパティに対応するcaseが見つかる。
Source/WebCore/css/CSSParser.cpp
bool CSSParser::parseValue(CSSPropertyID propId, bool important) { [...] case CSSPropertyFont: // [ [ 'font-style' || 'font-variant' || 'font-weight' ]? 'font-size' [ / 'line-height' ]? // 'font-family' ] | caption | icon | menu | message-box | small-caption | status-bar | inherit if (id >= CSSValueCaption && id <= CSSValueStatusBar) validPrimitive = true; else return parseFont(important); break; [...]
CSSParser::parseFont()を見る。
Source/WebCore/css/CSSParser.cpp
// [ 'font-style' || 'font-variant' || 'font-weight' ]? 'font-size' [ / 'line-height' ]? 'font-family' bool CSSParser::parseFont(bool important) { // Let's check if there is an inherit or initial somewhere in the shorthand. for (unsigned i = 0; i < m_valueList->size(); ++i) { if (m_valueList->valueAt(i)->id == CSSValueInherit || m_valueList->valueAt(i)->id == CSSValueInitial) return false; } ShorthandScope scope(this, CSSPropertyFont); // Optional font-style, font-variant and font-weight. bool fontStyleParsed = false; bool fontVariantParsed = false; bool fontWeightParsed = false; CSSParserValue* value; while ((value = m_valueList->current())) { if (!fontStyleParsed && isValidKeywordPropertyAndValue(CSSPropertyFontStyle, value->id, m_context)) { addProperty(CSSPropertyFontStyle, cssValuePool().createIdentifierValue(value->id), important); fontStyleParsed = true; } else if (!fontVariantParsed && (value->id == CSSValueNormal || value->id == CSSValueSmallCaps)) { // Font variant in the shorthand is particular, it only accepts normal or small-caps. addProperty(CSSPropertyFontVariant, cssValuePool().createIdentifierValue(value->id), important); fontVariantParsed = true; } else if (!fontWeightParsed && parseFontWeight(important)) fontWeightParsed = true; else break; m_valueList->next(); } if (!value) return false; if (!fontStyleParsed) addProperty(CSSPropertyFontStyle, cssValuePool().createIdentifierValue(CSSValueNormal), important, true); if (!fontVariantParsed) addProperty(CSSPropertyFontVariant, cssValuePool().createIdentifierValue(CSSValueNormal), important, true); if (!fontWeightParsed) addProperty(CSSPropertyFontWeight, cssValuePool().createIdentifierValue(CSSValueNormal), important, true); // Now a font size _must_ come. // <absolute-size> | <relative-size> | <length> | <percentage> | inherit if (!parseFontSize(important)) return false; value = m_valueList->current(); if (!value) return false; if (isForwardSlashOperator(value)) { // The line-height property. value = m_valueList->next(); if (!value) return false; if (!parseLineHeight(important)) return false; } else addProperty(CSSPropertyLineHeight, cssValuePool().createIdentifierValue(CSSValueNormal), important, true); // Font family must come now. RefPtr<CSSValue> parsedFamilyValue = parseFontFamily(); if (!parsedFamilyValue) return false; addProperty(CSSPropertyFontFamily, parsedFamilyValue.release(), important); // FIXME: http://www.w3.org/TR/2011/WD-css3-fonts-20110324/#font-prop requires that // "font-stretch", "font-size-adjust", and "font-kerning" be reset to their initial values // but we don't seem to support them at the moment. They should also be added here once implemented. if (m_valueList->current()) return false; return true; }
CSS2.1の仕様書にもあるBNF記法が、関数の先頭行にコメントとして転記されている。この関数は、このコメントのBNFにそって処理が構成されている。
大きくは3つ。
- 1. Optional font-style, font-variant and font-weight.
- 2. Now a font size _must_ come.
- 3. Font family must come now.
最初のコメントにある「Let's check if there is an inherit or initial somewhere in the shorthand.」ここはあまりよく理解できていない。inheritやinitialがプロパティ値に含まれている場合は、さっさと処理を切り上げているようだ。パースしても無駄になるからだろうか。
ShorthandScopeが目を引いた。ShorthandScopeはスコープをうまく使って、CSSParserでショートハンドプロパティのパースの状態管理を実現しているようだ。ShorthandScopeのローカル変数が作られたタイミングでコンストラクタが走り、スコープを抜けるタイミングでデストラクタが走る。CSSParser::parseFont()はreturnでスコープを抜けるケースが多くあり、手動で状態遷移するよりも、スコープにまかせたほうが漏れなく確実に所望の処理を行える。m_currentShorthandは初期状態がCSSPropertyInvalidなので、ショートハンドプロパティのパース中は、そのプロパティのIDを保持して、パースが終われば、元の状態のCSSPropertyInvalidに戻す、という動作をするのだろう。
class ShorthandScope { WTF_MAKE_FAST_ALLOCATED; public: ShorthandScope(CSSParser* parser, CSSPropertyID propId) : m_parser(parser) { if (!(m_parser->m_inParseShorthand++)) m_parser->m_currentShorthand = propId; } ~ShorthandScope() { if (!(--m_parser->m_inParseShorthand)) m_parser->m_currentShorthand = CSSPropertyInvalid; } private: CSSParser* m_parser; };
ここからはそれほど面白くはなく、CSSの仕様とWebKitの実装がうまく対応がとれているなあ、といったくらい。
// Optional font-style, font-variant and font-weight. を見る。
font-style、font-variant、font-weightは省略可能なので、あればプロパティとして認識する。
もしも無ければ、「 cssValuePool().createIdentifierValue(CSSValueNormal)」で「normal」(デフォルト値)として認識するわけだろう。
次に // Now a font size _must_ come. でfont sizeをパースする。
「/」(Forward Slash Operator)が見つかったら、line heightをパースする。
最後に、Font family must come now. でfont familyをパースする。