blob: b5eda9325ab4f665812c48ac1107b6a3afc52331 [file] [log] [blame]
/*
* Copyright (c) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "ComplexTextControllerLinux.h"
#include "Font.h"
#include <unicode/normlzr.h>
namespace WebCore {
// Harfbuzz uses 26.6 fixed point values for pixel offsets. However, we don't
// handle subpixel positioning so this function is used to truncate Harfbuzz
// values to a number of pixels.
static int truncateFixedPointToInteger(HB_Fixed value)
{
return value >> 6;
}
ComplexTextController::ComplexTextController(const TextRun& run, unsigned startingX, const Font* font)
: m_font(font)
, m_startingX(startingX)
, m_offsetX(m_startingX)
, m_run(getNormalizedTextRun(run, m_normalizedRun, m_normalizedBuffer))
, m_iterateBackwards(m_run.rtl())
, m_wordSpacingAdjustment(0)
, m_padding(0)
, m_padPerWordBreak(0)
, m_padError(0)
, m_letterSpacing(0)
{
// Do not use |run| inside this constructor. Use |m_run| instead.
memset(&m_item, 0, sizeof(m_item));
// We cannot know, ahead of time, how many glyphs a given script run
// will produce. We take a guess that script runs will not produce more
// than twice as many glyphs as there are code points plus a bit of
// padding and fallback if we find that we are wrong.
createGlyphArrays((m_run.length() + 2) * 2);
m_item.log_clusters = new unsigned short[m_run.length()];
m_item.face = 0;
m_item.font = allocHarfbuzzFont();
m_item.item.bidiLevel = m_run.rtl();
m_item.string = m_run.characters();
m_item.stringLength = m_run.length();
reset();
}
ComplexTextController::~ComplexTextController()
{
fastFree(m_item.font);
deleteGlyphArrays();
delete[] m_item.log_clusters;
}
bool ComplexTextController::isWordBreak(unsigned index)
{
return index && isCodepointSpace(m_item.string[index]) && !isCodepointSpace(m_item.string[index - 1]);
}
int ComplexTextController::determineWordBreakSpacing(unsigned logClustersIndex)
{
int wordBreakSpacing = 0;
// The first half of the conjunction works around the case where
// output glyphs aren't associated with any codepoints by the
// clusters log.
if (logClustersIndex < m_item.item.length
&& isWordBreak(m_item.item.pos + logClustersIndex)) {
wordBreakSpacing = m_wordSpacingAdjustment;
if (m_padding > 0) {
int toPad = roundf(m_padPerWordBreak + m_padError);
m_padError += m_padPerWordBreak - toPad;
if (m_padding < toPad)
toPad = m_padding;
m_padding -= toPad;
wordBreakSpacing += toPad;
}
}
return wordBreakSpacing;
}
// setPadding sets a number of pixels to be distributed across the TextRun.
// WebKit uses this to justify text.
void ComplexTextController::setPadding(int padding)
{
m_padding = padding;
if (!m_padding)
return;
// If we have padding to distribute, then we try to give an equal
// amount to each space. The last space gets the smaller amount, if
// any.
unsigned numWordBreaks = 0;
for (unsigned i = 0; i < m_item.stringLength; i++) {
if (isWordBreak(i))
numWordBreaks++;
}
if (numWordBreaks)
m_padPerWordBreak = m_padding / numWordBreaks;
else
m_padPerWordBreak = 0;
}
void ComplexTextController::reset()
{
if (m_iterateBackwards)
m_indexOfNextScriptRun = m_run.length() - 1;
else
m_indexOfNextScriptRun = 0;
m_offsetX = m_startingX;
}
void ComplexTextController::setBackwardsIteration(bool isBackwards)
{
m_iterateBackwards = isBackwards;
reset();
}
// Advance to the next script run, returning false when the end of the
// TextRun has been reached.
bool ComplexTextController::nextScriptRun()
{
if (m_iterateBackwards) {
// In right-to-left mode we need to render the shaped glyph backwards and
// also render the script runs themselves backwards. So given a TextRun:
// AAAAAAACTTTTTTT (A = Arabic, C = Common, T = Thai)
// we render:
// TTTTTTCAAAAAAA
// (and the glyphs in each A, C and T section are backwards too)
if (!hb_utf16_script_run_prev(&m_numCodePoints, &m_item.item, m_run.characters(), m_run.length(), &m_indexOfNextScriptRun))
return false;
m_currentFontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos], false).fontData;
} else {
if (!hb_utf16_script_run_next(&m_numCodePoints, &m_item.item, m_run.characters(), m_run.length(), &m_indexOfNextScriptRun))
return false;
// It is actually wrong to consider script runs at all in this code.
// Other WebKit code (e.g. Mac) segments complex text just by finding
// the longest span of text covered by a single font.
// But we currently need to call hb_utf16_script_run_next anyway to fill
// in the harfbuzz data structures to e.g. pick the correct script's shaper.
// So we allow that to run first, then do a second pass over the range it
// found and take the largest subregion that stays within a single font.
m_currentFontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos], false).fontData;
unsigned endOfRun;
for (endOfRun = 1; endOfRun < m_item.item.length; ++endOfRun) {
const SimpleFontData* nextFontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos + endOfRun], false).fontData;
if (nextFontData != m_currentFontData)
break;
}
m_item.item.length = endOfRun;
m_indexOfNextScriptRun = m_item.item.pos + endOfRun;
}
setupFontForScriptRun();
shapeGlyphs();
setGlyphXPositions(rtl());
return true;
}
float ComplexTextController::widthOfFullRun()
{
float widthSum = 0;
while (nextScriptRun())
widthSum += width();
return widthSum;
}
void ComplexTextController::setupFontForScriptRun()
{
const FontData* fontData = m_font->glyphDataForCharacter(m_item.string[m_item.item.pos], false).fontData;
const FontPlatformData& platformData = fontData->fontDataForCharacter(' ')->platformData();
m_item.face = platformData.harfbuzzFace();
void* opaquePlatformData = const_cast<FontPlatformData*>(&platformData);
m_item.font->userData = opaquePlatformData;
}
HB_FontRec* ComplexTextController::allocHarfbuzzFont()
{
HB_FontRec* font = reinterpret_cast<HB_FontRec*>(fastMalloc(sizeof(HB_FontRec)));
memset(font, 0, sizeof(HB_FontRec));
font->klass = &harfbuzzSkiaClass;
font->userData = 0;
// The values which harfbuzzSkiaClass returns are already scaled to
// pixel units, so we just set all these to one to disable further
// scaling.
font->x_ppem = 1;
font->y_ppem = 1;
font->x_scale = 1;
font->y_scale = 1;
return font;
}
void ComplexTextController::deleteGlyphArrays()
{
delete[] m_item.glyphs;
delete[] m_item.attributes;
delete[] m_item.advances;
delete[] m_item.offsets;
delete[] m_glyphs16;
delete[] m_xPositions;
}
void ComplexTextController::createGlyphArrays(int size)
{
m_item.glyphs = new HB_Glyph[size];
m_item.attributes = new HB_GlyphAttributes[size];
m_item.advances = new HB_Fixed[size];
m_item.offsets = new HB_FixedPoint[size];
m_glyphs16 = new uint16_t[size];
m_xPositions = new SkScalar[size];
m_item.num_glyphs = size;
m_glyphsArrayCapacity = size; // Save the GlyphArrays size.
resetGlyphArrays();
}
void ComplexTextController::resetGlyphArrays()
{
int size = m_item.num_glyphs;
// All the types here don't have pointers. It is safe to reset to
// zero unless Harfbuzz breaks the compatibility in the future.
memset(m_item.glyphs, 0, size * sizeof(HB_Glyph));
memset(m_item.attributes, 0, size * sizeof(HB_GlyphAttributes));
memset(m_item.advances, 0, size * sizeof(HB_Fixed));
memset(m_item.offsets, 0, size * sizeof(HB_FixedPoint));
memset(m_glyphs16, 0, size * sizeof(uint16_t));
memset(m_xPositions, 0, size * sizeof(SkScalar));
}
void ComplexTextController::shapeGlyphs()
{
// HB_ShapeItem() resets m_item.num_glyphs. If the previous call to
// HB_ShapeItem() used less space than was available, the capacity of
// the array may be larger than the current value of m_item.num_glyphs.
// So, we need to reset the num_glyphs to the capacity of the array.
m_item.num_glyphs = m_glyphsArrayCapacity;
resetGlyphArrays();
while (!HB_ShapeItem(&m_item)) {
// We overflowed our arrays. Resize and retry.
// HB_ShapeItem fills in m_item.num_glyphs with the needed size.
deleteGlyphArrays();
// The |+ 1| here is a workaround for a bug in Harfbuzz: the Khmer
// shaper (at least) can fail because of insufficient glyph buffers
// and request 0 additional glyphs: throwing us into an infinite
// loop.
createGlyphArrays(m_item.num_glyphs + 1);
}
}
void ComplexTextController::setGlyphXPositions(bool isRTL)
{
double position = 0;
// logClustersIndex indexes logClusters for the first (or last when
// RTL) codepoint of the current glyph. Each time we advance a glyph,
// we skip over all the codepoints that contributed to the current
// glyph.
int logClustersIndex = 0;
if (isRTL) {
logClustersIndex = m_item.num_glyphs - 1;
// Glyphs are stored in logical order, but for layout purposes we
// always go left to right.
for (int i = m_item.num_glyphs - 1; i >= 0; --i) {
if (!m_currentFontData->isZeroWidthSpaceGlyph(m_glyphs16[i])) {
// Whitespace must be laid out in logical order, so when inserting
// spaces in RTL (but iterating in LTR order) we must insert spaces
// _before_ the next glyph.
if (static_cast<unsigned>(i + 1) >= m_item.num_glyphs || m_item.attributes[i + 1].clusterStart)
position += m_letterSpacing;
position += determineWordBreakSpacing(logClustersIndex);
}
m_glyphs16[i] = m_item.glyphs[i];
double offsetX = truncateFixedPointToInteger(m_item.offsets[i].x);
m_xPositions[i] = m_offsetX + position + offsetX;
while (logClustersIndex > 0 && logClusters()[logClustersIndex] == i)
logClustersIndex--;
if (!m_currentFontData->isZeroWidthSpaceGlyph(m_glyphs16[i]))
position += truncateFixedPointToInteger(m_item.advances[i]);
}
} else {
for (size_t i = 0; i < m_item.num_glyphs; ++i) {
m_glyphs16[i] = m_item.glyphs[i];
double offsetX = truncateFixedPointToInteger(m_item.offsets[i].x);
m_xPositions[i] = m_offsetX + position + offsetX;
if (m_currentFontData->isZeroWidthSpaceGlyph(m_glyphs16[i]))
continue;
double advance = truncateFixedPointToInteger(m_item.advances[i]);
advance += determineWordBreakSpacing(logClustersIndex);
if (m_item.attributes[i].clusterStart)
advance += m_letterSpacing;
while (static_cast<unsigned>(logClustersIndex) < m_item.item.length && logClusters()[logClustersIndex] == i)
logClustersIndex++;
position += advance;
}
}
m_pixelWidth = std::max(position, 0.0);
m_offsetX += m_pixelWidth;
}
void ComplexTextController::normalizeSpacesAndMirrorChars(const UChar* source, bool rtl, UChar* destination, int length)
{
int position = 0;
bool error = false;
// Iterate characters in source and mirror character if needed.
while (position < length) {
UChar32 character;
int nextPosition = position;
U16_NEXT(source, nextPosition, length, character);
if (Font::treatAsSpace(character))
character = ' ';
else if (Font::treatAsZeroWidthSpace(character))
character = zeroWidthSpace;
else if (rtl)
character = u_charMirror(character);
U16_APPEND(destination, position, length, character, error);
ASSERT(!error);
position = nextPosition;
}
}
const TextRun& ComplexTextController::getNormalizedTextRun(const TextRun& originalRun, OwnPtr<TextRun>& normalizedRun, OwnArrayPtr<UChar>& normalizedBuffer)
{
// Normalize the text run in three ways:
// 1) Convert the |originalRun| to NFC normalized form if combining diacritical marks
// (U+0300..) are used in the run. This conversion is necessary since most OpenType
// fonts (e.g., Arial) don't have substitution rules for the diacritical marks in
// their GSUB tables.
//
// Note that we don't use the icu::Normalizer::isNormalized(UNORM_NFC) API here since
// the API returns FALSE (= not normalized) for complex runs that don't require NFC
// normalization (e.g., Arabic text). Unless the run contains the diacritical marks,
// Harfbuzz will do the same thing for us using the GSUB table.
// 2) Convert spacing characters into plain spaces, as some fonts will provide glyphs
// for characters like '\n' otherwise.
// 3) Convert mirrored characters such as parenthesis for rtl text.
// Convert to NFC form if the text has diacritical marks.
icu::UnicodeString normalizedString;
UErrorCode error = U_ZERO_ERROR;
for (int16_t i = 0; i < originalRun.length(); ++i) {
UChar ch = originalRun[i];
if (::ublock_getCode(ch) == UBLOCK_COMBINING_DIACRITICAL_MARKS) {
icu::Normalizer::normalize(icu::UnicodeString(originalRun.characters(),
originalRun.length()), UNORM_NFC, 0 /* no options */,
normalizedString, error);
if (U_FAILURE(error))
return originalRun;
break;
}
}
// Normalize space and mirror parenthesis for rtl text.
int normalizedBufferLength;
const UChar* sourceText;
if (normalizedString.isEmpty()) {
normalizedBufferLength = originalRun.length();
sourceText = originalRun.characters();
} else {
normalizedBufferLength = normalizedString.length();
sourceText = normalizedString.getBuffer();
}
normalizedBuffer.set(new UChar[normalizedBufferLength + 1]);
normalizeSpacesAndMirrorChars(sourceText, originalRun.rtl(), normalizedBuffer.get(), normalizedBufferLength);
normalizedRun.set(new TextRun(originalRun));
normalizedRun->setText(normalizedBuffer.get(), normalizedBufferLength);
return *normalizedRun;
}
} // namespace WebCore