summaryrefslogtreecommitdiffstats
path: root/WebCore/platform/graphics/WidthIterator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/platform/graphics/WidthIterator.cpp')
-rw-r--r--WebCore/platform/graphics/WidthIterator.cpp258
1 files changed, 258 insertions, 0 deletions
diff --git a/WebCore/platform/graphics/WidthIterator.cpp b/WebCore/platform/graphics/WidthIterator.cpp
new file mode 100644
index 0000000..a66b234
--- /dev/null
+++ b/WebCore/platform/graphics/WidthIterator.cpp
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2003, 2006, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Holger Hans Peter Freyther
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "WidthIterator.h"
+
+#include "Font.h"
+#include "GlyphBuffer.h"
+#include "SimpleFontData.h"
+#include <wtf/MathExtras.h>
+
+#if USE(ICU_UNICODE)
+#include <unicode/unorm.h>
+#endif
+
+using namespace WTF;
+using namespace Unicode;
+
+namespace WebCore {
+
+// According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
+static const uint8_t hiraganaKatakanaVoicingMarksCombiningClass = 8;
+
+WidthIterator::WidthIterator(const Font* font, const TextRun& run)
+ : m_font(font)
+ , m_run(run)
+ , m_end(run.length())
+ , m_currentCharacter(0)
+ , m_runWidthSoFar(0)
+ , m_finalRoundingWidth(0)
+{
+ // If the padding is non-zero, count the number of spaces in the run
+ // and divide that by the padding for per space addition.
+ m_padding = m_run.padding();
+ if (!m_padding)
+ m_padPerSpace = 0;
+ else {
+ float numSpaces = 0;
+ for (int i = 0; i < run.length(); i++)
+ if (Font::treatAsSpace(m_run[i]))
+ numSpaces++;
+
+ if (numSpaces == 0)
+ m_padPerSpace = 0;
+ else
+ m_padPerSpace = ceilf(m_run.padding() / numSpaces);
+ }
+}
+
+#ifdef ANDROID_GLYPHBUFFER_HAS_ADJUSTED_WIDTHS
+#define SIGNAL_ADJUSTED_WIDTHS() adjustedWidths = true
+bool WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
+#else
+#define SIGNAL_ADJUSTED_WIDTHS()
+void WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
+#endif
+{
+ if (offset > m_end)
+ offset = m_end;
+
+ int currentCharacter = m_currentCharacter;
+ const UChar* cp = m_run.data(currentCharacter);
+
+ bool rtl = m_run.rtl();
+ bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_padding) && !m_run.spacingDisabled();
+
+ float runWidthSoFar = m_runWidthSoFar;
+ float lastRoundingWidth = m_finalRoundingWidth;
+
+#ifdef ANDROID_GLYPHBUFFER_HAS_ADJUSTED_WIDTHS
+ bool adjustedWidths = false;
+#endif
+ while (currentCharacter < offset) {
+ UChar32 c = *cp;
+ unsigned clusterLength = 1;
+ if (c >= 0x3041) {
+ if (c <= 0x30FE) {
+ // Deal with Hiragana and Katakana voiced and semi-voiced syllables.
+ // Normalize into composed form, and then look for glyph with base + combined mark.
+ // Check above for character range to minimize performance impact.
+ UChar32 normalized = normalizeVoicingMarks(currentCharacter);
+ if (normalized) {
+ c = normalized;
+ clusterLength = 2;
+ }
+ } else if (U16_IS_SURROGATE(c)) {
+ if (!U16_IS_SURROGATE_LEAD(c))
+ break;
+
+ // Do we have a surrogate pair? If so, determine the full Unicode (32 bit)
+ // code point before glyph lookup.
+ // Make sure we have another character and it's a low surrogate.
+ if (currentCharacter + 1 >= m_run.length())
+ break;
+ UChar low = cp[1];
+ if (!U16_IS_TRAIL(low))
+ break;
+ c = U16_GET_SUPPLEMENTARY(c, low);
+ clusterLength = 2;
+ }
+ }
+
+ const GlyphData& glyphData = m_font->glyphDataForCharacter(c, rtl);
+ Glyph glyph = glyphData.glyph;
+ const SimpleFontData* fontData = glyphData.fontData;
+
+ ASSERT(fontData);
+
+ // Now that we have a glyph and font data, get its width.
+ float width;
+ if (c == '\t' && m_run.allowTabs()) {
+ float tabWidth = m_font->tabWidth();
+ width = tabWidth - fmodf(m_run.xPos() + runWidthSoFar, tabWidth);
+ SIGNAL_ADJUSTED_WIDTHS();
+ } else {
+ width = fontData->widthForGlyph(glyph);
+#ifndef ANDROID_NEVER_ROUND_FONT_METRICS
+ // We special case spaces in two ways when applying word rounding.
+ // First, we round spaces to an adjusted width in all fonts.
+ // Second, in fixed-pitch fonts we ensure that all characters that
+ // match the width of the space character have the same width as the space character.
+ if (width == fontData->m_spaceWidth && (fontData->m_treatAsFixedPitch || glyph == fontData->m_spaceGlyph) && m_run.applyWordRounding()) {
+ width = fontData->m_adjustedSpaceWidth;
+ SIGNAL_ADJUSTED_WIDTHS();
+ }
+#endif
+ }
+
+ if (hasExtraSpacing) {
+ // Account for letter-spacing.
+ if (width && m_font->letterSpacing()) {
+ width += m_font->letterSpacing();
+ SIGNAL_ADJUSTED_WIDTHS();
+ }
+
+ if (Font::treatAsSpace(c)) {
+ // Account for padding. WebCore uses space padding to justify text.
+ // We distribute the specified padding over the available spaces in the run.
+ if (m_padding) {
+ // Use left over padding if not evenly divisible by number of spaces.
+ if (m_padding < m_padPerSpace) {
+ width += m_padding;
+ m_padding = 0;
+ } else {
+ width += m_padPerSpace;
+ m_padding -= m_padPerSpace;
+ }
+ SIGNAL_ADJUSTED_WIDTHS();
+ }
+
+ // Account for word spacing.
+ // We apply additional space between "words" by adding width to the space character.
+ if (currentCharacter != 0 && !Font::treatAsSpace(cp[-1]) && m_font->wordSpacing()) {
+ width += m_font->wordSpacing();
+ SIGNAL_ADJUSTED_WIDTHS();
+ }
+ }
+ }
+
+ // Advance past the character we just dealt with.
+ cp += clusterLength;
+ currentCharacter += clusterLength;
+
+ // Account for float/integer impedance mismatch between CG and KHTML. "Words" (characters
+ // followed by a character defined by isRoundingHackCharacter()) are always an integer width.
+ // We adjust the width of the last character of a "word" to ensure an integer width.
+ // If we move KHTML to floats we can remove this (and related) hacks.
+
+ float oldWidth = width;
+
+#ifndef ANDROID_NEVER_ROUND_FONT_METRICS
+ // Force characters that are used to determine word boundaries for the rounding hack
+ // to be integer width, so following words will start on an integer boundary.
+ if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(c)) {
+ width = ceilf(width);
+ SIGNAL_ADJUSTED_WIDTHS();
+ }
+
+ // Check to see if the next character is a "rounding hack character", if so, adjust
+ // width so that the total run width will be on an integer boundary.
+ if ((m_run.applyWordRounding() && currentCharacter < m_run.length() && Font::isRoundingHackCharacter(*cp))
+ || (m_run.applyRunRounding() && currentCharacter >= m_end)) {
+ float totalWidth = runWidthSoFar + width;
+ width += ceilf(totalWidth) - totalWidth;
+ SIGNAL_ADJUSTED_WIDTHS();
+ }
+#endif
+
+ runWidthSoFar += width;
+
+ if (glyphBuffer)
+ glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width));
+
+ lastRoundingWidth = width - oldWidth;
+ }
+
+ m_currentCharacter = currentCharacter;
+ m_runWidthSoFar = runWidthSoFar;
+ m_finalRoundingWidth = lastRoundingWidth;
+
+#ifdef ANDROID_GLYPHBUFFER_HAS_ADJUSTED_WIDTHS
+ return adjustedWidths;
+#endif
+}
+
+bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer* glyphBuffer)
+{
+ glyphBuffer->clear();
+ advance(m_currentCharacter + 1, glyphBuffer);
+ float w = 0;
+ for (int i = 0; i < glyphBuffer->size(); ++i)
+ w += glyphBuffer->advanceAt(i);
+ width = w;
+ return !glyphBuffer->isEmpty();
+}
+
+UChar32 WidthIterator::normalizeVoicingMarks(int currentCharacter)
+{
+ if (currentCharacter + 1 < m_end) {
+ if (combiningClass(m_run[currentCharacter + 1]) == hiraganaKatakanaVoicingMarksCombiningClass) {
+#if USE(ICU_UNICODE)
+ // Normalize into composed form using 3.2 rules.
+ UChar normalizedCharacters[2] = { 0, 0 };
+ UErrorCode uStatus = U_ZERO_ERROR;
+ int32_t resultLength = unorm_normalize(m_run.data(currentCharacter), 2,
+ UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus);
+ if (resultLength == 1 && uStatus == 0)
+ return normalizedCharacters[0];
+#elif USE(QT4_UNICODE)
+ QString tmp(reinterpret_cast<const QChar*>(m_run.data(currentCharacter)), 2);
+ QString res = tmp.normalized(QString::NormalizationForm_C, QChar::Unicode_3_2);
+ if (res.length() == 1)
+ return res.at(0).unicode();
+#endif
+ }
+ }
+ return 0;
+}
+
+}