1 /**
2 * Copyright (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com)
3 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 */
21
22 #include "config.h"
23 #include "RenderCounter.h"
24
25 #include "CounterNode.h"
26 #include "Document.h"
27 #include "HTMLNames.h"
28 #include "HTMLOListElement.h"
29 #include "RenderListItem.h"
30 #include "RenderListMarker.h"
31 #include "RenderStyle.h"
32 #include <wtf/StdLibExtras.h>
33
34 namespace WebCore {
35
36 using namespace HTMLNames;
37
38 typedef HashMap<RefPtr<AtomicStringImpl>, CounterNode*> CounterMap;
39 typedef HashMap<const RenderObject*, CounterMap*> CounterMaps;
40
41 static CounterNode* counter(RenderObject*, const AtomicString& counterName, bool alwaysCreateCounter);
42
counterMaps()43 static CounterMaps& counterMaps()
44 {
45 DEFINE_STATIC_LOCAL(CounterMaps, staticCounterMaps, ());
46 return staticCounterMaps;
47 }
48
previousSiblingOrParent(RenderObject * object)49 static inline RenderObject* previousSiblingOrParent(RenderObject* object)
50 {
51 if (RenderObject* sibling = object->previousSibling())
52 return sibling;
53 return object->parent();
54 }
55
lastDescendant(CounterNode * node)56 static CounterNode* lastDescendant(CounterNode* node)
57 {
58 CounterNode* last = node->lastChild();
59 if (!last)
60 return 0;
61
62 while (CounterNode* lastChild = last->lastChild())
63 last = lastChild;
64
65 return last;
66 }
67
previousInPreOrder(CounterNode * node)68 static CounterNode* previousInPreOrder(CounterNode* node)
69 {
70 CounterNode* previous = node->previousSibling();
71 if (!previous)
72 return node->parent();
73
74 while (CounterNode* lastChild = previous->lastChild())
75 previous = lastChild;
76
77 return previous;
78 }
79
planCounter(RenderObject * object,const AtomicString & counterName,bool & isReset,int & value)80 static bool planCounter(RenderObject* object, const AtomicString& counterName, bool& isReset, int& value)
81 {
82 ASSERT(object);
83
84 // Real text nodes don't have their own style so they can't have counters.
85 // We can't even look at their styles or we'll see extra resets and increments!
86 if (object->isText() && !object->isBR())
87 return false;
88
89 RenderStyle* style = object->style();
90 ASSERT(style);
91
92 if (const CounterDirectiveMap* directivesMap = style->counterDirectives()) {
93 CounterDirectives directives = directivesMap->get(counterName.impl());
94 if (directives.m_reset) {
95 value = directives.m_resetValue;
96 if (directives.m_increment)
97 value += directives.m_incrementValue;
98 isReset = true;
99 return true;
100 }
101 if (directives.m_increment) {
102 value = directives.m_incrementValue;
103 isReset = false;
104 return true;
105 }
106 }
107
108 if (counterName == "list-item") {
109 if (object->isListItem()) {
110 if (static_cast<RenderListItem*>(object)->hasExplicitValue()) {
111 value = static_cast<RenderListItem*>(object)->explicitValue();
112 isReset = true;
113 return true;
114 }
115 value = 1;
116 isReset = false;
117 return true;
118 }
119 if (Node* e = object->element()) {
120 if (e->hasTagName(olTag)) {
121 value = static_cast<HTMLOListElement*>(e)->start();
122 isReset = true;
123 return true;
124 }
125 if (e->hasTagName(ulTag) || e->hasTagName(menuTag) || e->hasTagName(dirTag)) {
126 value = 0;
127 isReset = true;
128 return true;
129 }
130 }
131 }
132
133 return false;
134 }
135
findPlaceForCounter(RenderObject * object,const AtomicString & counterName,bool isReset,CounterNode * & parent,CounterNode * & previousSibling)136 static bool findPlaceForCounter(RenderObject* object, const AtomicString& counterName,
137 bool isReset, CounterNode*& parent, CounterNode*& previousSibling)
138 {
139 // Find the appropriate previous sibling for insertion into the parent node
140 // by searching in render tree order for a child of the counter.
141 parent = 0;
142 previousSibling = 0;
143 RenderObject* resetCandidate = isReset ? object->parent() : previousSiblingOrParent(object);
144 RenderObject* prevCounterCandidate = object;
145 CounterNode* candidateCounter = 0;
146 while ((prevCounterCandidate = prevCounterCandidate->previousInPreOrder())) {
147 CounterNode* c = counter(prevCounterCandidate, counterName, false);
148 if (prevCounterCandidate == resetCandidate) {
149 if (!candidateCounter)
150 candidateCounter = c;
151 if (candidateCounter) {
152 if (candidateCounter->isReset()) {
153 parent = candidateCounter;
154 previousSibling = 0;
155 } else {
156 parent = candidateCounter->parent();
157 previousSibling = candidateCounter;
158 }
159 return true;
160 }
161 resetCandidate = previousSiblingOrParent(resetCandidate);
162 } else if (c) {
163 if (c->isReset())
164 candidateCounter = 0;
165 else if (!candidateCounter)
166 candidateCounter = c;
167 }
168 }
169
170 return false;
171 }
172
counter(RenderObject * object,const AtomicString & counterName,bool alwaysCreateCounter)173 static CounterNode* counter(RenderObject* object, const AtomicString& counterName, bool alwaysCreateCounter)
174 {
175 ASSERT(object);
176
177 if (object->m_hasCounterNodeMap)
178 if (CounterMap* nodeMap = counterMaps().get(object))
179 if (CounterNode* node = nodeMap->get(counterName.impl()))
180 return node;
181
182 bool isReset = false;
183 int value = 0;
184 if (!planCounter(object, counterName, isReset, value) && !alwaysCreateCounter)
185 return 0;
186
187 CounterNode* newParent = 0;
188 CounterNode* newPreviousSibling = 0;
189 CounterNode* newNode;
190 if (findPlaceForCounter(object, counterName, isReset, newParent, newPreviousSibling)) {
191 newNode = new CounterNode(object, isReset, value);
192 newParent->insertAfter(newNode, newPreviousSibling);
193 } else {
194 // Make a reset node for counters that aren't inside an existing reset node.
195 newNode = new CounterNode(object, true, value);
196 }
197
198 CounterMap* nodeMap;
199 if (object->m_hasCounterNodeMap)
200 nodeMap = counterMaps().get(object);
201 else {
202 nodeMap = new CounterMap;
203 counterMaps().set(object, nodeMap);
204 object->m_hasCounterNodeMap = true;
205 }
206 nodeMap->set(counterName.impl(), newNode);
207
208 return newNode;
209 }
210
RenderCounter(Document * node,const CounterContent & counter)211 RenderCounter::RenderCounter(Document* node, const CounterContent& counter)
212 : RenderText(node, StringImpl::empty())
213 , m_counter(counter)
214 , m_counterNode(0)
215 {
216 }
217
renderName() const218 const char* RenderCounter::renderName() const
219 {
220 return "RenderCounter";
221 }
222
isCounter() const223 bool RenderCounter::isCounter() const
224 {
225 return true;
226 }
227
originalText() const228 PassRefPtr<StringImpl> RenderCounter::originalText() const
229 {
230 if (!parent())
231 return 0;
232
233 if (!m_counterNode)
234 m_counterNode = counter(parent(), m_counter.identifier(), true);
235
236 CounterNode* child = m_counterNode;
237 int value = child->isReset() ? child->value() : child->countInParent();
238
239 String text = listMarkerText(m_counter.listStyle(), value);
240
241 if (!m_counter.separator().isNull()) {
242 if (!child->isReset())
243 child = child->parent();
244 while (CounterNode* parent = child->parent()) {
245 text = listMarkerText(m_counter.listStyle(), child->countInParent())
246 + m_counter.separator() + text;
247 child = parent;
248 }
249 }
250
251 return text.impl();
252 }
253
dirtyLineBoxes(bool fullLayout,bool dummy)254 void RenderCounter::dirtyLineBoxes(bool fullLayout, bool dummy)
255 {
256 if (prefWidthsDirty())
257 calcPrefWidths(0);
258 RenderText::dirtyLineBoxes(fullLayout, dummy);
259 }
260
calcPrefWidths(int lead)261 void RenderCounter::calcPrefWidths(int lead)
262 {
263 setTextInternal(originalText());
264 RenderText::calcPrefWidths(lead);
265 }
266
invalidate()267 void RenderCounter::invalidate()
268 {
269 m_counterNode = 0;
270 setNeedsLayoutAndPrefWidthsRecalc();
271 }
272
destroyCounterNodeChildren(AtomicStringImpl * identifier,CounterNode * node)273 static void destroyCounterNodeChildren(AtomicStringImpl* identifier, CounterNode* node)
274 {
275 CounterNode* previous;
276 for (CounterNode* child = lastDescendant(node); child && child != node; child = previous) {
277 previous = previousInPreOrder(child);
278 child->parent()->removeChild(child);
279 ASSERT(counterMaps().get(child->renderer())->get(identifier) == child);
280 counterMaps().get(child->renderer())->remove(identifier);
281 child->renderer()->invalidateCounters();
282 delete child;
283 }
284 }
285
destroyCounterNodes(RenderObject * object)286 void RenderCounter::destroyCounterNodes(RenderObject* object)
287 {
288 CounterMaps& maps = counterMaps();
289 CounterMap* map = maps.get(object);
290 if (!map)
291 return;
292 maps.remove(object);
293
294 CounterMap::const_iterator end = map->end();
295 for (CounterMap::const_iterator it = map->begin(); it != end; ++it) {
296 CounterNode* node = it->second;
297 destroyCounterNodeChildren(it->first.get(), node);
298 if (CounterNode* parent = node->parent())
299 parent->removeChild(node);
300 delete node;
301 }
302
303 delete map;
304 }
305
306 } // namespace WebCore
307