Home | History | Annotate | Download | only in rendering
      1 /**
      2  * Copyright (C) 2011 Nokia Inc.  All rights reserved.
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  *
     19  */
     20 
     21 #include "config.h"
     22 #include "RenderQuote.h"
     23 
     24 #include "Document.h"
     25 #include "Element.h"
     26 #include "HTMLElement.h"
     27 #include "QuotesData.h"
     28 #include "RenderStyle.h"
     29 #include <algorithm>
     30 #include <wtf/text/AtomicString.h>
     31 #include <wtf/text/CString.h>
     32 
     33 #define UNKNOWN_DEPTH -1
     34 
     35 namespace WebCore {
     36 static inline void adjustDepth(int &depth, QuoteType type)
     37 {
     38     switch (type) {
     39     case OPEN_QUOTE:
     40     case NO_OPEN_QUOTE:
     41         ++depth;
     42         break;
     43     case CLOSE_QUOTE:
     44     case NO_CLOSE_QUOTE:
     45         if (depth)
     46             --depth;
     47         break;
     48     default:
     49         ASSERT_NOT_REACHED();
     50     }
     51 }
     52 
     53 RenderQuote::RenderQuote(Document* node, QuoteType quote)
     54     : RenderText(node, StringImpl::empty())
     55     , m_type(quote)
     56     , m_depth(UNKNOWN_DEPTH)
     57     , m_next(0)
     58     , m_previous(0)
     59 {
     60 }
     61 
     62 RenderQuote::~RenderQuote()
     63 {
     64 }
     65 
     66 const char* RenderQuote::renderName() const
     67 {
     68     return "RenderQuote";
     69 }
     70 
     71 // This function places a list of quote renderers starting at "this" in the list of quote renderers already
     72 // in the document's renderer tree.
     73 // The assumptions are made (for performance):
     74 // 1. The list of quotes already in the renderers tree of the document is already in a consistent state
     75 // (All quote renderers are linked and have the correct depth set)
     76 // 2. The quote renderers of the inserted list are in a tree of renderers of their own which has been just
     77 // inserted in the main renderer tree with its root as child of some renderer.
     78 // 3. The quote renderers in the inserted list have depths consistent with their position in the list relative
     79 // to "this", thus if "this" does not need to change its depth upon insertion, the other renderers in the list don't
     80 // need to either.
     81 void RenderQuote::placeQuote()
     82 {
     83     RenderQuote* head = this;
     84     ASSERT(!head->m_previous);
     85     RenderQuote* tail = 0;
     86     for (RenderObject* predecessor = head->previousInPreOrder(); predecessor; predecessor = predecessor->previousInPreOrder()) {
     87         if (!predecessor->isQuote())
     88             continue;
     89         head->m_previous = toRenderQuote(predecessor);
     90         if (head->m_previous->m_next) {
     91             // We need to splice the list of quotes headed by head into the document's list of quotes.
     92             tail = head;
     93             while (tail->m_next)
     94                  tail = tail->m_next;
     95             tail->m_next = head->m_previous->m_next;
     96             ASSERT(tail->m_next->m_previous == head->m_previous);
     97             tail->m_next->m_previous =  tail;
     98             tail = tail->m_next; // This marks the splicing point here there may be a depth discontinuity
     99         }
    100         head->m_previous->m_next = head;
    101         ASSERT(head->m_previous->m_depth != UNKNOWN_DEPTH);
    102         break;
    103     }
    104     int newDepth;
    105     if (!head->m_previous) {
    106         newDepth = 0;
    107         goto skipNewDepthCalc;
    108     }
    109     newDepth = head->m_previous->m_depth;
    110     do {
    111         adjustDepth(newDepth, head->m_previous->m_type);
    112 skipNewDepthCalc:
    113         if (head->m_depth == newDepth) { // All remaining depth should be correct except if splicing was done.
    114             if (!tail) // We've done the post splicing section already or there was no splicing.
    115                 break;
    116             head = tail; // Continue after the splicing point
    117             tail = 0; // Mark the possible splicing point discontinuity fixed.
    118             newDepth = head->m_previous->m_depth;
    119             continue;
    120         }
    121         head->m_depth = newDepth;
    122         // FIXME: If the width and height of the quotation characters does not change we may only need to
    123         // Invalidate the renderer's area not a relayout.
    124         head->setNeedsLayoutAndPrefWidthsRecalc();
    125         head = head->m_next;
    126         if (head == tail) // We are at the splicing point
    127             tail = 0; // Mark the possible depth discontinuity fixed.
    128     } while (head);
    129 }
    130 
    131 #define ARRAY_SIZE(Carray) (sizeof(Carray) / sizeof(*Carray))
    132 #define LANGUAGE_DATA(name, languageSourceArray) { name, languageSourceArray, ARRAY_SIZE(languageSourceArray) }
    133 #define U(x) ((const UChar*)L##x)
    134 
    135 static const UChar* simpleQuotes[] = {U("\""), U("\""), U("'"), U("'")};
    136 
    137 static const UChar* englishQuotes[] = {U("\x201C"), U("\x201D"), U("\x2018"), U("\x2019")};
    138 static const UChar* norwegianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x2039"), U("\x203A") };
    139 static const UChar* romanianQuotes[] = { U("\x201E"), U("\x201D")};
    140 static const UChar* russianQuotes[] = { U("\x00AB"), U("\x00BB"), U("\x201E"), U("\x201C") };
    141 #undef U
    142 
    143 struct LanguageData {
    144     const char *name;
    145     const UChar* const* const array;
    146     const int arraySize;
    147     bool operator<(const LanguageData& compareTo) const
    148     {
    149         return strcmp(name, compareTo.name);
    150     }
    151 };
    152 
    153 // Data mast be alphabetically sorted and in all lower case for fast comparison
    154 LanguageData languageData[] = {
    155     LANGUAGE_DATA("en", englishQuotes),
    156     LANGUAGE_DATA("no", norwegianQuotes),
    157     LANGUAGE_DATA("ro", romanianQuotes),
    158     LANGUAGE_DATA("ru", russianQuotes)
    159 };
    160 #undef LANGUAGE_DATA
    161 const LanguageData* const languageDataEnd = languageData + ARRAY_SIZE(languageData);
    162 
    163 #define defaultLanguageQuotesSource simpleQuotes
    164 #define defaultLanguageQuotesCount ARRAY_SIZE(defaultLanguageQuotesSource)
    165 
    166 static QuotesData* defaultLanguageQuotesValue = 0;
    167 static const QuotesData* defaultLanguageQuotes()
    168 {
    169     if (!defaultLanguageQuotesValue) {
    170         defaultLanguageQuotesValue = QuotesData::create(defaultLanguageQuotesCount);
    171         if (!defaultLanguageQuotesValue)
    172             return 0;
    173         String* data = defaultLanguageQuotesValue->data();
    174         for (size_t i = 0; i < defaultLanguageQuotesCount; ++i)
    175             data[i] = defaultLanguageQuotesSource[i];
    176     }
    177     return defaultLanguageQuotesValue;
    178 }
    179 #undef defaultLanguageQuotesSource
    180 #undef defaultLanguageQuotesCount
    181 
    182 typedef HashMap<RefPtr<AtomicStringImpl>, QuotesData* > QuotesMap;
    183 
    184 static QuotesMap& quotesMap()
    185 {
    186     DEFINE_STATIC_LOCAL(QuotesMap, staticQuotesMap, ());
    187     return staticQuotesMap;
    188 }
    189 
    190 static const QuotesData* quotesForLanguage(AtomicStringImpl* language)
    191 {
    192     QuotesData* returnValue;
    193     AtomicString lower(language->lower());
    194     returnValue = quotesMap().get(lower.impl());
    195     if (returnValue)
    196         return returnValue;
    197     CString s(static_cast<const String&>(lower).ascii());
    198     LanguageData request = { s.buffer()->data(), 0, 0 };
    199     const LanguageData* lowerBound = std::lower_bound<const LanguageData*, const LanguageData>(languageData, languageDataEnd, request);
    200     if (lowerBound == languageDataEnd)
    201         return defaultLanguageQuotes();
    202     if (strncmp(lowerBound->name, request.name, strlen(lowerBound->name)))
    203         return defaultLanguageQuotes();
    204     returnValue = QuotesData::create(lowerBound->arraySize);
    205     if (!returnValue)
    206         return defaultLanguageQuotes();
    207     String* data = returnValue->data();
    208     for (int i = 0; i < lowerBound->arraySize; ++i)
    209         data[i] = lowerBound->array[i];
    210     quotesMap().set(lower.impl(), returnValue);
    211     return returnValue;
    212 }
    213 #undef ARRAY_SIZE
    214 
    215 static const QuotesData* defaultQuotes(const RenderObject* object)
    216 {
    217     DEFINE_STATIC_LOCAL(String, langString, ("lang"));
    218     Node* node =  object->generatingNode();
    219     Element* element;
    220     if (!node) {
    221         element = object->document()->body();
    222         if (!element)
    223             element = object->document()->documentElement();
    224     } else if (!node->isElementNode()) {
    225         element = node->parentElement();
    226         if (!element)
    227             return defaultLanguageQuotes();
    228     } else
    229       element = toElement(node);
    230     const AtomicString* language;
    231     while ((language = &element->getAttribute(langString)) && language->isNull()) {
    232         element = element->parentElement();
    233         if (!element)
    234             return defaultLanguageQuotes();
    235     }
    236     return quotesForLanguage(language->impl());
    237 }
    238 
    239 PassRefPtr<StringImpl> RenderQuote::originalText() const
    240 {
    241     if (!parent())
    242         return 0;
    243     ASSERT(m_depth != UNKNOWN_DEPTH);
    244     const QuotesData* quotes = style()->quotes();
    245     if (!quotes)
    246         quotes = defaultQuotes(this);
    247     if (!quotes->length)
    248         return emptyAtom.impl();
    249     int index = m_depth * 2;
    250     switch (m_type) {
    251     case NO_OPEN_QUOTE:
    252     case NO_CLOSE_QUOTE:
    253         return String("").impl();
    254     case CLOSE_QUOTE:
    255         if (index)
    256             --index;
    257         else
    258             ++index;
    259         break;
    260     case OPEN_QUOTE:
    261         break;
    262     default:
    263         ASSERT_NOT_REACHED();
    264         return emptyAtom.impl();
    265     }
    266     if (index >= quotes->length)
    267         index = (quotes->length-2) | (index & 1);
    268     if (index < 0)
    269         return emptyAtom.impl();
    270     return quotes->data()[index].impl();
    271 }
    272 
    273 void RenderQuote::computePreferredLogicalWidths(float lead)
    274 {
    275     setTextInternal(originalText());
    276     RenderText::computePreferredLogicalWidths(lead);
    277 }
    278 
    279 void RenderQuote::rendererSubtreeAttached(RenderObject* renderer)
    280 {
    281     if (renderer->documentBeingDestroyed())
    282         return;
    283     for (RenderObject* descendant = renderer; descendant; descendant = descendant->nextInPreOrder(renderer))
    284         if (descendant->isQuote()) {
    285             toRenderQuote(descendant)->placeQuote();
    286             break;
    287         }
    288 }
    289 
    290 void RenderQuote::rendererRemovedFromTree(RenderObject* subtreeRoot)
    291 {
    292     if (subtreeRoot->documentBeingDestroyed())
    293         return;
    294     for (RenderObject* descendant = subtreeRoot; descendant; descendant = descendant->nextInPreOrder(subtreeRoot))
    295         if (descendant->isQuote()) {
    296             RenderQuote* removedQuote = toRenderQuote(descendant);
    297             RenderQuote* lastQuoteBefore = removedQuote->m_previous;
    298             removedQuote->m_previous = 0;
    299             int depth = removedQuote->m_depth;
    300             for (descendant = descendant->nextInPreOrder(subtreeRoot); descendant; descendant = descendant->nextInPreOrder(subtreeRoot))
    301                 if (descendant->isQuote())
    302                     removedQuote = toRenderQuote(descendant);
    303             RenderQuote* quoteAfter = removedQuote->m_next;
    304             removedQuote->m_next = 0;
    305             if (lastQuoteBefore)
    306                 lastQuoteBefore->m_next = quoteAfter;
    307             if (quoteAfter) {
    308                 quoteAfter->m_previous = lastQuoteBefore;
    309                 do {
    310                     if (depth == quoteAfter->m_depth)
    311                         break;
    312                     quoteAfter->m_depth = depth;
    313                     quoteAfter->setNeedsLayoutAndPrefWidthsRecalc();
    314                     adjustDepth(depth, quoteAfter->m_type);
    315                     quoteAfter = quoteAfter->m_next;
    316                 } while (quoteAfter);
    317             }
    318             break;
    319         }
    320 }
    321 
    322 void RenderQuote::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
    323 {
    324     const QuotesData* newQuotes = style()->quotes();
    325     const QuotesData* oldQuotes = oldStyle ? oldStyle->quotes() : 0;
    326     if (!((newQuotes && oldQuotes && (*newQuotes == *oldQuotes)) || (!newQuotes && !oldQuotes)))
    327         setNeedsLayoutAndPrefWidthsRecalc();
    328     RenderText::styleDidChange(diff, oldStyle);
    329 }
    330 
    331 } // namespace WebCore
    332