1/*
2 * Copyright (C) 2011 Apple 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 "RenderCombineText.h"
23
24#include "RenderBlock.h"
25#include "StyleInheritedData.h"
26
27namespace WebCore {
28
29const float textCombineMargin = 1.15f; // Allow em + 15% margin
30
31RenderCombineText::RenderCombineText(Node* node, PassRefPtr<StringImpl> string)
32     : RenderText(node, string)
33     , m_combinedTextWidth(0)
34     , m_isCombined(false)
35     , m_needsFontUpdate(false)
36{
37}
38
39void RenderCombineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
40{
41    setStyleInternal(RenderStyle::clone(style()));
42    RenderText::styleDidChange(diff, oldStyle);
43
44    if (m_isCombined) {
45        RenderText::setTextInternal(originalText()); // This RenderCombineText has been combined once. Restore the original text for the next combineText().
46        m_isCombined = false;
47    }
48
49    m_needsFontUpdate = true;
50}
51
52void RenderCombineText::setTextInternal(PassRefPtr<StringImpl> text)
53{
54    RenderText::setTextInternal(text);
55
56    m_needsFontUpdate = true;
57}
58
59float RenderCombineText::width(unsigned from, unsigned length, const Font& font, float xPosition, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
60{
61    if (!characters())
62        return 0;
63
64    if (m_isCombined)
65        return font.size();
66
67    return RenderText::width(from, length, font, xPosition, fallbackFonts, glyphOverflow);
68}
69
70void RenderCombineText::adjustTextOrigin(FloatPoint& textOrigin, const FloatRect& boxRect) const
71{
72    if (m_isCombined)
73        textOrigin.move(boxRect.height() / 2 - ceilf(m_combinedTextWidth) / 2, style()->font().pixelSize());
74}
75
76void RenderCombineText::getStringToRender(int start, String& string, int& length) const
77{
78    ASSERT(start >= 0);
79    if (m_isCombined) {
80        string = originalText();
81        length = string.length();
82        return;
83    }
84
85    string = text();
86    string = string.substringSharingImpl(static_cast<unsigned>(start), length);
87}
88
89void RenderCombineText::combineText()
90{
91    if (!m_needsFontUpdate)
92        return;
93
94    m_isCombined = false;
95    m_needsFontUpdate = false;
96
97    // CSS3 spec says text-combine works only in vertical writing mode.
98    if (style()->isHorizontalWritingMode())
99        return;
100
101    TextRun run = RenderBlock::constructTextRun(this, originalFont(), this, style());
102    FontDescription description = originalFont().fontDescription();
103    float emWidth = description.computedSize() * textCombineMargin;
104    bool shouldUpdateFont = false;
105
106    description.setOrientation(Horizontal); // We are going to draw combined text horizontally.
107    m_combinedTextWidth = originalFont().width(run);
108    m_isCombined = m_combinedTextWidth <= emWidth;
109
110    FontSelector* fontSelector = style()->font().fontSelector();
111
112    if (m_isCombined)
113        shouldUpdateFont = style()->setFontDescription(description); // Need to change font orientation to horizontal.
114    else {
115        // Need to try compressed glyphs.
116        static const FontWidthVariant widthVariants[] = { HalfWidth, ThirdWidth, QuarterWidth };
117        for (size_t i = 0 ; i < WTF_ARRAY_LENGTH(widthVariants) ; ++i) {
118            description.setWidthVariant(widthVariants[i]);
119            Font compressedFont = Font(description, style()->font().letterSpacing(), style()->font().wordSpacing());
120            compressedFont.update(fontSelector);
121            float runWidth = compressedFont.width(run);
122            if (runWidth <= emWidth) {
123                m_combinedTextWidth = runWidth;
124                m_isCombined = true;
125
126                // Replace my font with the new one.
127                shouldUpdateFont = style()->setFontDescription(description);
128                break;
129            }
130        }
131    }
132
133    if (!m_isCombined)
134        shouldUpdateFont = style()->setFontDescription(originalFont().fontDescription());
135
136    if (shouldUpdateFont)
137        style()->font().update(fontSelector);
138
139    if (m_isCombined) {
140        DEFINE_STATIC_LOCAL(String, objectReplacementCharacterString, (&objectReplacementCharacter, 1));
141        RenderText::setTextInternal(objectReplacementCharacterString.impl());
142    }
143}
144
145} // namespace WebCore
146