|
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 |
|
33 namespace WebCore { |
|
34 |
|
35 using namespace HTMLNames; |
|
36 |
|
37 typedef HashMap<RefPtr<AtomicStringImpl>, CounterNode*> CounterMap; |
|
38 typedef HashMap<const RenderObject*, CounterMap*> CounterMaps; |
|
39 |
|
40 static CounterNode* counter(RenderObject*, const AtomicString& counterName, bool alwaysCreateCounter); |
|
41 |
|
42 static CounterMaps& counterMaps() |
|
43 { |
|
44 static CounterMaps staticCounterMaps; |
|
45 return staticCounterMaps; |
|
46 } |
|
47 |
|
48 static inline RenderObject* previousSiblingOrParent(RenderObject* object) |
|
49 { |
|
50 if (RenderObject* sibling = object->previousSibling()) |
|
51 return sibling; |
|
52 return object->parent(); |
|
53 } |
|
54 |
|
55 static CounterNode* lastDescendant(CounterNode* node) |
|
56 { |
|
57 CounterNode* last = node->lastChild(); |
|
58 if (!last) |
|
59 return 0; |
|
60 |
|
61 while (CounterNode* lastChild = last->lastChild()) |
|
62 last = lastChild; |
|
63 |
|
64 return last; |
|
65 } |
|
66 |
|
67 static CounterNode* previousInPreOrder(CounterNode* node) |
|
68 { |
|
69 CounterNode* previous = node->previousSibling(); |
|
70 if (!previous) |
|
71 return node->parent(); |
|
72 |
|
73 while (CounterNode* lastChild = previous->lastChild()) |
|
74 previous = lastChild; |
|
75 |
|
76 return previous; |
|
77 } |
|
78 |
|
79 static bool planCounter(RenderObject* object, const AtomicString& counterName, bool& isReset, int& value) |
|
80 { |
|
81 ASSERT(object); |
|
82 |
|
83 // Real text nodes don't have their own style so they can't have counters. |
|
84 // We can't even look at their styles or we'll see extra resets and increments! |
|
85 if (object->isText() && !object->isBR()) |
|
86 return false; |
|
87 |
|
88 RenderStyle* style = object->style(); |
|
89 ASSERT(style); |
|
90 |
|
91 if (const CounterDirectiveMap* directivesMap = style->counterDirectives()) { |
|
92 CounterDirectives directives = directivesMap->get(counterName.impl()); |
|
93 if (directives.m_reset) { |
|
94 value = directives.m_resetValue; |
|
95 if (directives.m_increment) |
|
96 value += directives.m_incrementValue; |
|
97 isReset = true; |
|
98 return true; |
|
99 } |
|
100 if (directives.m_increment) { |
|
101 value = directives.m_incrementValue; |
|
102 isReset = false; |
|
103 return true; |
|
104 } |
|
105 } |
|
106 |
|
107 if (counterName == "list-item") { |
|
108 if (object->isListItem()) { |
|
109 if (static_cast<RenderListItem*>(object)->hasExplicitValue()) { |
|
110 value = static_cast<RenderListItem*>(object)->explicitValue(); |
|
111 isReset = true; |
|
112 return true; |
|
113 } |
|
114 value = 1; |
|
115 isReset = false; |
|
116 return true; |
|
117 } |
|
118 if (Node* e = object->element()) { |
|
119 if (e->hasTagName(olTag)) { |
|
120 value = static_cast<HTMLOListElement*>(e)->start(); |
|
121 isReset = true; |
|
122 return true; |
|
123 } |
|
124 if (e->hasTagName(ulTag) || e->hasTagName(menuTag) || e->hasTagName(dirTag)) { |
|
125 value = 0; |
|
126 isReset = true; |
|
127 return true; |
|
128 } |
|
129 } |
|
130 } |
|
131 |
|
132 return false; |
|
133 } |
|
134 |
|
135 static bool findPlaceForCounter(RenderObject* object, const AtomicString& counterName, |
|
136 bool isReset, CounterNode*& parent, CounterNode*& previousSibling) |
|
137 { |
|
138 // Find the appropriate previous sibling for insertion into the parent node |
|
139 // by searching in render tree order for a child of the counter. |
|
140 parent = 0; |
|
141 previousSibling = 0; |
|
142 RenderObject* resetCandidate = isReset ? object->parent() : previousSiblingOrParent(object); |
|
143 RenderObject* prevCounterCandidate = object; |
|
144 CounterNode* candidateCounter = 0; |
|
145 while ((prevCounterCandidate = prevCounterCandidate->previousInPreOrder())) { |
|
146 CounterNode* c = counter(prevCounterCandidate, counterName, false); |
|
147 if (prevCounterCandidate == resetCandidate) { |
|
148 if (!candidateCounter) |
|
149 candidateCounter = c; |
|
150 if (candidateCounter) { |
|
151 if (candidateCounter->isReset()) { |
|
152 parent = candidateCounter; |
|
153 previousSibling = 0; |
|
154 } else { |
|
155 parent = candidateCounter->parent(); |
|
156 previousSibling = candidateCounter; |
|
157 } |
|
158 return true; |
|
159 } |
|
160 resetCandidate = previousSiblingOrParent(resetCandidate); |
|
161 } else if (c) { |
|
162 if (c->isReset()) |
|
163 candidateCounter = 0; |
|
164 else if (!candidateCounter) |
|
165 candidateCounter = c; |
|
166 } |
|
167 } |
|
168 |
|
169 return false; |
|
170 } |
|
171 |
|
172 static CounterNode* counter(RenderObject* object, const AtomicString& counterName, bool alwaysCreateCounter) |
|
173 { |
|
174 ASSERT(object); |
|
175 |
|
176 if (object->m_hasCounterNodeMap) |
|
177 if (CounterMap* nodeMap = counterMaps().get(object)) |
|
178 if (CounterNode* node = nodeMap->get(counterName.impl())) |
|
179 return node; |
|
180 |
|
181 bool isReset = false; |
|
182 int value = 0; |
|
183 if (!planCounter(object, counterName, isReset, value) && !alwaysCreateCounter) |
|
184 return 0; |
|
185 |
|
186 CounterNode* newParent = 0; |
|
187 CounterNode* newPreviousSibling = 0; |
|
188 CounterNode* newNode; |
|
189 if (findPlaceForCounter(object, counterName, isReset, newParent, newPreviousSibling)) { |
|
190 newNode = new CounterNode(object, isReset, value); |
|
191 newParent->insertAfter(newNode, newPreviousSibling); |
|
192 } else { |
|
193 // Make a reset node for counters that aren't inside an existing reset node. |
|
194 newNode = new CounterNode(object, true, value); |
|
195 } |
|
196 |
|
197 CounterMap* nodeMap; |
|
198 if (object->m_hasCounterNodeMap) |
|
199 nodeMap = counterMaps().get(object); |
|
200 else { |
|
201 nodeMap = new CounterMap; |
|
202 counterMaps().set(object, nodeMap); |
|
203 object->m_hasCounterNodeMap = true; |
|
204 } |
|
205 nodeMap->set(counterName.impl(), newNode); |
|
206 |
|
207 return newNode; |
|
208 } |
|
209 |
|
210 RenderCounter::RenderCounter(Document* node, const CounterContent& counter) |
|
211 : RenderText(node, StringImpl::empty()) |
|
212 , m_counter(counter) |
|
213 , m_counterNode(0) |
|
214 { |
|
215 } |
|
216 |
|
217 const char* RenderCounter::renderName() const |
|
218 { |
|
219 return "RenderCounter"; |
|
220 } |
|
221 |
|
222 bool RenderCounter::isRenderCounter() const |
|
223 { |
|
224 return true; |
|
225 } |
|
226 |
|
227 PassRefPtr<StringImpl> RenderCounter::originalText() const |
|
228 { |
|
229 if (!parent()) |
|
230 return 0; |
|
231 |
|
232 if (!m_counterNode) |
|
233 m_counterNode = counter(parent(), m_counter.identifier(), true); |
|
234 |
|
235 CounterNode* child = m_counterNode; |
|
236 int value = child->isReset() ? child->value() : child->countInParent(); |
|
237 |
|
238 String text = listMarkerText(m_counter.listStyle(), value); |
|
239 |
|
240 if (!m_counter.separator().isNull()) { |
|
241 if (!child->isReset()) |
|
242 child = child->parent(); |
|
243 while (CounterNode* parent = child->parent()) { |
|
244 text = listMarkerText(m_counter.listStyle(), child->countInParent()) |
|
245 + m_counter.separator() + text; |
|
246 child = parent; |
|
247 } |
|
248 } |
|
249 |
|
250 return text.impl(); |
|
251 } |
|
252 |
|
253 void RenderCounter::dirtyLineBoxes(bool fullLayout, bool dummy) |
|
254 { |
|
255 if (prefWidthsDirty()) |
|
256 calcPrefWidths(0); |
|
257 RenderText::dirtyLineBoxes(fullLayout, dummy); |
|
258 } |
|
259 |
|
260 void RenderCounter::calcPrefWidths(int lead) |
|
261 { |
|
262 setTextInternal(originalText()); |
|
263 RenderText::calcPrefWidths(lead); |
|
264 } |
|
265 |
|
266 static void destroyCounterNodeChildren(AtomicStringImpl* identifier, CounterNode* node) |
|
267 { |
|
268 CounterNode* previous; |
|
269 for (CounterNode* child = lastDescendant(node); child && child != node; child = previous) { |
|
270 previous = previousInPreOrder(child); |
|
271 child->parent()->removeChild(child); |
|
272 ASSERT(counterMaps().get(child->renderer())->get(identifier) == child); |
|
273 counterMaps().get(child->renderer())->remove(identifier); |
|
274 delete child; |
|
275 } |
|
276 } |
|
277 |
|
278 void RenderCounter::destroyCounterNodes(RenderObject* object) |
|
279 { |
|
280 CounterMaps& maps = counterMaps(); |
|
281 CounterMap* map = maps.get(object); |
|
282 if (!map) |
|
283 return; |
|
284 maps.remove(object); |
|
285 |
|
286 CounterMap::const_iterator end = map->end(); |
|
287 for (CounterMap::const_iterator it = map->begin(); it != end; ++it) { |
|
288 CounterNode* node = it->second; |
|
289 destroyCounterNodeChildren(it->first.get(), node); |
|
290 if (CounterNode* parent = node->parent()) |
|
291 parent->removeChild(node); |
|
292 delete node; |
|
293 } |
|
294 |
|
295 delete map; |
|
296 } |
|
297 |
|
298 } // namespace WebCore |