2007-02-05 Mitz Pettel <mitz@webkit.org>
authorap <ap@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 5 Feb 2007 18:32:29 +0000 (18:32 +0000)
committerap <ap@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 5 Feb 2007 18:32:29 +0000 (18:32 +0000)
        Reviewed by Darin.

        - fix http://bugs.webkit.org/show_bug.cgi?id=6626
          Arabic & Farsi rendered with no shaping (all glyphs separate, unreadable!)

        No test possible since Mac OS X only ships with fonts that contain shaping
        information.

        * icu/unicode/ushape.h: Added from
        http://www.opensource.apple.com/darwinsource/10.4.8.ppc/ICU-6.2.9/icuSources/common/unicode/
        * platform/FontData.h:
        (WebCore::FontData::shapesArabic): Added. Returns whether the font contains
        shaping information for Arabic or not.
        * platform/mac/FontDataMac.mm:
        (WebCore::FontData::platformInit):
        (WebCore::FontData::checkShapesArabic): Added. Checks whether the font contains
        a glyph metamorphosis table. The heuristic is that if a font contains Arabic glyphs
        and a metamorphosis table, then it includes shaping information for Arabic.
        * platform/mac/FontMac.mm:
        (WebCore::overrideLayoutOperation): Changed to use the same character buffer passed
        to ATSUI instead of the original character buffer and to give zero-width spaces zero
        width.
        (WebCore::isArabicLamWithAlefLigature): Added. Checks if the character is one
        of the Arabic presentation forms of Lam with Alef.
        (WebCore::shapeArabic): Added. This is a wrapper around the ICU Arabic shaping routine
        that replaces each space following a Lam with Alef ligature resulting from shaping
        with a zero-width space.
        (WebCore::ATSULayoutParameters::initialize): Changed to allocate the auxiliary buffer
        lazily for mirroring and Arabic shaping. Added a call to shapeArabic() when required.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@19407 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebCore/ChangeLog
WebCore/icu/unicode/ushape.h [new file with mode: 0644]
WebCore/platform/FontData.h
WebCore/platform/mac/FontDataMac.mm
WebCore/platform/mac/FontMac.mm

index 5d36401..881817a 100644 (file)
@@ -1,3 +1,35 @@
+2007-02-05  Mitz Pettel  <mitz@webkit.org>
+
+        Reviewed by Darin.
+
+        - fix http://bugs.webkit.org/show_bug.cgi?id=6626
+          Arabic & Farsi rendered with no shaping (all glyphs separate, unreadable!)
+
+        No test possible since Mac OS X only ships with fonts that contain shaping
+        information.
+
+        * icu/unicode/ushape.h: Added from
+        http://www.opensource.apple.com/darwinsource/10.4.8.ppc/ICU-6.2.9/icuSources/common/unicode/
+        * platform/FontData.h:
+        (WebCore::FontData::shapesArabic): Added. Returns whether the font contains
+        shaping information for Arabic or not.
+        * platform/mac/FontDataMac.mm:
+        (WebCore::FontData::platformInit):
+        (WebCore::FontData::checkShapesArabic): Added. Checks whether the font contains
+        a glyph metamorphosis table. The heuristic is that if a font contains Arabic glyphs
+        and a metamorphosis table, then it includes shaping information for Arabic.
+        * platform/mac/FontMac.mm:
+        (WebCore::overrideLayoutOperation): Changed to use the same character buffer passed
+        to ATSUI instead of the original character buffer and to give zero-width spaces zero
+        width.
+        (WebCore::isArabicLamWithAlefLigature): Added. Checks if the character is one
+        of the Arabic presentation forms of Lam with Alef.
+        (WebCore::shapeArabic): Added. This is a wrapper around the ICU Arabic shaping routine
+        that replaces each space following a Lam with Alef ligature resulting from shaping
+        with a zero-width space.
+        (WebCore::ATSULayoutParameters::initialize): Changed to allocate the auxiliary buffer
+        lazily for mirroring and Arabic shaping. Added a call to shapeArabic() when required.
+
 2007-02-05  Eric Seidel  <eric@webkit.org>
 
         No review, build fix only.
diff --git a/WebCore/icu/unicode/ushape.h b/WebCore/icu/unicode/ushape.h
new file mode 100644 (file)
index 0000000..34e81e3
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+******************************************************************************
+*
+*   Copyright (C) 2000-2004, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*
+******************************************************************************
+*   file name:  ushape.h
+*   encoding:   US-ASCII
+*   tab size:   8 (not used)
+*   indentation:4
+*
+*   created on: 2000jun29
+*   created by: Markus W. Scherer
+*/
+
+#ifndef __USHAPE_H__
+#define __USHAPE_H__
+
+#include "unicode/utypes.h"
+
+/**
+ * \file
+ * \brief C API:  Arabic shaping
+ * 
+ */
+
+/**
+ * Shape Arabic text on a character basis.
+ *
+ * <p>This function performs basic operations for "shaping" Arabic text. It is most
+ * useful for use with legacy data formats and legacy display technology
+ * (simple terminals). All operations are performed on Unicode characters.</p>
+ *
+ * <p>Text-based shaping means that some character code points in the text are
+ * replaced by others depending on the context. It transforms one kind of text
+ * into another. In comparison, modern displays for Arabic text select
+ * appropriate, context-dependent font glyphs for each text element, which means
+ * that they transform text into a glyph vector.</p>
+ *
+ * <p>Text transformations are necessary when modern display technology is not
+ * available or when text needs to be transformed to or from legacy formats that
+ * use "shaped" characters. Since the Arabic script is cursive, connecting
+ * adjacent letters to each other, computers select images for each letter based
+ * on the surrounding letters. This usually results in four images per Arabic
+ * letter: initial, middle, final, and isolated forms. In Unicode, on the other
+ * hand, letters are normally stored abstract, and a display system is expected
+ * to select the necessary glyphs. (This makes searching and other text
+ * processing easier because the same letter has only one code.) It is possible
+ * to mimic this with text transformations because there are characters in
+ * Unicode that are rendered as letters with a specific shape
+ * (or cursive connectivity). They were included for interoperability with
+ * legacy systems and codepages, and for unsophisticated display systems.</p>
+ *
+ * <p>A second kind of text transformations is supported for Arabic digits:
+ * For compatibility with legacy codepages that only include European digits,
+ * it is possible to replace one set of digits by another, changing the
+ * character code points. These operations can be performed for either
+ * Arabic-Indic Digits (U+0660...U+0669) or Eastern (Extended) Arabic-Indic
+ * digits (U+06f0...U+06f9).</p>
+ *
+ * <p>Some replacements may result in more or fewer characters (code points).
+ * By default, this means that the destination buffer may receive text with a
+ * length different from the source length. Some legacy systems rely on the
+ * length of the text to be constant. They expect extra spaces to be added
+ * or consumed either next to the affected character or at the end of the
+ * text.</p>
+ *
+ * <p>For details about the available operations, see the description of the
+ * <code>U_SHAPE_...</code> options.</p>
+ *
+ * @param source The input text.
+ *
+ * @param sourceLength The number of UChars in <code>source</code>.
+ *
+ * @param dest The destination buffer that will receive the results of the
+ *             requested operations. It may be <code>NULL</code> only if
+ *             <code>destSize</code> is 0. The source and destination must not
+ *             overlap.
+ *
+ * @param destSize The size (capacity) of the destination buffer in UChars.
+ *                 If <code>destSize</code> is 0, then no output is produced,
+ *                 but the necessary buffer size is returned ("preflighting").
+ *
+ * @param options This is a 32-bit set of flags that specify the operations
+ *                that are performed on the input text. If no error occurs,
+ *                then the result will always be written to the destination
+ *                buffer.
+ *
+ * @param pErrorCode must be a valid pointer to an error code value,
+ *        which must not indicate a failure before the function call.
+ *
+ * @return The number of UChars written to the destination buffer.
+ *         If an error occured, then no output was written, or it may be
+ *         incomplete. If <code>U_BUFFER_OVERFLOW_ERROR</code> is set, then
+ *         the return value indicates the necessary destination buffer size.
+ * @stable ICU 2.0
+ */
+U_STABLE int32_t U_EXPORT2
+u_shapeArabic(const UChar *source, int32_t sourceLength,
+              UChar *dest, int32_t destSize,
+              uint32_t options,
+              UErrorCode *pErrorCode);
+
+/**
+ * Memory option: allow the result to have a different length than the source.
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_LENGTH_GROW_SHRINK              0
+
+/**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces next to modified characters.
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_LENGTH_FIXED_SPACES_NEAR        1
+
+/**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces at the end of the text.
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_LENGTH_FIXED_SPACES_AT_END      2
+
+/**
+ * Memory option: the result must have the same length as the source.
+ * If more room is necessary, then try to consume spaces at the beginning of the text.
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_LENGTH_FIXED_SPACES_AT_BEGINNING 3
+
+/** Bit mask for memory options. @stable ICU 2.0 */
+#define U_SHAPE_LENGTH_MASK                     3
+
+
+/** Direction indicator: the source is in logical (keyboard) order. @stable ICU 2.0 */
+#define U_SHAPE_TEXT_DIRECTION_LOGICAL          0
+
+/**
+ * Direction indicator:
+ * the source is in visual LTR order,
+ * the leftmost displayed character stored first.
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_TEXT_DIRECTION_VISUAL_LTR       4
+
+/** Bit mask for direction indicators. @stable ICU 2.0 */
+#define U_SHAPE_TEXT_DIRECTION_MASK             4
+
+
+/** Letter shaping option: do not perform letter shaping. @stable ICU 2.0 */
+#define U_SHAPE_LETTERS_NOOP                    0
+
+/** Letter shaping option: replace abstract letter characters by "shaped" ones. @stable ICU 2.0 */
+#define U_SHAPE_LETTERS_SHAPE                   8
+
+/** Letter shaping option: replace "shaped" letter characters by abstract ones. @stable ICU 2.0 */
+#define U_SHAPE_LETTERS_UNSHAPE                 0x10
+
+/**
+ * Letter shaping option: replace abstract letter characters by "shaped" ones.
+ * The only difference with U_SHAPE_LETTERS_SHAPE is that Tashkeel letters
+ * are always "shaped" into the isolated form instead of the medial form
+ * (selecting code points from the Arabic Presentation Forms-B block).
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_LETTERS_SHAPE_TASHKEEL_ISOLATED 0x18
+
+/** Bit mask for letter shaping options. @stable ICU 2.0 */
+#define U_SHAPE_LETTERS_MASK                    0x18
+
+
+/** Digit shaping option: do not perform digit shaping. @stable ICU 2.0 */
+#define U_SHAPE_DIGITS_NOOP                     0
+
+/**
+ * Digit shaping option:
+ * Replace European digits (U+0030...) by Arabic-Indic digits.
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_DIGITS_EN2AN                    0x20
+
+/**
+ * Digit shaping option:
+ * Replace Arabic-Indic digits by European digits (U+0030...).
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_DIGITS_AN2EN                    0x40
+
+/**
+ * Digit shaping option:
+ * Replace European digits (U+0030...) by Arabic-Indic digits if the most recent
+ * strongly directional character is an Arabic letter
+ * (<code>u_charDirection()</code> result <code>U_RIGHT_TO_LEFT_ARABIC</code> [AL]).<br>
+ * The direction of "preceding" depends on the direction indicator option.
+ * For the first characters, the preceding strongly directional character
+ * (initial state) is assumed to be not an Arabic letter
+ * (it is <code>U_LEFT_TO_RIGHT</code> [L] or <code>U_RIGHT_TO_LEFT</code> [R]).
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_DIGITS_ALEN2AN_INIT_LR          0x60
+
+/**
+ * Digit shaping option:
+ * Replace European digits (U+0030...) by Arabic-Indic digits if the most recent
+ * strongly directional character is an Arabic letter
+ * (<code>u_charDirection()</code> result <code>U_RIGHT_TO_LEFT_ARABIC</code> [AL]).<br>
+ * The direction of "preceding" depends on the direction indicator option.
+ * For the first characters, the preceding strongly directional character
+ * (initial state) is assumed to be an Arabic letter.
+ * @stable ICU 2.0
+ */
+#define U_SHAPE_DIGITS_ALEN2AN_INIT_AL          0x80
+
+/** Not a valid option value. May be replaced by a new option. @stable ICU 2.0 */
+#define U_SHAPE_DIGITS_RESERVED                 0xa0
+
+/** Bit mask for digit shaping options. @stable ICU 2.0 */
+#define U_SHAPE_DIGITS_MASK                     0xe0
+
+
+/** Digit type option: Use Arabic-Indic digits (U+0660...U+0669). @stable ICU 2.0 */
+#define U_SHAPE_DIGIT_TYPE_AN                   0
+
+/** Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9). @stable ICU 2.0 */
+#define U_SHAPE_DIGIT_TYPE_AN_EXTENDED          0x100
+
+/** Not a valid option value. May be replaced by a new option. @stable ICU 2.0 */
+#define U_SHAPE_DIGIT_TYPE_RESERVED             0x200
+
+/** Bit mask for digit type options. @stable ICU 2.0 */
+#define U_SHAPE_DIGIT_TYPE_MASK                 0x3f00
+
+#endif
index 16a0dfb..130ef89 100644 (file)
@@ -70,6 +70,13 @@ public:
 
 #if PLATFORM(MAC)
     NSFont* getNSFont() const { return m_font.font; }
+    void checkShapesArabic() const;
+    bool shapesArabic() const
+    {
+        if (!m_checkedShapesArabic)
+            checkShapesArabic();
+        return m_shapesArabic;
+    }
 #endif
 
 #if PLATFORM(WIN)
@@ -113,6 +120,8 @@ public:
     mutable ATSUStyle m_ATSUStyle;
     mutable bool m_ATSUStyleInitialized;
     mutable bool m_ATSUMirrors;
+    mutable bool m_checkedShapesArabic;
+    mutable bool m_shapesArabic;
 #endif
 
 #if PLATFORM(WIN)
index 000a271..c182f9b 100644 (file)
@@ -95,6 +95,8 @@ void FontData::platformInit()
     m_styleGroup = 0;
     m_ATSUStyleInitialized = false;
     m_ATSUMirrors = false;
+    m_checkedShapesArabic = false;
+    m_shapesArabic = false;
     
     m_syntheticBoldOffset = m_font.syntheticBold ? 1.0f : 0.f;
     
@@ -277,4 +279,33 @@ float FontData::platformWidthForGlyph(Glyph glyph) const
     return advance.width + m_syntheticBoldOffset;
 }
 
+void FontData::checkShapesArabic() const
+{
+    ASSERT(!m_checkedShapesArabic);
+
+    m_checkedShapesArabic = true;
+    
+    ATSUFontID fontID = wkGetNSFontATSUFontId(m_font.font);
+    if (!fontID) {
+        LOG_ERROR("unable to get ATSUFontID for %@", m_font.font);
+        return;
+    }
+
+    // This function is called only on fonts that contain Arabic glyphs. Our
+    // heuristic is that if such a font has a glyph metamorphosis table, then
+    // it includes shaping information for Arabic.
+    FourCharCode tables[] = { 'morx', 'mort' };
+    for (unsigned i = 0; i < sizeof(tables) / sizeof(tables[0]); ++i) {
+        ByteCount tableSize;
+        OSStatus status = ATSFontGetTable(fontID, tables[i], 0, 0, 0, &tableSize);
+        if (status == noErr) {
+            m_shapesArabic = true;
+            return;
+        }
+
+        if (status != kATSInvalidFontTableAccess)
+            LOG_ERROR("ATSFontGetTable failed (%d)", status);
+    }
+}
+
 }
index d4af21b..33f8825 100644 (file)
@@ -39,6 +39,8 @@
 #import "WebCoreSystemInterface.h"
 #import "WebCoreTextRenderer.h"
 
+#import <unicode/ushape.h>
+
 #define SYNTHETIC_OBLIQUE_ANGLE 14
 
 #ifdef __LP64__
@@ -165,7 +167,7 @@ static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector iCurrentOper
         
         Fixed lastNativePos = 0;
         float lastAdjustedPos = 0;
-        const UChar *characters = params->m_run.data(params->m_run.from());
+        const UChar* characters = params->m_charBuffer ? params->m_charBuffer + params->m_run.from() : params->m_run.data(params->m_run.from());
         const FontData **renderers = params->m_fonts + params->m_run.from();
         const FontData *renderer;
         const FontData *lastRenderer = 0;
@@ -196,13 +198,18 @@ static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector iCurrentOper
                     spaceGlyph = renderer->m_spaceGlyph;
                 }
             }
-            float width = FixedToFloat(layoutRecords[i].realPos - lastNativePos);
+            float width;
+            if (nextCh == zeroWidthSpace)
+                width = 0;
+            else {
+                width = FixedToFloat(layoutRecords[i].realPos - lastNativePos);
+                if (shouldRound)
+                    width = roundf(width);
+                width += renderer->m_syntheticBoldOffset;
+                if (renderer->m_treatAsFixedPitch ? width == renderer->m_spaceWidth : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace))
+                    width = renderer->m_adjustedSpaceWidth;
+            }
             lastNativePos = layoutRecords[i].realPos;
-            if (shouldRound)
-                width = roundf(width);
-            width += renderer->m_syntheticBoldOffset;
-            if (renderer->m_treatAsFixedPitch ? width == renderer->m_spaceWidth : (layoutRecords[i-1].flags & kATSGlyphInfoIsWhiteSpace))
-                width = renderer->m_adjustedSpaceWidth;
 
             if (hasExtraSpacing) {
                 if (width && params->m_font->letterSpacing())
@@ -263,15 +270,57 @@ static OSStatus overrideLayoutOperation(ATSULayoutOperationSelector iCurrentOper
     return noErr;
 }
 
+static inline bool isArabicLamWithAlefLigature(UChar c)
+{
+    return c >= 0xfef5 && c <= 0xfefc;
+}
+
+static void shapeArabic(const UChar* source, UChar* dest, unsigned totalLength, unsigned shapingStart)
+{
+    while (shapingStart < totalLength) {
+        unsigned shapingEnd;
+        // We do not want to pass a Lam with Alef ligature followed by a space to the shaper,
+        // since we want to be able to identify this sequence as the result of shaping a Lam
+        // followed by an Alef and padding with a space.
+        bool foundLigatureSpace = false;
+        for (shapingEnd = shapingStart; !foundLigatureSpace && shapingEnd < totalLength - 1; ++shapingEnd)
+            foundLigatureSpace = isArabicLamWithAlefLigature(source[shapingEnd]) && source[shapingEnd + 1] == ' ';
+        shapingEnd++;
+
+        UErrorCode shapingError = U_ZERO_ERROR;
+        unsigned charsWritten = u_shapeArabic(source + shapingStart, shapingEnd - shapingStart, dest + shapingStart, shapingEnd - shapingStart, U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR, &shapingError);
+
+        if (U_SUCCESS(shapingError) && charsWritten == shapingEnd - shapingStart) {
+            for (unsigned j = shapingStart; j < shapingEnd - 1; ++j) {
+                if (isArabicLamWithAlefLigature(dest[j]) && dest[j + 1] == ' ')
+                    dest[++j] = zeroWidthSpace;
+            }
+            if (foundLigatureSpace) {
+                dest[shapingEnd] = ' ';
+                shapingEnd++;
+            } else if (isArabicLamWithAlefLigature(dest[shapingEnd - 1])) {
+                // u_shapeArabic quirk: if the last two characters in the source string are a Lam and an Alef,
+                // the space is put at the beginning of the string, despite U_SHAPE_LENGTH_FIXED_SPACES_NEAR.
+                ASSERT(dest[shapingStart] == ' ');
+                dest[shapingStart] = zeroWidthSpace;
+            }
+        } else {
+            // Something went wrong. Abandon shaping and just copy the rest of the buffer.
+            LOG_ERROR("u_shapeArabic failed(%d)", shapingError);
+            shapingEnd = totalLength;
+            memcpy(dest + shapingStart, source + shapingStart, (shapingEnd - shapingStart) * sizeof(UChar));
+        }
+        shapingStart = shapingEnd;
+    }
+}
+
 void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* graphicsContext)
 {
     m_font = font;
     
-    // FIXME: It is probably best to always allocate a buffer for RTL, since even if for this
-    // fontData ATSUMirrors is true, for a substitute fontData it might be false.
     const FontData* fontData = font->primaryFont();
     m_fonts = new const FontData*[m_run.length()];
-    m_charBuffer = (UChar*)((font->isSmallCaps() || (m_style.rtl() && !fontData->m_ATSUMirrors)) ? new UChar[m_run.length()] : 0);
+    m_charBuffer = font->isSmallCaps() ? new UChar[m_run.length()] : 0;
     
     ATSUTextLayout layout;
     OSStatus status;
@@ -352,6 +401,7 @@ void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* g
             substituteLength = 0;
         }
 
+        bool shapedArabic = false;
         bool isSmallCap = false;
         UniCharArrayOffset firstSmallCap = 0;
         const FontData *r = fontData;
@@ -368,8 +418,26 @@ void ATSULayoutParameters::initialize(const Font* font, const GraphicsContext* g
                 else
                     break;
             }
-            if (m_style.rtl() && m_charBuffer && !r->m_ATSUMirrors)
-                m_charBuffer[i] = u_charMirror(m_charBuffer[i]);
+            if (!shapedArabic && ublock_getCode(m_run[i]) == UBLOCK_ARABIC && !r->shapesArabic()) {
+                shapedArabic = true;
+                if (!m_charBuffer) {
+                    m_charBuffer = new UChar[totalLength];
+                    memcpy(m_charBuffer, m_run.characters(), i * sizeof(UChar));
+                    ATSUTextMoved(layout, m_charBuffer);
+                }
+                shapeArabic(m_run.characters(), m_charBuffer, totalLength, i);
+            }
+            if (m_style.rtl() && !r->m_ATSUMirrors) {
+                UChar mirroredChar = u_charMirror(m_run[i]);
+                if (mirroredChar != m_run[i]) {
+                    if (!m_charBuffer) {
+                        m_charBuffer = new UChar[totalLength];
+                        memcpy(m_charBuffer, m_run.characters(), totalLength * sizeof(UChar));
+                        ATSUTextMoved(layout, m_charBuffer);
+                    }
+                    m_charBuffer[i] = mirroredChar;
+                }
+            }
             if (m_font->isSmallCaps()) {
                 const FontData* smallCapsData = r->smallCapsFontData(m_font->fontDescription());
                 UChar c = m_charBuffer[i];