2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #include "apply_style_command.h"
28 #include "html_interchange.h"
30 #include "css/css_valueimpl.h"
31 #include "css/css_computedstyle.h"
32 #include "css/cssparser.h"
33 #include "css/cssproperties.h"
34 #include "dom/dom_string.h"
35 #include "html/html_elementimpl.h"
36 #include "misc/htmltags.h"
37 #include "misc/htmlattrs.h"
38 #include "rendering/render_object.h"
39 #include "xml/dom_docimpl.h"
40 #include "xml/dom_textimpl.h"
41 #include "xml/dom2_rangeimpl.h"
44 #include "KWQAssertions.h"
46 #define ASSERT(assertion) assert(assertion)
49 using DOM::CSSComputedStyleDeclarationImpl;
50 using DOM::CSSMutableStyleDeclarationImpl;
52 using DOM::CSSPrimitiveValue;
53 using DOM::CSSPrimitiveValueImpl;
54 using DOM::CSSProperty;
55 using DOM::CSSStyleDeclarationImpl;
57 using DOM::CSSValueImpl;
59 using DOM::DoNotUpdateLayout;
60 using DOM::DocumentImpl;
61 using DOM::ElementImpl;
62 using DOM::HTMLElementImpl;
63 using DOM::NamedAttrMapImpl;
73 enum ELegacyHTMLStyles { DoNotUseLegacyHTMLStyles, UseLegacyHTMLStyles };
75 explicit StyleChange(CSSStyleDeclarationImpl *, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
76 StyleChange(CSSStyleDeclarationImpl *, const Position &, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
78 static ELegacyHTMLStyles styleModeForParseMode(bool);
80 DOMString cssStyle() const { return m_cssStyle; }
81 bool applyBold() const { return m_applyBold; }
82 bool applyItalic() const { return m_applyItalic; }
83 bool applyFontColor() const { return m_applyFontColor.length() > 0; }
84 bool applyFontFace() const { return m_applyFontFace.length() > 0; }
85 bool applyFontSize() const { return m_applyFontSize.length() > 0; }
87 DOMString fontColor() { return m_applyFontColor; }
88 DOMString fontFace() { return m_applyFontFace; }
89 DOMString fontSize() { return m_applyFontSize; }
91 bool usesLegacyStyles() const { return m_usesLegacyStyles; }
94 void init(CSSStyleDeclarationImpl *, const Position &);
95 bool checkForLegacyHTMLStyleChange(const CSSProperty *);
96 static bool currentlyHasStyle(const Position &, const CSSProperty *);
101 DOMString m_applyFontColor;
102 DOMString m_applyFontFace;
103 DOMString m_applyFontSize;
104 bool m_usesLegacyStyles;
109 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
110 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
112 init(style, Position());
115 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
116 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
118 init(style, position);
121 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
124 CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
128 QString styleText("");
130 QValueListConstIterator<CSSProperty> end;
131 for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
132 const CSSProperty *property = &*it;
134 // If position is empty or the position passed in already has the
135 // style, just move on.
136 if (position.isNotNull() && currentlyHasStyle(position, property))
139 // If needed, figure out if this change is a legacy HTML style change.
140 if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
145 if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) {
146 // we have to special-case text decorations
147 CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
148 styleText += alteredProperty.cssText().string();
150 styleText += property->cssText().string();
154 mutableStyle->deref();
156 // Save the result for later
157 m_cssStyle = styleText.stripWhiteSpace();
160 StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
162 return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
165 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
167 if (!property || !property->value()) {
171 DOMString valueText(property->value()->cssText());
172 switch (property->id()) {
173 case CSS_PROP_FONT_WEIGHT:
174 if (strcasecmp(valueText, "bold") == 0) {
179 case CSS_PROP_FONT_STYLE:
180 if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
181 m_applyItalic = true;
185 case CSS_PROP_COLOR: {
186 QColor color(CSSParser::parseColor(valueText));
187 m_applyFontColor = color.name();
190 case CSS_PROP_FONT_FAMILY:
191 m_applyFontFace = valueText;
193 case CSS_PROP_FONT_SIZE:
194 if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
195 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(property->value());
196 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
198 m_applyFontSize = "1";
199 else if (number <= 10)
200 m_applyFontSize = "2";
201 else if (number <= 13)
202 m_applyFontSize = "3";
203 else if (number <= 16)
204 m_applyFontSize = "4";
205 else if (number <= 18)
206 m_applyFontSize = "5";
207 else if (number <= 24)
208 m_applyFontSize = "6";
210 m_applyFontSize = "7";
211 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
212 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
213 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
217 // Can't make sense of the number. Put no font size.
224 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
226 ASSERT(pos.isNotNull());
227 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
230 CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
235 bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
240 static DOMString &styleSpanClassString()
242 static DOMString styleSpanClassString = AppleStyleSpanClass;
243 return styleSpanClassString;
246 bool isStyleSpan(const NodeImpl *node)
248 if (!node || !node->isHTMLElement())
251 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
252 return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
255 static bool isEmptyStyleSpan(const NodeImpl *node)
257 if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
260 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
261 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
262 return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
265 static bool isEmptyFontTag(const NodeImpl *node)
267 if (!node || node->id() != ID_FONT)
270 const ElementImpl *elem = static_cast<const ElementImpl *>(node);
271 NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
272 return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
275 static ElementImpl *createFontElement(DocumentImpl *document)
277 int exceptionCode = 0;
278 ElementImpl *fontNode = document->createHTMLElement("font", exceptionCode);
279 ASSERT(exceptionCode == 0);
280 fontNode->setAttribute(ATTR_CLASS, styleSpanClassString());
284 ElementImpl *createStyleSpanElement(DocumentImpl *document)
286 int exceptionCode = 0;
287 ElementImpl *styleElement = document->createHTMLElement("SPAN", exceptionCode);
288 ASSERT(exceptionCode == 0);
289 styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
293 ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel)
294 : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel)
300 ApplyStyleCommand::~ApplyStyleCommand()
306 void ApplyStyleCommand::doApply()
308 switch (m_propertyLevel) {
309 case PropertyDefault: {
310 // apply the block-centric properties of the style
311 CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
313 applyBlockStyle(blockStyle);
314 // apply any remaining styles to the inline elements
315 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
316 if (blockStyle->length() < m_style->length()) {
317 CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
319 applyRelativeFontStyleChange(inlineStyle);
320 blockStyle->diff(inlineStyle);
321 applyInlineStyle(inlineStyle);
322 inlineStyle->deref();
327 case ForceBlockProperties:
328 // Force all properties to be applied as block styles.
329 applyBlockStyle(m_style);
333 setEndingSelectionNeedsLayout();
336 EditAction ApplyStyleCommand::editingAction() const
338 return m_editingAction;
341 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
343 // update document layout once before removing styles
344 // so that we avoid the expense of updating before each and every call
345 // to check a computed style
346 document()->updateLayout();
348 // get positions we want to use for applying style
349 Position start(endingSelection().start());
350 Position end(endingSelection().end());
352 // remove current values, if any, of the specified styles from the blocks
353 // NOTE: tracks the previous block to avoid repeated processing
354 // Also, gather up all the nodes we want to process in a QPtrList before
355 // doing anything. This averts any bugs iterating over these nodes
356 // once you start removing and applying style.
357 NodeImpl *beyondEnd = end.node()->traverseNextNode();
358 QPtrList<NodeImpl> nodes;
359 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
362 NodeImpl *prevBlock = 0;
363 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
364 NodeImpl *block = it.current()->enclosingBlockFlowElement();
365 if (block != prevBlock && block->isHTMLElement()) {
366 removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
371 // apply specified styles to the block flow elements in the selected range
373 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
374 NodeImpl *node = it.current();
375 if (node->renderer()) {
376 NodeImpl *block = node->enclosingBlockFlowElement();
377 if (block != prevBlock) {
378 addBlockStyleIfNeeded(style, node);
385 #define NoFontDelta (0.0f)
386 #define MinimumFontSize (0.1f)
388 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
390 if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
391 // Explicit font size overrides any delta.
392 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
396 // Get the adjustment amount out of the style.
397 CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
401 float adjustment = NoFontDelta;
402 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
403 CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
404 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
405 // Only PX handled now. If we handle more types in the future, perhaps
406 // a switch statement here would be more appropriate.
407 adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
410 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
412 if (adjustment == NoFontDelta)
415 // Adjust to the positions we want to use for applying style.
416 Selection selection = endingSelection();
417 Position start(selection.start().downstream());
418 Position end(selection.end().upstream());
419 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
420 Position swap = start;
425 // Join up any adjacent text nodes.
426 if (start.node()->isTextNode()) {
427 joinChildTextNodes(start.node()->parentNode(), start, end);
428 selection = endingSelection();
429 start = selection.start();
430 end = selection.end();
432 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
433 joinChildTextNodes(end.node()->parentNode(), start, end);
434 selection = endingSelection();
435 start = selection.start();
436 end = selection.end();
439 // Split the start text nodes if needed to apply style.
440 bool splitStart = splitTextAtStartIfNeeded(start, end);
442 start = endingSelection().start();
443 end = endingSelection().end();
445 bool splitEnd = splitTextAtEndIfNeeded(start, end);
447 start = endingSelection().start();
448 end = endingSelection().end();
451 NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
452 start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
453 NodeImpl *startNode = start.node();
454 if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters.
455 startNode = startNode->traverseNextNode();
457 // Store away font size before making any changes to the document.
458 // This ensures that changes to one node won't effect another.
459 QMap<const NodeImpl *,float> startingFontSizes;
460 for (const NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode())
461 startingFontSizes.insert(node, computedFontSize(node));
463 // These spans were added by us. If empty after font size changes, they can be removed.
464 QPtrList<NodeImpl> emptySpans;
466 NodeImpl *lastStyledNode = 0;
467 for (NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
468 HTMLElementImpl *elem = 0;
469 if (node->isHTMLElement()) {
470 // Only work on fully selected nodes.
471 if (!nodeFullySelected(node, start, end))
473 elem = static_cast<HTMLElementImpl *>(node);
475 else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
476 // Last styled node was not parent node of this text node, but we wish to style this
477 // text node. To make this possible, add a style span to surround this text node.
478 elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
479 insertNodeBefore(elem, node);
480 surroundNodeRangeWithElement(node, node, elem);
483 // Only handle HTML elements and text nodes.
486 lastStyledNode = node;
488 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
489 float currentFontSize = computedFontSize(node);
490 float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
491 if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
492 inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
493 currentFontSize = computedFontSize(node);
495 if (currentFontSize != desiredFontSize) {
496 QString desiredFontSizeString = QString::number(desiredFontSize);
497 desiredFontSizeString += "px";
498 inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
499 setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
501 if (inlineStyleDecl->length() == 0) {
502 removeNodeAttribute(elem, ATTR_STYLE);
503 if (isEmptyStyleSpan(elem))
504 emptySpans.append(elem);
508 for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
509 removeNodePreservingChildren(it.current());
513 #undef MinimumFontSize
515 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
517 // adjust to the positions we want to use for applying style
518 Position start(endingSelection().start().downstream().equivalentRangeCompliantPosition());
519 Position end(endingSelection().end().upstream());
521 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
522 Position swap = start;
527 // update document layout once before removing styles
528 // so that we avoid the expense of updating before each and every call
529 // to check a computed style
530 document()->updateLayout();
532 // split the start node and containing element if the selection starts inside of it
533 bool splitStart = splitTextElementAtStartIfNeeded(start, end);
535 start = endingSelection().start();
536 end = endingSelection().end();
539 // split the end node and containing element if the selection ends inside of it
540 bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
541 start = endingSelection().start();
542 end = endingSelection().end();
544 // Remove style from the selection.
545 // Use the upstream position of the start for removing style.
546 // This will ensure we remove all traces of the relevant styles from the selection
547 // and prevent us from adding redundant ones, as described in:
548 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
549 removeInlineStyle(style, start.upstream(), end);
550 start = endingSelection().start();
551 end = endingSelection().end();
554 bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
556 start = endingSelection().start();
557 end = endingSelection().end();
562 mergeEndWithNextIfIdentical(start, end);
563 start = endingSelection().start();
564 end = endingSelection().end();
567 // update document layout once before running the rest of the function
568 // so that we avoid the expense of updating before each and every call
569 // to check a computed style
570 document()->updateLayout();
572 if (start.node() == end.node()) {
573 // simple case...start and end are the same node
574 addInlineStyleIfNeeded(style, start.node(), end.node());
577 NodeImpl *node = start.node();
578 if (start.offset() >= start.node()->caretMaxOffset())
579 node = node->traverseNextNode();
581 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
582 NodeImpl *runStart = node;
584 NodeImpl *next = node->traverseNextNode();
585 // Break if node is the end node, or if the next node does not fit in with
586 // the current group.
587 if (node == end.node() ||
588 runStart->parentNode() != next->parentNode() ||
589 (next->isHTMLElement() && next->id() != ID_BR) ||
590 (next->renderer() && !next->renderer()->isInline()))
594 // Now apply style to the run we found.
595 addInlineStyleIfNeeded(style, runStart, node);
597 if (node == end.node())
599 node = node->traverseNextNode();
603 if (splitStart || splitEnd) {
604 cleanUpEmptyStyleSpans(start, end);
608 //------------------------------------------------------------------------------------------
609 // ApplyStyleCommand: style-removal helpers
611 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
613 QValueListConstIterator<CSSProperty> end;
614 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
615 switch ((*it).id()) {
616 case CSS_PROP_FONT_WEIGHT:
617 if (elem->id() == ID_B)
620 case CSS_PROP_FONT_STYLE:
621 if (elem->id() == ID_I)
629 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
631 // This node can be removed.
632 // EDIT FIXME: This does not handle the case where the node
633 // has attributes. But how often do people add attributes to <B> tags?
634 // Not so often I think.
636 removeNodePreservingChildren(elem);
639 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
644 if (elem->id() != ID_FONT)
647 int exceptionCode = 0;
648 QValueListConstIterator<CSSProperty> end;
649 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
650 switch ((*it).id()) {
652 elem->removeAttribute(ATTR_COLOR, exceptionCode);
653 ASSERT(exceptionCode == 0);
655 case CSS_PROP_FONT_FAMILY:
656 elem->removeAttribute(ATTR_FACE, exceptionCode);
657 ASSERT(exceptionCode == 0);
659 case CSS_PROP_FONT_SIZE:
660 elem->removeAttribute(ATTR_SIZE, exceptionCode);
661 ASSERT(exceptionCode == 0);
666 if (isEmptyFontTag(elem))
667 removeNodePreservingChildren(elem);
670 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
675 CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
679 QValueListConstIterator<CSSProperty> end;
680 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
681 int propertyID = (*it).id();
682 CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
685 removeCSSProperty(decl, propertyID);
690 if (isEmptyStyleSpan(elem))
691 removeNodePreservingChildren(elem);
694 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
696 ASSERT(start.isNotNull());
697 ASSERT(end.isNotNull());
698 ASSERT(start.node()->inDocument());
699 ASSERT(end.node()->inDocument());
700 ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
704 static bool hasTextDecorationProperty(NodeImpl *node)
706 if (!node->isElementNode())
709 ElementImpl *element = static_cast<ElementImpl *>(node);
710 CSSComputedStyleDeclarationImpl style(element);
712 CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
716 DOMString valueText(value->cssText());
718 if (strcasecmp(valueText,"none") != 0)
725 static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node)
727 NodeImpl *result = NULL;
729 for (NodeImpl *n = node; n; n = n->parentNode()) {
730 if (hasTextDecorationProperty(n))
737 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node)
740 ASSERT(node->isElementNode());
742 // non-html elements not handled yet
743 if (!node->isHTMLElement())
746 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
747 CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl();
752 int properties[1] = { CSS_PROP_TEXT_DECORATION };
753 CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1);
755 CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
756 if (property && strcasecmp(property->cssText(), "none") != 0) {
757 removeCSSProperty(style, CSS_PROP_TEXT_DECORATION);
762 return textDecorationStyle;
765 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node)
768 ASSERT(node->isElementNode());
770 // non-html elements not handled yet
771 if (!node->isHTMLElement())
774 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
775 CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element);
776 ASSERT(computedStyle);
778 computedStyle->ref();
780 int properties[1] = { CSS_PROP_TEXT_DECORATION };
781 CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
784 CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
785 if (property && strcasecmp(property->cssText(), "none") != 0) {
787 CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy();
790 newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
791 applyTextDecorationStyle(node, newStyle);
797 computedStyle->deref();
799 return textDecorationStyle;
802 void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
806 if (!style || !style->cssText().length())
809 if (node->isTextNode()) {
810 HTMLElementImpl *styleSpan = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
811 insertNodeBefore(styleSpan, node);
812 surroundNodeRangeWithElement(node, node, styleSpan);
816 if (!node->isElementNode())
819 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
821 StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
822 if (styleChange.cssStyle().length() > 0) {
823 DOMString cssText = styleChange.cssStyle();
824 CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl();
826 cssText += decl->cssText();
827 setNodeAttribute(element, ATTR_STYLE, cssText);
831 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force)
833 NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node);
835 if (highestAncestor) {
836 NodeImpl *nextCurrent;
838 for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) {
843 CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
847 for (NodeImpl *child = current->firstChild(); child; child = nextChild) {
848 nextChild = child->nextSibling();
852 } else if (node->isAncestor(child)) {
853 applyTextDecorationStyle(child, decoration);
856 applyTextDecorationStyle(child, decoration);
866 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
868 // We need to work in two passes. First we push down any inline
869 // styles that set text decoration. Then we look for any remaining
870 // styles (caused by stylesheets) and explicitly negate text
871 // decoration while pushing down.
873 pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
874 document()->updateLayout();
875 pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
877 pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
878 document()->updateLayout();
879 pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
882 static int maxRangeOffset(NodeImpl *n)
884 if (DOM::offsetInCharacters(n->nodeType()))
885 return n->maxOffset();
887 if (n->isElementNode())
888 return n->childNodeCount();
893 void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
895 ASSERT(start.isNotNull());
896 ASSERT(end.isNotNull());
897 ASSERT(start.node()->inDocument());
898 ASSERT(end.node()->inDocument());
899 ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0);
901 CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
903 if (textDecorationSpecialProperty) {
904 pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
905 style = style->copy();
906 style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
909 // The s and e variables store the positions used to set the ending selection after style removal
910 // takes place. This will help callers to recognize when either the start node or the end node
911 // are removed from the document during the work of this function.
915 NodeImpl *node = start.node();
917 NodeImpl *next = node->traverseNextNode();
918 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
919 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
920 NodeImpl *prev = elem->traversePreviousNodePostOrder();
921 NodeImpl *next = elem->traverseNextNode();
922 if (isHTMLStyleNode(style, elem)) {
923 removeHTMLStyleNode(elem);
926 removeHTMLFontStyle(style, elem);
927 removeCSSStyle(style, elem);
929 if (!elem->inDocument()) {
930 if (s.node() == elem) {
931 // Since elem must have been fully selected, and it is at the start
932 // of the selection, it is clear we can set the new s offset to 0.
933 ASSERT(s.offset() <= s.node()->caretMinOffset());
934 s = Position(next, 0);
936 if (e.node() == elem) {
937 // Since elem must have been fully selected, and it is at the end
938 // of the selection, it is clear we can set the new e offset to
939 // the max range offset of prev.
940 ASSERT(e.offset() >= maxRangeOffset(e.node()));
941 e = Position(prev, maxRangeOffset(prev));
945 if (node == end.node())
951 if (textDecorationSpecialProperty) {
955 ASSERT(s.node()->inDocument());
956 ASSERT(e.node()->inDocument());
957 setEndingSelection(Selection(s, VP_DEFAULT_AFFINITY, e, VP_DEFAULT_AFFINITY));
960 bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const
963 ASSERT(node->isElementNode());
965 Position pos = Position(node, node->childNodeCount()).upstream();
966 return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
967 RangeImpl::compareBoundaryPoints(pos, end) <= 0;
970 bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
973 ASSERT(node->isElementNode());
975 Position pos = Position(node, node->childNodeCount()).upstream();
976 bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
977 bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
979 return isFullyBeforeStart || isFullyAfterEnd;
983 //------------------------------------------------------------------------------------------
984 // ApplyStyleCommand: style-application helpers
986 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
988 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
989 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
990 TextImpl *text = static_cast<TextImpl *>(start.node());
991 splitTextNode(text, start.offset());
992 setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
998 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
1000 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1001 TextImpl *text = static_cast<TextImpl *>(end.node());
1002 splitTextNode(text, end.offset());
1004 NodeImpl *prevNode = text->previousSibling();
1006 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1008 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
1014 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
1016 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
1017 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
1018 TextImpl *text = static_cast<TextImpl *>(start.node());
1019 splitTextNodeContainingElement(text, start.offset());
1021 setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1027 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
1029 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1030 TextImpl *text = static_cast<TextImpl *>(end.node());
1031 splitTextNodeContainingElement(text, end.offset());
1033 NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
1035 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
1037 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
1043 static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
1045 // check that tag name and all attribute names and values are identical
1047 if (!first->isElementNode())
1050 if (!second->isElementNode())
1053 ElementImpl *firstElement = static_cast<ElementImpl *>(first);
1054 ElementImpl *secondElement = static_cast<ElementImpl *>(second);
1056 if (firstElement->id() != secondElement->id())
1059 NamedAttrMapImpl *firstMap = firstElement->attributes();
1060 NamedAttrMapImpl *secondMap = secondElement->attributes();
1062 unsigned firstLength = firstMap->length();
1064 if (firstLength != secondMap->length())
1067 for (unsigned i = 0; i < firstLength; i++) {
1068 DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
1069 DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
1071 if (!secondAttribute || attribute->value() != secondAttribute->value())
1078 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
1080 NodeImpl *startNode = start.node();
1081 long startOffset = start.offset();
1083 if (start.node()->isAtomicNode()) {
1084 if (start.offset() != 0)
1087 if (start.node()->previousSibling())
1090 startNode = start.node()->parent();
1094 if (!startNode->isElementNode())
1097 if (startOffset != 0)
1100 NodeImpl *previousSibling = startNode->previousSibling();
1102 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1103 ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
1104 ElementImpl *element = static_cast<ElementImpl *>(startNode);
1105 NodeImpl *startChild = element->firstChild();
1107 mergeIdenticalElements(previousElement, element);
1109 long startOffsetAdjustment = startChild->nodeIndex();
1110 long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
1112 setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
1113 Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
1121 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
1123 NodeImpl *endNode = end.node();
1124 int endOffset = end.offset();
1126 if (endNode->isAtomicNode()) {
1127 if (endOffset < endNode->caretMaxOffset())
1130 unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
1131 if (end.node()->nextSibling())
1134 endNode = end.node()->parent();
1135 endOffset = parentLastOffset;
1138 if (!endNode->isElementNode() || endNode->id() == ID_BR)
1141 NodeImpl *nextSibling = endNode->nextSibling();
1143 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1144 ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
1145 ElementImpl *element = static_cast<ElementImpl *>(endNode);
1146 NodeImpl *nextChild = nextElement->firstChild();
1148 mergeIdenticalElements(element, nextElement);
1150 NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
1153 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1155 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY,
1156 Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
1163 void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
1166 for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
1169 if (node && isEmptyStyleSpan(node->previousSibling())) {
1170 removeNodePreservingChildren(node->previousSibling());
1173 if (start.node() == end.node()) {
1174 if (start.node()->isTextNode()) {
1175 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1176 if (isEmptyStyleSpan(cur)) {
1177 removeNodePreservingChildren(cur);
1184 if (start.node()->isTextNode()) {
1185 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
1186 if (isEmptyStyleSpan(cur)) {
1187 removeNodePreservingChildren(cur);
1193 if (end.node()->isTextNode()) {
1194 for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1195 if (isEmptyStyleSpan(cur)) {
1196 removeNodePreservingChildren(cur);
1203 for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
1205 if (node && isEmptyStyleSpan(node->nextSibling())) {
1206 removeNodePreservingChildren(node->nextSibling());
1210 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
1216 NodeImpl *node = startNode;
1218 NodeImpl *next = node->traverseNextNode();
1219 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1221 appendNode(node, element);
1223 if (node == endNode)
1229 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
1231 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1236 HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
1240 StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1241 if (styleChange.cssStyle().length() > 0) {
1242 moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
1243 block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
1244 DOMString cssText = styleChange.cssStyle();
1245 CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
1247 cssText += decl->cssText();
1248 setNodeAttribute(block, ATTR_STYLE, cssText);
1252 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
1254 StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1255 int exceptionCode = 0;
1258 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1260 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1261 ElementImpl *fontElement = createFontElement(document());
1262 ASSERT(exceptionCode == 0);
1263 insertNodeBefore(fontElement, startNode);
1264 if (styleChange.applyFontColor())
1265 fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
1266 if (styleChange.applyFontFace())
1267 fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
1268 if (styleChange.applyFontSize())
1269 fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
1270 surroundNodeRangeWithElement(startNode, endNode, fontElement);
1273 if (styleChange.cssStyle().length() > 0) {
1274 ElementImpl *styleElement = createStyleSpanElement(document());
1275 styleElement->ref();
1276 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
1277 insertNodeBefore(styleElement, startNode);
1278 styleElement->deref();
1279 surroundNodeRangeWithElement(startNode, endNode, styleElement);
1282 if (styleChange.applyBold()) {
1283 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
1284 ASSERT(exceptionCode == 0);
1285 insertNodeBefore(boldElement, startNode);
1286 surroundNodeRangeWithElement(startNode, endNode, boldElement);
1289 if (styleChange.applyItalic()) {
1290 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
1291 ASSERT(exceptionCode == 0);
1292 insertNodeBefore(italicElement, startNode);
1293 surroundNodeRangeWithElement(startNode, endNode, italicElement);
1297 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
1304 Position pos(const_cast<NodeImpl *>(node), 0);
1305 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
1308 computedStyle->ref();
1310 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
1313 size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1317 computedStyle->deref();
1321 void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
1326 Position newStart = start;
1327 Position newEnd = end;
1329 NodeImpl *child = node->firstChild();
1331 NodeImpl *next = child->nextSibling();
1332 if (child->isTextNode() && next && next->isTextNode()) {
1333 TextImpl *childText = static_cast<TextImpl *>(child);
1334 TextImpl *nextText = static_cast<TextImpl *>(next);
1335 if (next == start.node())
1336 newStart = Position(childText, childText->length() + start.offset());
1337 if (next == end.node())
1338 newEnd = Position(childText, childText->length() + end.offset());
1339 DOMString textToMove = nextText->data();
1340 insertTextIntoNode(childText, childText->length(), textToMove);
1342 // don't move child node pointer. it may want to merge with more text nodes.
1345 child = child->nextSibling();
1349 setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
1352 } // namespace khtml