1/*
2 * Copyright (c) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "ComplexTextControllerLinux.h"
33
34#include "Font.h"
35#include "TextRun.h"
36
37#include <unicode/normlzr.h>
38
39namespace WebCore {
40
41// Harfbuzz uses 26.6 fixed point values for pixel offsets. However, we don't
42// handle subpixel positioning so this function is used to truncate Harfbuzz
43// values to a number of pixels.
44static int truncateFixedPointToInteger(HB_Fixed value)
45{
46    return value >> 6;
47}
48
49ComplexTextController::ComplexTextController(const TextRun& run, unsigned startingX, const Font* font)
50    : m_font(font)
51    , m_run(getNormalizedTextRun(run, m_normalizedRun, m_normalizedBuffer))
52    , m_wordSpacingAdjustment(0)
53    , m_padding(0)
54    , m_padPerWordBreak(0)
55    , m_padError(0)
56    , m_letterSpacing(0)
57{
58    // Do not use |run| inside this constructor. Use |m_run| instead.
59
60    memset(&m_item, 0, sizeof(m_item));
61    // We cannot know, ahead of time, how many glyphs a given script run
62    // will produce. We take a guess that script runs will not produce more
63    // than twice as many glyphs as there are code points plus a bit of
64    // padding and fallback if we find that we are wrong.
65    createGlyphArrays((m_run.length() + 2) * 2);
66
67    m_item.log_clusters = new unsigned short[m_run.length()];
68
69    m_item.face = 0;
70    m_item.font = allocHarfbuzzFont();
71
72    m_item.item.bidiLevel = m_run.rtl();
73
74    m_item.string = m_run.characters();
75    m_item.stringLength = m_run.length();
76
77    reset(startingX);
78}
79
80ComplexTextController::~ComplexTextController()
81{
82    fastFree(m_item.font);
83    deleteGlyphArrays();
84    delete[] m_item.log_clusters;
85}
86
87bool ComplexTextController::isWordBreak(unsigned index)
88{
89    return index && isCodepointSpace(m_item.string[index]) && !isCodepointSpace(m_item.string[index - 1]);
90}
91
92int ComplexTextController::determineWordBreakSpacing(unsigned logClustersIndex)
93{
94    int wordBreakSpacing = 0;
95    // The first half of the conjunction works around the case where
96    // output glyphs aren't associated with any codepoints by the
97    // clusters log.
98    if (logClustersIndex < m_item.item.length
99        && isWordBreak(m_item.item.pos + logClustersIndex)) {
100        wordBreakSpacing = m_wordSpacingAdjustment;
101
102        if (m_padding > 0) {
103            int toPad = roundf(m_padPerWordBreak + m_padError);
104            m_padError += m_padPerWordBreak - toPad;
105
106            if (m_padding < toPad)
107                toPad = m_padding;
108            m_padding -= toPad;
109            wordBreakSpacing += toPad;
110        }
111    }
112    return wordBreakSpacing;
113}
114
115// setPadding sets a number of pixels to be distributed across the TextRun.
116// WebKit uses this to justify text.
117void ComplexTextController::setPadding(int padding)
118{
119    m_padding = padding;
120    if (!m_padding)
121        return;
122
123    // If we have padding to distribute, then we try to give an equal
124    // amount to each space. The last space gets the smaller amount, if
125    // any.
126    unsigned numWordBreaks = 0;
127
128    for (unsigned i = 0; i < m_item.stringLength; i++) {
129        if (isWordBreak(i))
130            numWordBreaks++;
131    }
132
133    if (numWordBreaks)
134        m_padPerWordBreak = m_padding / numWordBreaks;
135    else
136        m_padPerWordBreak = 0;
137}
138
139void ComplexTextController::reset(unsigned offset)
140{
141    m_indexOfNextScriptRun = 0;
142    m_offsetX = offset;
143}
144
145// Advance to the next script run, returning false when the end of the
146// TextRun has been reached.
147bool ComplexTextController::nextScriptRun()
148{
149    // Ensure we're not pointing at the small caps buffer.
150    m_item.string = m_run.characters();
151
152    if (!hb_utf16_script_run_next(0, &m_item.item, m_run.characters(), m_run.length(), &m_indexOfNextScriptRun))
153        return false;
154
155    // It is actually wrong to consider script runs at all in this code.
156    // Other WebKit code (e.g. Mac) segments complex text just by finding
157    // the longest span of text covered by a single font.
158    // But we currently need to call hb_utf16_script_run_next anyway to fill
159    // in the harfbuzz data structures to e.g. pick the correct script's shaper.
160    // So we allow that to run first, then do a second pass over the range it
161    // found and take the largest subregion that stays within a single font.
162    m_currentFontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos], false).fontData;
163    unsigned endOfRun;
164    for (endOfRun = 1; endOfRun < m_item.item.length; ++endOfRun) {
165        const SimpleFontData* nextFontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos + endOfRun], false).fontData;
166        if (nextFontData != m_currentFontData)
167            break;
168    }
169    m_item.item.length = endOfRun;
170    m_indexOfNextScriptRun = m_item.item.pos + endOfRun;
171
172    setupFontForScriptRun();
173    shapeGlyphs();
174    setGlyphXPositions(rtl());
175
176    return true;
177}
178
179float ComplexTextController::widthOfFullRun()
180{
181    float widthSum = 0;
182    while (nextScriptRun())
183        widthSum += width();
184
185    return widthSum;
186}
187
188void ComplexTextController::setupFontForScriptRun()
189{
190    FontDataVariant fontDataVariant = AutoVariant;
191    // Determine if this script run needs to be converted to small caps.
192    // nextScriptRun() will always send us a run of the same case, because a
193    // case change while in small-caps mode always results in different
194    // FontData, so we only need to check the first character's case.
195    if (m_font->isSmallCaps() && u_islower(m_item.string[m_item.item.pos])) {
196        m_smallCapsString = String(m_run.data(m_item.item.pos), m_item.item.length);
197        m_smallCapsString.makeUpper();
198        m_item.string = m_smallCapsString.characters();
199        m_item.item.pos = 0;
200        fontDataVariant = SmallCapsVariant;
201    }
202    const FontData* fontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos], false, fontDataVariant).fontData;
203    const FontPlatformData& platformData = fontData->fontDataForCharacter(' ')->platformData();
204    m_item.face = platformData.harfbuzzFace();
205    void* opaquePlatformData = const_cast<FontPlatformData*>(&platformData);
206    m_item.font->userData = opaquePlatformData;
207
208    int size = platformData.size();
209    m_item.font->x_ppem = size;
210    m_item.font->y_ppem = size;
211    // x_ and y_scale are the conversion factors from font design space (fEmSize) to 1/64th of device pixels in 16.16 format.
212    const int devicePixelFraction = 64;
213    const int multiplyFor16Dot16 = 1 << 16;
214    int scale = devicePixelFraction * size * multiplyFor16Dot16 / platformData.emSizeInFontUnits();
215    m_item.font->x_scale = scale;
216    m_item.font->y_scale = scale;
217}
218
219HB_FontRec* ComplexTextController::allocHarfbuzzFont()
220{
221    HB_FontRec* font = reinterpret_cast<HB_FontRec*>(fastMalloc(sizeof(HB_FontRec)));
222    memset(font, 0, sizeof(HB_FontRec));
223    font->klass = &harfbuzzSkiaClass;
224    font->userData = 0;
225
226    return font;
227}
228
229void ComplexTextController::deleteGlyphArrays()
230{
231    delete[] m_item.glyphs;
232    delete[] m_item.attributes;
233    delete[] m_item.advances;
234    delete[] m_item.offsets;
235    delete[] m_glyphs16;
236    delete[] m_xPositions;
237}
238
239void ComplexTextController::createGlyphArrays(int size)
240{
241    m_item.glyphs = new HB_Glyph[size];
242    m_item.attributes = new HB_GlyphAttributes[size];
243    m_item.advances = new HB_Fixed[size];
244    m_item.offsets = new HB_FixedPoint[size];
245
246    m_glyphs16 = new uint16_t[size];
247    m_xPositions = new SkScalar[size];
248
249    m_item.num_glyphs = size;
250    m_glyphsArrayCapacity = size; // Save the GlyphArrays size.
251    resetGlyphArrays();
252}
253
254void ComplexTextController::resetGlyphArrays()
255{
256    int size = m_item.num_glyphs;
257    // All the types here don't have pointers. It is safe to reset to
258    // zero unless Harfbuzz breaks the compatibility in the future.
259    memset(m_item.glyphs, 0, size * sizeof(HB_Glyph));
260    memset(m_item.attributes, 0, size * sizeof(HB_GlyphAttributes));
261    memset(m_item.advances, 0, size * sizeof(HB_Fixed));
262    memset(m_item.offsets, 0, size * sizeof(HB_FixedPoint));
263    memset(m_glyphs16, 0, size * sizeof(uint16_t));
264    memset(m_xPositions, 0, size * sizeof(SkScalar));
265}
266
267void ComplexTextController::shapeGlyphs()
268{
269    // HB_ShapeItem() resets m_item.num_glyphs. If the previous call to
270    // HB_ShapeItem() used less space than was available, the capacity of
271    // the array may be larger than the current value of m_item.num_glyphs.
272    // So, we need to reset the num_glyphs to the capacity of the array.
273    m_item.num_glyphs = m_glyphsArrayCapacity;
274    resetGlyphArrays();
275    while (!HB_ShapeItem(&m_item)) {
276        // We overflowed our arrays. Resize and retry.
277        // HB_ShapeItem fills in m_item.num_glyphs with the needed size.
278        deleteGlyphArrays();
279        // The |+ 1| here is a workaround for a bug in Harfbuzz: the Khmer
280        // shaper (at least) can fail because of insufficient glyph buffers
281        // and request 0 additional glyphs: throwing us into an infinite
282        // loop.
283        createGlyphArrays(m_item.num_glyphs + 1);
284    }
285}
286
287void ComplexTextController::setGlyphXPositions(bool isRTL)
288{
289    const double rtlFlip = isRTL ? -1 : 1;
290    double position = 0;
291
292    // logClustersIndex indexes logClusters for the first codepoint of the current glyph.
293    // Each time we advance a glyph, we skip over all the codepoints that contributed to the current glyph.
294    int logClustersIndex = 0;
295
296    // Iterate through the glyphs in logical order, flipping for RTL where necessary.
297    // Glyphs are positioned starting from m_offsetX; in RTL mode they go leftwards from there.
298    for (size_t i = 0; i < m_item.num_glyphs; ++i) {
299        while (static_cast<unsigned>(logClustersIndex) < m_item.item.length && logClusters()[logClustersIndex] < i)
300            logClustersIndex++;
301
302        // If the current glyph is just after a space, add in the word spacing.
303        position += determineWordBreakSpacing(logClustersIndex);
304
305        m_glyphs16[i] = m_item.glyphs[i];
306        double offsetX = truncateFixedPointToInteger(m_item.offsets[i].x);
307        double advance = truncateFixedPointToInteger(m_item.advances[i]);
308        if (isRTL)
309            offsetX -= advance;
310
311        m_xPositions[i] = m_offsetX + (position * rtlFlip) + offsetX;
312
313        if (m_currentFontData->isZeroWidthSpaceGlyph(m_glyphs16[i]))
314            continue;
315
316        // At the end of each cluster, add in the letter spacing.
317        if (i + 1 == m_item.num_glyphs || m_item.attributes[i + 1].clusterStart)
318            position += m_letterSpacing;
319
320        position += advance;
321    }
322    m_pixelWidth = std::max(position, 0.0);
323    m_offsetX += m_pixelWidth * rtlFlip;
324}
325
326void ComplexTextController::normalizeSpacesAndMirrorChars(const UChar* source, bool rtl, UChar* destination, int length)
327{
328    int position = 0;
329    bool error = false;
330    // Iterate characters in source and mirror character if needed.
331    while (position < length) {
332        UChar32 character;
333        int nextPosition = position;
334        U16_NEXT(source, nextPosition, length, character);
335        if (Font::treatAsSpace(character))
336            character = ' ';
337        else if (Font::treatAsZeroWidthSpace(character))
338            character = zeroWidthSpace;
339        else if (rtl)
340            character = u_charMirror(character);
341        U16_APPEND(destination, position, length, character, error);
342        ASSERT(!error);
343        position = nextPosition;
344    }
345}
346
347const TextRun& ComplexTextController::getNormalizedTextRun(const TextRun& originalRun, OwnPtr<TextRun>& normalizedRun, OwnArrayPtr<UChar>& normalizedBuffer)
348{
349    // Normalize the text run in three ways:
350    // 1) Convert the |originalRun| to NFC normalized form if combining diacritical marks
351    // (U+0300..) are used in the run. This conversion is necessary since most OpenType
352    // fonts (e.g., Arial) don't have substitution rules for the diacritical marks in
353    // their GSUB tables.
354    //
355    // Note that we don't use the icu::Normalizer::isNormalized(UNORM_NFC) API here since
356    // the API returns FALSE (= not normalized) for complex runs that don't require NFC
357    // normalization (e.g., Arabic text). Unless the run contains the diacritical marks,
358    // Harfbuzz will do the same thing for us using the GSUB table.
359    // 2) Convert spacing characters into plain spaces, as some fonts will provide glyphs
360    // for characters like '\n' otherwise.
361    // 3) Convert mirrored characters such as parenthesis for rtl text.
362
363    // Convert to NFC form if the text has diacritical marks.
364    icu::UnicodeString normalizedString;
365    UErrorCode error = U_ZERO_ERROR;
366
367    for (int16_t i = 0; i < originalRun.length(); ++i) {
368        UChar ch = originalRun[i];
369        if (::ublock_getCode(ch) == UBLOCK_COMBINING_DIACRITICAL_MARKS) {
370            icu::Normalizer::normalize(icu::UnicodeString(originalRun.characters(),
371                                       originalRun.length()), UNORM_NFC, 0 /* no options */,
372                                       normalizedString, error);
373            if (U_FAILURE(error))
374                return originalRun;
375            break;
376        }
377    }
378
379    // Normalize space and mirror parenthesis for rtl text.
380    int normalizedBufferLength;
381    const UChar* sourceText;
382    if (normalizedString.isEmpty()) {
383        normalizedBufferLength = originalRun.length();
384        sourceText = originalRun.characters();
385    } else {
386        normalizedBufferLength = normalizedString.length();
387        sourceText = normalizedString.getBuffer();
388    }
389
390    normalizedBuffer = adoptArrayPtr(new UChar[normalizedBufferLength + 1]);
391
392    normalizeSpacesAndMirrorChars(sourceText, originalRun.rtl(), normalizedBuffer.get(), normalizedBufferLength);
393
394    normalizedRun.set(new TextRun(originalRun));
395    normalizedRun->setText(normalizedBuffer.get(), normalizedBufferLength);
396    return *normalizedRun;
397}
398
399} // namespace WebCore
400