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