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