/** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc. * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include "config.h" #include "RenderListMarker.h" #include "CachedImage.h" #include "Document.h" #include "GraphicsContext.h" #include "ListMarkerBox.h" #include "RenderView.h" #include "RenderListItem.h" using namespace std; namespace WebCore { const int cMarkerPadding = 7; static DeprecatedString toRoman(int number, bool upper) { DeprecatedString roman; const UChar ldigits[] = { 'i', 'v', 'x', 'l', 'c', 'd', 'm' }; const UChar udigits[] = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' }; const UChar* digits = upper ? udigits : ldigits; int d = 0; do { int num = number % 10; if (num % 5 < 4) for (int i = num % 5; i > 0; i--) roman.insert(0, digits[d]); if (num >= 4 && num <= 8) roman.insert(0, digits[d + 1]); if (num == 9) roman.insert(0, digits[d + 2]); if (num % 5 == 4) roman.insert(0, digits[d]); number /= 10; d += 2; } while (number); return roman; } static DeprecatedString toLetterString(int number, int letterA) { if (number < 2) return DeprecatedChar(letterA); // match WinIE (A.) not FireFox (0.) DeprecatedString letterString; while (number > 0) { int onesDigit = (number - 1) % 26; letterString = DeprecatedChar(letterA + onesDigit) + letterString; number -= onesDigit; number /= 26; } return letterString; } static DeprecatedString toHebrew(int number) { const DeprecatedChar tenDigit[] = {1497, 1499, 1500, 1502, 1504, 1505, 1506, 1508, 1510}; DeprecatedString letter; if (number > 999) { letter = toHebrew(number / 1000) + "'"; number = number % 1000; } int fourHundreds = number / 400; for (int i = 0; i < fourHundreds; i++) letter += DeprecatedChar(1511 + 3); number %= 400; if (number / 100) letter += DeprecatedChar(1511 + (number / 100) - 1); number %= 100; if (number == 15 || number == 16) { letter += DeprecatedChar(1487 + 9); letter += DeprecatedChar(1487 + number - 9); } else { int tens = number / 10; if (tens) letter += tenDigit[tens - 1]; number = number % 10; if (number) letter += DeprecatedChar(1487 + number); } return letter; } RenderListMarker::RenderListMarker(Document* document) : RenderBox(document) , m_listImage(0) , m_selectionState(SelectionNone) { // init RenderObject attributes setInline(true); // our object is Inline setReplaced(true); // pretend to be replaced } RenderListMarker::~RenderListMarker() { if (m_listImage) m_listImage->deref(this); } void RenderListMarker::setStyle(RenderStyle* s) { if (s && style() && s->listStylePosition() != style()->listStylePosition()) setNeedsLayoutAndMinMaxRecalc(); RenderBox::setStyle(s); if (m_listImage != style()->listStyleImage()) { if (m_listImage) m_listImage->deref(this); m_listImage = style()->listStyleImage(); if (m_listImage) m_listImage->ref(this); } } InlineBox* RenderListMarker::createInlineBox(bool, bool isRootLineBox, bool) { ASSERT(!isRootLineBox); ListMarkerBox* box = new (renderArena()) ListMarkerBox(this); m_inlineBoxWrapper = box; return box; } void RenderListMarker::paint(PaintInfo& i, int _tx, int _ty) { if (i.phase != PaintPhaseForeground) return; if (style()->visibility() != VISIBLE) return; IntRect marker = getRelativeMarkerRect(); marker.move(_tx, _ty); IntRect box(_tx + m_x, _ty + m_y, m_width, m_height); if (box.y() > i.r.bottom() || box.y() + box.height() < i.r.y()) return; if (shouldPaintBackgroundOrBorder()) paintBoxDecorations(i, box.x(), box.y()); GraphicsContext* p = i.p; p->setFont(style()->font()); if (document()->printing()) { if (box.y() < i.r.y()) // This has been printed already we suppose. return; RenderView* c = view(); if (box.y() + box.height() + paddingBottom() + borderBottom() >= c->printRect().bottom()) { if (box.y() < c->truncatedAt()) c->setBestTruncatedAt(box.y(), this); // Let's print this on the next page. return; } } if (m_listImage && !m_listImage->isErrorImage()) { #if PLATFORM(MAC) if (style()->highlight() != nullAtom && !i.p->paintingDisabled()) paintCustomHighlight(_tx, _ty, style()->highlight(), true); #endif p->drawImage(m_listImage->image(), marker.location()); if (!p->paintingDisabled()) m_listImage->liveResourceAccessed(); if (selectionState() != SelectionNone) p->fillRect(selectionRect(), selectionBackgroundColor()); return; } #ifdef BOX_DEBUG p->setPen(red); p->drawRect(box.x(), box.y(), box.width(), box.height()); #endif #if PLATFORM(MAC) // FIXME: paint gap between marker and list item proper if (style()->highlight() != nullAtom && !i.p->paintingDisabled()) paintCustomHighlight(_tx, _ty, style()->highlight(), true); #endif if (selectionState() != SelectionNone) p->fillRect(selectionRect(), selectionBackgroundColor()); const Color color(style()->color()); p->setPen(color); switch(style()->listStyleType()) { case DISC: p->setFillColor(color.rgb()); p->drawEllipse(marker); return; case CIRCLE: p->setFillColor(Color::transparent); p->drawEllipse(marker); return; case SQUARE: p->setFillColor(color.rgb()); p->drawRect(marker); return; case LNONE: return; default: if (!m_item.isEmpty()) { const Font& font = style()->font(); TextRun textRun(reinterpret_cast(m_item.unicode()), m_item.length()); if (style()->direction() == LTR) { int width = font.width(textRun); p->drawText(textRun, marker.location()); UChar periodSpace[2] = { '.', ' ' }; p->drawText(TextRun(periodSpace, 2), marker.location() + IntSize(width, 0)); } else { UChar spacePeriod[2] = { ' ', '.' }; TextRun spacePeriodRun(spacePeriod, 2); int width = font.width(spacePeriodRun); p->drawText(spacePeriodRun, marker.location()); p->drawText(textRun, marker.location() + IntSize(width, 0)); } } } } void RenderListMarker::layout() { ASSERT(needsLayout()); // ### ASSERT(minMaxKnown()); if (!minMaxKnown()) calcMinMaxWidth(); setNeedsLayout(false); } void RenderListMarker::imageChanged(CachedImage *o) { if (o != m_listImage) { RenderBox::imageChanged(o); return; } if (m_width != m_listImage->imageSize().width() || m_height != m_listImage->imageSize().height()) setNeedsLayoutAndMinMaxRecalc(); else repaint(); } void RenderListMarker::calcMinMaxWidth() { ASSERT(!minMaxKnown()); if (m_listImage) { m_width = m_listImage->image()->width(); m_height = m_listImage->image()->height(); m_minWidth = m_maxWidth = m_width; setMinMaxKnown(); return; } if (m_listItem->value() < 0) // not yet calculated m_listItem->calcValue(); const Font& font = style()->font(); m_height = font.height(); switch(style()->listStyleType()) { case DISC: case CIRCLE: case SQUARE: m_width = (font.ascent() * 2 / 3 + 1) / 2 + 2; goto end; case ARMENIAN: case GEORGIAN: case CJK_IDEOGRAPHIC: case HIRAGANA: case KATAKANA: case HIRAGANA_IROHA: case KATAKANA_IROHA: case DECIMAL_LEADING_ZERO: // ### unsupported, we use decimal instead case LDECIMAL: m_item.sprintf("%d", m_listItem->value()); break; case LOWER_ROMAN: m_item = toRoman(m_listItem->value(), false); break; case UPPER_ROMAN: m_item = toRoman(m_listItem->value(), true); break; case LOWER_GREEK: { int number = m_listItem->value() - 1; int l = (number % 24); if (l>16) {l++;} // Skip GREEK SMALL LETTER FINAL SIGMA m_item = DeprecatedChar(945 + l); for (int i = 0; i < (number / 24); i++) { m_item += "'"; } break; } case HEBREW: m_item = toHebrew(m_listItem->value()); break; case LOWER_ALPHA: case LOWER_LATIN: m_item = toLetterString(m_listItem->value(), 'a'); break; case UPPER_ALPHA: case UPPER_LATIN: m_item = toLetterString(m_listItem->value(), 'A'); break; case LNONE: break; } { int itemWidth = font.width(TextRun(reinterpret_cast(m_item.unicode()), m_item.length())); UChar periodSpace[2] = { '.', ' ' }; int periodSpaceWidth = font.width(TextRun(periodSpace, 2)); m_width = itemWidth + periodSpaceWidth; } end: m_minWidth = m_width; m_maxWidth = m_width; setMinMaxKnown(); } void RenderListMarker::calcWidth() { // m_width is set in calcMinMaxWidth() bool haveImage = m_listImage && !m_listImage->isErrorImage(); const Font& font = style()->font(); if (isInside()) { if (haveImage) { if (style()->direction() == LTR) { m_marginLeft = 0; m_marginRight = cMarkerPadding; } else { m_marginLeft = cMarkerPadding; m_marginRight = 0; } } else switch (style()->listStyleType()) { case DISC: case CIRCLE: case SQUARE: if (style()->direction() == LTR) { m_marginLeft = -1; m_marginRight = font.ascent() - m_width + 1; } else { m_marginLeft = font.ascent() - m_width + 1; m_marginRight = -1; } break; default: m_marginLeft = 0; m_marginRight = 0; } } else { if (style()->direction() == LTR) { if (haveImage) m_marginLeft = -m_width - cMarkerPadding; else { int offset = font.ascent() * 2 / 3; switch (style()->listStyleType()) { case DISC: case CIRCLE: case SQUARE: m_marginLeft = -offset - cMarkerPadding - 1; break; case LNONE: m_marginLeft = 0; break; default: m_marginLeft = m_item.isEmpty() ? 0 : -m_width - offset / 2; } } } else { if (haveImage) m_marginLeft = cMarkerPadding; else { int offset = font.ascent() * 2 / 3; switch (style()->listStyleType()) { case DISC: case CIRCLE: case SQUARE: m_marginLeft = offset + cMarkerPadding + 1 - m_width; break; case LNONE: m_marginLeft = 0; break; default: m_marginLeft = m_item.isEmpty() ? 0 : offset / 2; } } } m_marginRight = -m_marginLeft - m_width; } } short RenderListMarker::lineHeight(bool, bool) const { if (!m_listImage) return m_listItem->lineHeight(false, true); return height(); } short RenderListMarker::baselinePosition(bool, bool) const { if (!m_listImage) { const Font& font = style()->font(); return font.ascent() + (lineHeight(false) - font.height())/2; } return height(); } bool RenderListMarker::isInside() const { return m_listItem->notInList() || style()->listStylePosition() == INSIDE; } IntRect RenderListMarker::getRelativeMarkerRect() { if (m_listImage && !m_listImage->isErrorImage()) return IntRect(m_x, m_y, m_listImage->imageSize().width(), m_listImage->imageSize().height()); const Font& font = style()->font(); int ascent = font.ascent(); switch (style()->listStyleType()) { case DISC: case CIRCLE: case SQUARE: { // FIXME: Are these particular rounding rules necessary? int bulletWidth = (ascent * 2 / 3 + 1) / 2; return IntRect(m_x + 1, m_y + 3 * (ascent - ascent * 2 / 3) / 2, bulletWidth, bulletWidth); } case LNONE: return IntRect(); case ARMENIAN: case CJK_IDEOGRAPHIC: case DECIMAL_LEADING_ZERO: case GEORGIAN: case HEBREW: case HIRAGANA: case HIRAGANA_IROHA: case KATAKANA: case KATAKANA_IROHA: case LDECIMAL: case LOWER_ALPHA: case LOWER_GREEK: case LOWER_LATIN: case LOWER_ROMAN: case UPPER_ALPHA: case UPPER_LATIN: case UPPER_ROMAN: break; } if (m_item.isEmpty()) return IntRect(); int itemWidth = font.width(TextRun(reinterpret_cast(m_item.unicode()), m_item.length())); UChar periodSpace[2] = { '.', ' ' }; int periodSpaceWidth = font.width(TextRun(periodSpace, 2)); return IntRect(m_x, m_y + ascent, itemWidth + periodSpaceWidth, font.height()); } void RenderListMarker::setSelectionState(SelectionState state) { m_selectionState = state; RootInlineBox* root = inlineBoxWrapper()->root(); if (root) root->setHasSelectedChildren(state != SelectionNone); containingBlock()->setSelectionState(state); } IntRect RenderListMarker::selectionRect() { if (selectionState() == SelectionNone) return IntRect(); int absx, absy; RenderBlock* cb = containingBlock(); cb->absolutePosition(absx, absy); if (cb->hasOverflowClip()) cb->layer()->subtractScrollOffset(absx, absy); RootInlineBox* root = inlineBoxWrapper()->root(); return IntRect(absx + xPos(), absy + root->selectionTop(), width(), root->selectionHeight()); } }