EditingStyle.cpp revision cad810f21b803229eb11403f9209855525a25d57
1/*
2 * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "EditingStyle.h"
29
30#include "ApplyStyleCommand.h"
31#include "CSSComputedStyleDeclaration.h"
32#include "CSSMutableStyleDeclaration.h"
33#include "CSSValueKeywords.h"
34#include "Frame.h"
35#include "Node.h"
36#include "Position.h"
37#include "RenderStyle.h"
38#include "SelectionController.h"
39
40namespace WebCore {
41
42// Editing style properties must be preserved during editing operation.
43// e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
44// FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties
45static const int editingStyleProperties[] = {
46    // CSS inheritable properties
47    CSSPropertyBorderCollapse,
48    CSSPropertyColor,
49    CSSPropertyFontFamily,
50    CSSPropertyFontSize,
51    CSSPropertyFontStyle,
52    CSSPropertyFontVariant,
53    CSSPropertyFontWeight,
54    CSSPropertyLetterSpacing,
55    CSSPropertyLineHeight,
56    CSSPropertyOrphans,
57    CSSPropertyTextAlign,
58    CSSPropertyTextIndent,
59    CSSPropertyTextTransform,
60    CSSPropertyWhiteSpace,
61    CSSPropertyWidows,
62    CSSPropertyWordSpacing,
63    CSSPropertyWebkitBorderHorizontalSpacing,
64    CSSPropertyWebkitBorderVerticalSpacing,
65    CSSPropertyWebkitTextDecorationsInEffect,
66    CSSPropertyWebkitTextFillColor,
67    CSSPropertyWebkitTextSizeAdjust,
68    CSSPropertyWebkitTextStrokeColor,
69    CSSPropertyWebkitTextStrokeWidth,
70};
71size_t numEditingStyleProperties = WTF_ARRAY_LENGTH(editingStyleProperties);
72
73static PassRefPtr<CSSMutableStyleDeclaration> copyEditingProperties(CSSStyleDeclaration* style)
74{
75    return style->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties);
76}
77
78static PassRefPtr<CSSMutableStyleDeclaration> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style)
79{
80    if (!style)
81        return CSSMutableStyleDeclaration::create();
82    return copyEditingProperties(style.get());
83}
84
85float EditingStyle::NoFontDelta = 0.0f;
86
87EditingStyle::EditingStyle()
88    : m_shouldUseFixedDefaultFontSize(false)
89    , m_fontSizeDelta(NoFontDelta)
90{
91}
92
93EditingStyle::EditingStyle(Node* node)
94    : m_shouldUseFixedDefaultFontSize(false)
95    , m_fontSizeDelta(NoFontDelta)
96{
97    init(node);
98}
99
100EditingStyle::EditingStyle(const Position& position)
101    : m_shouldUseFixedDefaultFontSize(false)
102    , m_fontSizeDelta(NoFontDelta)
103{
104    init(position.node());
105}
106
107EditingStyle::EditingStyle(const CSSStyleDeclaration* style)
108    : m_mutableStyle(style->copy())
109    , m_shouldUseFixedDefaultFontSize(false)
110    , m_fontSizeDelta(NoFontDelta)
111{
112    extractFontSizeDelta();
113}
114
115EditingStyle::~EditingStyle()
116{
117}
118
119void EditingStyle::init(Node* node)
120{
121    RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = computedStyle(node);
122    m_mutableStyle = editingStyleFromComputedStyle(computedStyleAtPosition);
123
124    if (node && node->computedStyle()) {
125        RenderStyle* renderStyle = node->computedStyle();
126        removeTextFillAndStrokeColorsIfNeeded(renderStyle);
127        replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
128    }
129
130    m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
131    extractFontSizeDelta();
132}
133
134void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
135{
136    // If a node's text fill color is invalid, then its children use
137    // their font-color as their text fill color (they don't
138    // inherit it).  Likewise for stroke color.
139    ExceptionCode ec = 0;
140    if (!renderStyle->textFillColor().isValid())
141        m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor, ec);
142    if (!renderStyle->textStrokeColor().isValid())
143        m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor, ec);
144    ASSERT(!ec);
145}
146
147void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
148{
149    ASSERT(renderStyle);
150    if (renderStyle->fontDescription().keywordSize())
151        m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
152}
153
154void EditingStyle::extractFontSizeDelta()
155{
156    if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
157        // Explicit font size overrides any delta.
158        m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
159        return;
160    }
161
162    // Get the adjustment amount out of the style.
163    RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
164    if (!value || value->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE)
165        return;
166
167    CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value.get());
168
169    // Only PX handled now. If we handle more types in the future, perhaps
170    // a switch statement here would be more appropriate.
171    if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_PX)
172        return;
173
174    m_fontSizeDelta = primitiveValue->getFloatValue();
175    m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
176}
177
178bool EditingStyle::isEmpty() const
179{
180    return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
181}
182
183bool EditingStyle::textDirection(WritingDirection& writingDirection) const
184{
185    RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
186    if (!unicodeBidi)
187        return false;
188
189    ASSERT(unicodeBidi->isPrimitiveValue());
190    int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
191    if (unicodeBidiValue == CSSValueEmbed) {
192        RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
193        ASSERT(!direction || direction->isPrimitiveValue());
194        if (!direction)
195            return false;
196
197        writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
198
199        return true;
200    }
201
202    if (unicodeBidiValue == CSSValueNormal) {
203        writingDirection = NaturalWritingDirection;
204        return true;
205    }
206
207    return false;
208}
209
210void EditingStyle::setStyle(PassRefPtr<CSSMutableStyleDeclaration> style)
211{
212    m_mutableStyle = style;
213    // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
214    // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
215    m_shouldUseFixedDefaultFontSize = false;
216    extractFontSizeDelta();
217}
218
219void EditingStyle::overrideWithStyle(const CSSMutableStyleDeclaration* style)
220{
221    if (!style || !style->length())
222        return;
223    if (!m_mutableStyle)
224        m_mutableStyle = CSSMutableStyleDeclaration::create();
225    m_mutableStyle->merge(style);
226    extractFontSizeDelta();
227}
228
229void EditingStyle::clear()
230{
231    m_mutableStyle.clear();
232    m_shouldUseFixedDefaultFontSize = false;
233    m_fontSizeDelta = NoFontDelta;
234}
235
236PassRefPtr<EditingStyle> EditingStyle::copy() const
237{
238    RefPtr<EditingStyle> copy = EditingStyle::create();
239    if (m_mutableStyle)
240        copy->m_mutableStyle = m_mutableStyle->copy();
241    copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
242    copy->m_fontSizeDelta = m_fontSizeDelta;
243    return copy;
244}
245
246PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
247{
248    RefPtr<EditingStyle> blockProperties = EditingStyle::create();
249    if (!m_mutableStyle)
250        return blockProperties;
251
252    blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
253    m_mutableStyle->removeBlockProperties();
254
255    return blockProperties;
256}
257
258void EditingStyle::removeBlockProperties()
259{
260    if (!m_mutableStyle)
261        return;
262
263    m_mutableStyle->removeBlockProperties();
264}
265
266void EditingStyle::removeStyleAddedByNode(Node* node)
267{
268    if (!node || !node->parentNode())
269        return;
270    RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
271    RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
272    parentStyle->diff(nodeStyle.get());
273    nodeStyle->diff(m_mutableStyle.get());
274}
275
276void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
277{
278    if (!node || !node->parentNode() || !m_mutableStyle)
279        return;
280    RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
281    RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
282    parentStyle->diff(nodeStyle.get());
283
284    CSSMutableStyleDeclaration::const_iterator end = nodeStyle->end();
285    for (CSSMutableStyleDeclaration::const_iterator it = nodeStyle->begin(); it != end; ++it)
286        m_mutableStyle->removeProperty(it->id());
287}
288
289void EditingStyle::removeNonEditingProperties()
290{
291    if (m_mutableStyle)
292        m_mutableStyle = copyEditingProperties(m_mutableStyle.get());
293}
294
295void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
296{
297    if (!m_mutableStyle)
298        return;
299
300    // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
301    // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
302    // which one of editingStyleAtPosition or computedStyle is called.
303    RefPtr<EditingStyle> style = EditingStyle::create(position);
304
305    RefPtr<CSSValue> unicodeBidi;
306    RefPtr<CSSValue> direction;
307    if (shouldPreserveWritingDirection == PreserveWritingDirection) {
308        unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
309        direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
310    }
311
312    style->m_mutableStyle->diff(m_mutableStyle.get());
313
314    // if alpha value is zero, we don't add the background color.
315    RefPtr<CSSValue> backgroundColor = m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
316    if (backgroundColor && backgroundColor->isPrimitiveValue()
317        && !alphaChannel(static_cast<CSSPrimitiveValue*>(backgroundColor.get())->getRGBA32Value())) {
318        ExceptionCode ec;
319        m_mutableStyle->removeProperty(CSSPropertyBackgroundColor, ec);
320    }
321
322    if (unicodeBidi) {
323        ASSERT(unicodeBidi->isPrimitiveValue());
324        m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
325        if (direction) {
326            ASSERT(direction->isPrimitiveValue());
327            m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
328        }
329    }
330}
331
332PassRefPtr<EditingStyle> editingStyleIncludingTypingStyle(const Position& position)
333{
334    RefPtr<EditingStyle> editingStyle = EditingStyle::create(position);
335    RefPtr<EditingStyle> typingStyle = position.node()->document()->frame()->selection()->typingStyle();
336    if (typingStyle && typingStyle->style())
337        editingStyle->style()->merge(copyEditingProperties(typingStyle->style()).get());
338    return editingStyle;
339}
340
341}
342