changeset 4197:8ce796688bc6

RT-31470: Reuse code among DWGlyphLayout, CTGlyphLayout and PangoGlyphLayout
author Felipe Heidrich <felipe.heidrich@oracle.com>
date Mon, 08 Jul 2013 11:23:09 -0700
parents d8efc7324c17
children 1ef3f9aba44e
files modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTGlyphLayout.java modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWGlyphLayout.java modules/graphics/src/main/java/com/sun/javafx/font/pango/PangoGlyphLayout.java modules/graphics/src/main/java/com/sun/javafx/text/GlyphLayout.java
diffstat 4 files changed, 381 insertions(+), 769 deletions(-) [+]
line wrap: on
line diff
--- a/modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTGlyphLayout.java	Mon Jul 08 20:36:12 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/coretext/CTGlyphLayout.java	Mon Jul 08 11:23:09 2013 -0700
@@ -27,308 +27,108 @@
 
 import com.sun.javafx.font.CompositeFontResource;
 import com.sun.javafx.font.CompositeStrike;
-import com.sun.javafx.font.FontResource;
 import com.sun.javafx.font.FontStrike;
 import com.sun.javafx.font.PGFont;
 import com.sun.javafx.font.PrismFontFactory;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.scene.text.TextSpan;
 import com.sun.javafx.text.GlyphLayout;
-import com.sun.javafx.text.ScriptMapper;
 import com.sun.javafx.text.PrismTextLayout;
 import com.sun.javafx.text.TextRun;
 
-import java.text.Bidi;
 import java.util.Arrays;
 
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_ANALYSIS_VALID;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_COMPLEX;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_BIDI;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_TABS;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_EMBEDDED;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_RTL_BASE;
-
 class CTGlyphLayout extends GlyphLayout {
 
-    private TextRun addTextRun(PrismTextLayout layout, char[] chars, int start, int length,
-                               PGFont font, TextSpan span, byte level, boolean complex) {
+    protected TextRun addTextRun(PrismTextLayout layout, char[] chars, int start,
+                                 int length, PGFont font, TextSpan span, byte level) {
 
         TextRun textRun = null;
-        if (complex) {
-            /* Use CoreText to analize the run */
-            long alloc = OS.kCFAllocatorDefault();
-            long textRef = OS.CFStringCreateWithCharacters(alloc, chars, start, length);
 
-            FontStrike fontStrike = font.getStrike(BaseTransform.IDENTITY_TRANSFORM);
-            boolean composite = fontStrike instanceof CompositeStrike;
-            if (composite) {
-                fontStrike = ((CompositeStrike)fontStrike).getStrikeSlot(0);
+        /* Use CoreText to analize the run */
+        long alloc = OS.kCFAllocatorDefault();
+        long textRef = OS.CFStringCreateWithCharacters(alloc, chars, start, length);
+
+        FontStrike fontStrike = font.getStrike(BaseTransform.IDENTITY_TRANSFORM);
+        boolean composite = fontStrike instanceof CompositeStrike;
+        if (composite) {
+            fontStrike = ((CompositeStrike)fontStrike).getStrikeSlot(0);
+        }
+        CTFontStrike strike = (CTFontStrike)fontStrike;
+        long fontRef = strike.getFontRef();
+
+        long attributes = OS.CFDictionaryCreateMutable(alloc, 4, OS.kCFTypeDictionaryKeyCallBacks(), OS.kCFTypeDictionaryValueCallBacks());
+        OS.CFDictionaryAddValue(attributes, OS.kCTFontAttributeName(), fontRef);
+        /* Note that by default CoreText will apply kerning depending on the font*/
+        long attString = OS.CFAttributedStringCreate(alloc, textRef, attributes);
+        long lineRef = OS.CTLineCreateWithAttributedString(attString);
+        OS.CFRelease(attributes);
+        OS.CFRelease(attString);
+        OS.CFRelease(textRef);
+
+        long runs = OS.CTLineGetGlyphRuns(lineRef);
+        long runCount = OS.CFArrayGetCount(runs);
+
+        /*
+         * Need to undo the bidi reordering done by CoreText as it will be
+         * done again in text layout after wrapping.
+         */
+        boolean bidi = (level & 1) != 0;
+        int i = bidi ? (int)runCount - 1 : 0;
+        int step = bidi ? -1 : 1;
+        for (; 0 <= i && i < runCount; i += step) {
+            long run = OS.CFArrayGetValueAtIndex(runs, i);
+            int status = OS.CTRunGetStatus(run);
+            boolean runBidi = (status & OS.kCTRunStatusRightToLeft) != 0;
+            if (bidi != runBidi) {
+                if (PrismFontFactory.debugFonts) {
+                    System.err.println("[CoreText] not expecing bidi level to differ.");
+                }
             }
-            CTFontStrike strike = (CTFontStrike)fontStrike;
-            long fontRef = strike.getFontRef();
-
-            long attributes = OS.CFDictionaryCreateMutable(alloc, 4, OS.kCFTypeDictionaryKeyCallBacks(), OS.kCFTypeDictionaryValueCallBacks());
-            OS.CFDictionaryAddValue(attributes, OS.kCTFontAttributeName(), fontRef);
-            /* Note that by default CoreText will apply kerning depending on the font*/
-            long attString = OS.CFAttributedStringCreate(alloc, textRef, attributes);
-            long lineRef = OS.CTLineCreateWithAttributedString(attString);
-            OS.CFRelease(attributes);
-            OS.CFRelease(attString);
-            OS.CFRelease(textRef);
-
-            long runs = OS.CTLineGetGlyphRuns(lineRef);
-            long runCount = OS.CFArrayGetCount(runs);
+            CFRange range = OS.CTRunGetStringRange(run);
+            int glyphCount = (int)OS.CTRunGetGlyphCount(run);
+            int[] glyphs = OS.CTRunGetGlyphsPtr(run);
 
             /*
-             * Need to undo the bidi reordering done by CoreText as it will be
-             * done again in text layout after wrapping.
+             * The positions and indices returned by core text are
+             * relative to the line, the following native methods
+             * are custom to return values relative to the run.
              */
-            boolean bidi = (level & 1) != 0;
-            int i = bidi ? (int)runCount - 1 : 0;
-            int step = bidi ? -1 : 1;
-            for (; 0 <= i && i < runCount; i += step) {
-                long run = OS.CFArrayGetValueAtIndex(runs, i);
-                int status = OS.CTRunGetStatus(run);
-                boolean runBidi = (status & OS.kCTRunStatusRightToLeft) != 0;
-                if (bidi != runBidi) {
+            float[] positions = OS.CTRunGetPositionsPtr(run);
+            int[] indices = OS.CTRunGetStringIndicesPtr(run);
+
+            if (PrismFontFactory.debugFonts) {
+                System.err.println("Run " + i + " range " + (start+range.location) + ", " + range.length);
+                System.err.println("\tText=[" + new String(chars, start + (int)range.location, (int)range.length) + "]");
+                System.err.println("\tFont=" +strike.getFontResource());
+                System.err.println("\tStatus="+status);
+                System.err.println("\tGlyphs="+Arrays.toString(glyphs));
+                System.err.println("\tPositions="+Arrays.toString(positions));
+                System.err.println("\tIndices="+Arrays.toString(indices));
+            }
+            if (composite) {
+                long runAttrs = OS.CTRunGetAttributes(run);
+                long actualFont = OS.CFDictionaryGetValue(runAttrs, OS.kCTFontAttributeName());
+                String fontName = OS.CTFontCopyDisplayName(actualFont);
+                if (!fontName.equalsIgnoreCase(strike.getFontResource().getFullName())) {
+                    CompositeFontResource fr = (CompositeFontResource)font.getFontResource();
+                    int slot = fr.getSlotForFont(fontName) << 24;
                     if (PrismFontFactory.debugFonts) {
-                        System.err.println("[CoreText] not expecing bidi level to differ.");
+                        System.err.println("\tFallback front= "+ fontName + " slot=" + (slot>>24));
+                    }
+                    for (int j = 0; j < glyphs.length; j++) {
+                        glyphs[j] |= slot;
                     }
                 }
-                CFRange range = OS.CTRunGetStringRange(run);
-                int glyphCount = (int)OS.CTRunGetGlyphCount(run);
-                int[] glyphs = OS.CTRunGetGlyphsPtr(run);
-
-                /*
-                 * The positions and indices returned by core text are
-                 * relative to the line, the following native methods
-                 * are custom to return values relative to the run.
-                 */
-                float[] positions = OS.CTRunGetPositionsPtr(run);
-                int[] indices = OS.CTRunGetStringIndicesPtr(run);
-
-                if (PrismFontFactory.debugFonts) {
-                    System.err.println("Run " + i + " range " + (start+range.location) + ", " + range.length);
-                    System.err.println("\tText=[" + new String(chars, start + (int)range.location, (int)range.length) + "]");
-                    System.err.println("\tFont=" +strike.getFontResource());
-                    System.err.println("\tStatus="+status);
-                    System.err.println("\tGlyphs="+Arrays.toString(glyphs));
-                    System.err.println("\tPositions="+Arrays.toString(positions));
-                    System.err.println("\tIndices="+Arrays.toString(indices));
-                }
-                if (composite) {
-                    long runAttrs = OS.CTRunGetAttributes(run);
-                    long actualFont = OS.CFDictionaryGetValue(runAttrs, OS.kCTFontAttributeName());
-                    String fontName = OS.CTFontCopyDisplayName(actualFont);
-                    if (!fontName.equalsIgnoreCase(strike.getFontResource().getFullName())) {
-                        CompositeFontResource fr = (CompositeFontResource)font.getFontResource();
-                        int slot = fr.getSlotForFont(fontName) << 24;
-                        if (PrismFontFactory.debugFonts) {
-                            System.err.println("\tFallback front= "+ fontName + " slot=" + (slot>>24));
-                        }
-                        for (int j = 0; j < glyphs.length; j++) {
-                            glyphs[j] |= slot;
-                        }
-                    }
-                }
-                textRun = new TextRun(start + (int)range.location, (int)range.length, level, true, 0, span, 0, false);
-                textRun.shape(glyphCount, glyphs, positions, indices);
-                layout.addTextRun(textRun);
             }
-            OS.CFRelease(lineRef);
-        } else {
-            textRun = new TextRun(start, length, level, false, 0, span, 0, false);
+            textRun = new TextRun(start + (int)range.location, (int)range.length, level, true, 0, span, 0, false);
+            textRun.shape(glyphCount, glyphs, positions, indices);
             layout.addTextRun(textRun);
         }
+        OS.CFRelease(lineRef);
         return textRun;
     }
 
-    public int breakRuns(PrismTextLayout layout, char[] chars, int flags) {
-        int length = chars.length;
-        boolean complexRun = false;
-        boolean complex = false;
-        boolean feature = false;
-
-        boolean checkComplex = true;
-        boolean checkBidi = true;
-        if ((flags & FLAGS_ANALYSIS_VALID) != 0) {
-            /* Avoid work when it is known neither complex
-             * text nor bidi are not present. */
-            checkComplex = (flags & FLAGS_HAS_COMPLEX) != 0;
-            checkBidi = (flags & FLAGS_HAS_BIDI) != 0;
-        }
-
-        TextRun run = null;
-        Bidi bidi = null;
-        byte bidiLevel = 0;
-        int bidiEnd = length;
-        int bidiIndex = 0;
-        int spanIndex = 0;
-        TextSpan span = null;
-        int spanEnd = length;
-        PGFont font = null;
-        TextSpan[] spans = layout.getTextSpans();
-        if (spans != null) {
-            if (spans.length > 0) {
-                span = spans[spanIndex];
-                spanEnd = span.getText().length();
-                font = (PGFont)span.getFont();
-                if (font == null) {
-                    flags |= FLAGS_HAS_EMBEDDED;
-                }
-            }
-        } else {
-            font = layout.getFont();
-        }
-        if (font != null) {
-            FontResource fr = font.getFontResource();
-            int requestedFeatures = font.getFeatures();
-            int supportedFeatures = fr.getFeatures();
-            feature = (requestedFeatures & supportedFeatures) != 0;
-        }
-        if (checkBidi && length > 0) {
-            int direction = layout.getDirection();
-            bidi = new Bidi(chars, 0, null, 0, length, direction);
-            /* Temporary Code: See RT-26997 */
-//            bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
-            bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
-            bidiEnd = bidi.getRunLimit(bidiIndex);
-            if ((bidiLevel & 1) != 0) {
-                flags |= FLAGS_HAS_BIDI;
-            }
-        }
-
-        int start = 0;
-        int i = 0;
-        while (i < length) {
-            char ch = chars[i];
-            int codePoint = ch;
-
-            boolean delimiterChanged = ch == '\t' || ch == '\n' || ch == '\r';
-            boolean spanChanged = i >= spanEnd;
-            boolean levelChanged = i >= bidiEnd;
-            boolean complexChanged = false;
-
-            if (checkComplex) {
-                if (Character.isHighSurrogate(ch)) {
-                    /* Only merge surrogate when the pair is in the same span. */
-                    if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
-                        codePoint = Character.toCodePoint(ch, chars[++i]);
-                    }
-                }
-                if (Character.isWhitespace(codePoint)) {
-                    complex = feature || complexRun;
-                } else {
-                    complex = feature || ScriptMapper.isComplexCharCode(codePoint);
-                }
-                complexChanged = complex != complexRun;
-            }
-
-            if (delimiterChanged || spanChanged || levelChanged || complexChanged) {
-
-                /* Create text run */
-                if (i != start) {
-                    run = addTextRun(layout, chars, start, i - start,
-                                     font, span, bidiLevel, complexRun);
-                    if (complexRun) {
-                        flags |= FLAGS_HAS_COMPLEX;
-                    }
-                    start = i;
-                }
-
-                if (delimiterChanged) {
-                    i++;
-                    /* Only merge \r\n when the are in the same text span */
-                    if (ch == '\r' && i < spanEnd && chars[i] == '\n') {
-                        i++;
-                    }
-
-                    /* Create delimiter run */
-                    run = new TextRun(start, i - start, bidiLevel, false,
-                                      ScriptMapper.COMMON, span, 0, false);
-                    if (ch == '\t') {
-                        run.setTab();
-                        flags |= FLAGS_HAS_TABS;
-                    } else {
-                        run.setLinebreak();
-                    }
-                    layout.addTextRun(run);
-
-                    start = i;
-                    if (i == length) break;
-                    spanChanged = i >= spanEnd;
-                    levelChanged = i >= bidiEnd;
-                }
-                if (spanChanged) {
-                    /* Only true for rich text (spans != null) */
-                    span = spans[++spanIndex];
-                    spanEnd += span.getText().length();
-                    font = (PGFont)span.getFont();
-                    if (font == null) {
-                        flags |= FLAGS_HAS_EMBEDDED;
-                    } else {
-                        FontResource fr = font.getFontResource();
-                        int requestedFeatures = font.getFeatures();
-                        int supportedFeatures = fr.getFeatures();
-                        feature = (requestedFeatures & supportedFeatures) != 0;
-                    }
-                }
-                if (levelChanged) {
-                    bidiIndex++;
-                    /* Temporary Code: See RT-26997 */
-//                    bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
-                    bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
-                    bidiEnd = bidi.getRunLimit(bidiIndex);
-                    if ((bidiLevel & 1) != 0) {
-                        flags |= FLAGS_HAS_BIDI;
-                    }
-                }
-
-                if (complexChanged) {
-                    if (delimiterChanged) {
-                        ch = chars[i]; /* update ch because of delimiterChanged */
-                        if (Character.isHighSurrogate(ch)) {
-                            /* Only merge surrogate when the pair is in the same span */
-                            if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
-                                codePoint = Character.toCodePoint(ch, chars[++i]);
-                            }
-                        }
-                        if (Character.isWhitespace(codePoint)) {
-                            complex = feature || complexRun;
-                        } else {
-                            complex = feature || ScriptMapper.isComplexCharCode(codePoint);
-                        }
-                    }
-                    complexRun = complex;
-                }
-            }
-            if (!delimiterChanged) i++;
-        }
-
-        /* Create final text run */
-        if (start < length) {
-            addTextRun(layout, chars, start, length - start,
-                       font, span, bidiLevel, complexRun);
-            if (complexRun) {
-                flags |= FLAGS_HAS_COMPLEX;
-            }
-        } else {
-            /* Ensure every lines has at least one run */
-            if (run == null || run.isLinebreak()) {
-                run = new TextRun(start, 0, (byte)0, false,
-                                  ScriptMapper.COMMON, span, 0, false);
-                layout.addTextRun(run);
-            }
-        }
-        if (bidi != null) {
-            if (!bidi.baseIsLeftToRight()) {
-                flags |= FLAGS_RTL_BASE;
-            }
-        }
-        flags |= FLAGS_ANALYSIS_VALID;
-        return flags;
-    }
-
     public void layout(TextRun run, PGFont font, FontStrike strike, char[] text) {
         // Nothing - complex run are analyzed by CoreText during break run
     }
--- a/modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWGlyphLayout.java	Mon Jul 08 20:36:12 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/directwrite/DWGlyphLayout.java	Mon Jul 08 11:23:09 2013 -0700
@@ -25,7 +25,6 @@
 
 package com.sun.javafx.font.directwrite;
 
-import java.text.Bidi;
 import com.sun.javafx.font.CompositeFontResource;
 import com.sun.javafx.font.FontResource;
 import com.sun.javafx.font.FontStrike;
@@ -34,43 +33,36 @@
 import com.sun.javafx.scene.text.TextSpan;
 import com.sun.javafx.text.GlyphLayout;
 import com.sun.javafx.text.PrismTextLayout;
-import com.sun.javafx.text.ScriptMapper;
 import com.sun.javafx.text.TextRun;
-import static com.sun.javafx.scene.text.TextLayout.*;
 
 public class DWGlyphLayout extends GlyphLayout {
 
     private static final String LOCALE = "en-us";
 
-    private TextRun addTextRun(PrismTextLayout layout, char[] chars, int start, int length,
-                               PGFont font, TextSpan span, byte level, boolean complex) {
+    protected TextRun addTextRun(PrismTextLayout layout, char[] chars, int start,
+                                 int length, PGFont font, TextSpan span, byte level) {
         TextRun textRun = null;
-        if (complex) {
-            int dir = (level & 1) != 0 ? OS.DWRITE_READING_DIRECTION_RIGHT_TO_LEFT :
-                                         OS.DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
-            JFXTextAnalysisSink sink = OS.NewJFXTextAnalysisSink(chars, start, length, LOCALE, dir);
-            sink.AddRef();
+        int dir = (level & 1) != 0 ? OS.DWRITE_READING_DIRECTION_RIGHT_TO_LEFT :
+                                     OS.DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
+        JFXTextAnalysisSink sink = OS.NewJFXTextAnalysisSink(chars, start, length, LOCALE, dir);
+        sink.AddRef();
 
-            IDWriteFactory factory = DWFactory.getDWriteFactory();
-            IDWriteTextAnalyzer analyzer = factory.CreateTextAnalyzer();
-            analyzer.AnalyzeScript(sink, 0, length, sink);
+        IDWriteFactory factory = DWFactory.getDWriteFactory();
+        IDWriteTextAnalyzer analyzer = factory.CreateTextAnalyzer();
+        analyzer.AnalyzeScript(sink, 0, length, sink);
 
-            while (sink.Next()) {
-                int runStart = sink.GetStart();
-                int runLength = sink.GetLength();
-                DWRITE_SCRIPT_ANALYSIS analysis = sink.GetAnalysis();
-                textRun = new TextRun(start + runStart, runLength, level, true,
-                                      analysis.script, span,
-                                      analysis.shapes, false);
-                layout.addTextRun(textRun) ;
-            }
+        while (sink.Next()) {
+            int runStart = sink.GetStart();
+            int runLength = sink.GetLength();
+            DWRITE_SCRIPT_ANALYSIS analysis = sink.GetAnalysis();
+            textRun = new TextRun(start + runStart, runLength, level, true,
+                                  analysis.script, span,
+                                  analysis.shapes, false);
+            layout.addTextRun(textRun) ;
+        }
 
-            analyzer.Release();
-            sink.Release();
-        } else {
-            textRun = new TextRun(start, length, level, false, 0, span, 0, false);
-            layout.addTextRun(textRun);
-        }
+        analyzer.Release();
+        sink.Release();
         return textRun;
     }
 
@@ -279,198 +271,4 @@
         }
         run.shape(totalGlyphCount, glyphs, pos, clusterMap);
     }
-
-    /*
-    * Ideally DirectWrite could be used to do the entire job.
-    * Still need to verify if JFX handling of non-complex is indeed faster
-    * than DirectWrite.
-    *
-    * (this method was copied from the CoreText implementation).
-    */
-    public int breakRuns(PrismTextLayout layout, char[] chars, int flags) {
-        int length = chars.length;
-        boolean complexRun = false;
-        boolean complex = false;
-        boolean feature = false;
-
-        boolean checkComplex = true;
-        boolean checkBidi = true;
-        if ((flags & FLAGS_ANALYSIS_VALID) != 0) {
-            /* Avoid work when it is known neither complex
-             * text nor bidi are not present. */
-            checkComplex = (flags & FLAGS_HAS_COMPLEX) != 0;
-            checkBidi = (flags & FLAGS_HAS_BIDI) != 0;
-        }
-
-        TextRun run = null;
-        Bidi bidi = null;
-        byte bidiLevel = 0;
-        int bidiEnd = length;
-        int bidiIndex = 0;
-        int spanIndex = 0;
-        TextSpan span = null;
-        int spanEnd = length;
-        PGFont font = null;
-        TextSpan[] spans = layout.getTextSpans();
-        if (spans != null) {
-            if (spans.length > 0) {
-                span = spans[spanIndex];
-                spanEnd = span.getText().length();
-                font = (PGFont)span.getFont();
-                if (font == null) {
-                    flags |= FLAGS_HAS_EMBEDDED;
-                }
-            }
-        } else {
-            font = layout.getFont();
-        }
-        if (font != null) {
-            FontResource fr = font.getFontResource();
-            int requestedFeatures = font.getFeatures();
-            int supportedFeatures = fr.getFeatures();
-            feature = (requestedFeatures & supportedFeatures) != 0;
-        }
-        if (checkBidi && length > 0) {
-            int direction = layout.getDirection();
-            bidi = new Bidi(chars, 0, null, 0, length, direction);
-            /* Temporary Code: See RT-26997 */
-//            bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
-            bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
-            bidiEnd = bidi.getRunLimit(bidiIndex);
-            if ((bidiLevel & 1) != 0) {
-                flags |= FLAGS_HAS_BIDI;
-            }
-        }
-
-        int start = 0;
-        int i = 0;
-        while (i < length) {
-            char ch = chars[i];
-            int codePoint = ch;
-
-            boolean delimiterChanged = ch == '\t' || ch == '\n' || ch == '\r';
-            boolean spanChanged = i >= spanEnd;
-            boolean levelChanged = i >= bidiEnd;
-            boolean complexChanged = false;
-
-            if (checkComplex) {
-                if (Character.isHighSurrogate(ch)) {
-                    /* Only merge surrogate when the pair is in the same span. */
-                    if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
-                        codePoint = Character.toCodePoint(ch, chars[++i]);
-                    }
-                }
-                if (Character.isWhitespace(codePoint)) {
-                    complex = feature || complexRun;
-                } else {
-                    complex = feature || ScriptMapper.isComplexCharCode(codePoint);
-                }
-                complexChanged = complex != complexRun;
-            }
-
-            if (delimiterChanged || spanChanged || levelChanged || complexChanged) {
-
-                /* Create text run */
-                if (i != start) {
-                    run = addTextRun(layout, chars, start, i - start,
-                                     font, span, bidiLevel, complexRun);
-                    if (complexRun) {
-                        flags |= FLAGS_HAS_COMPLEX;
-                    }
-                    start = i;
-                }
-
-                if (delimiterChanged) {
-                    i++;
-                    /* Only merge \r\n when the are in the same text span */
-                    if (ch == '\r' && i < spanEnd && chars[i] == '\n') {
-                        i++;
-                    }
-
-                    /* Create delimiter run */
-                    run = new TextRun(start, i - start, bidiLevel, false,
-                                      ScriptMapper.COMMON, span, 0, false);
-                    if (ch == '\t') {
-                        run.setTab();
-                        flags |= FLAGS_HAS_TABS;
-                    } else {
-                        run.setLinebreak();
-                    }
-                    layout.addTextRun(run);
-
-                    start = i;
-                    if (i == length) break;
-                    spanChanged = i >= spanEnd;
-                    levelChanged = i >= bidiEnd;
-                }
-                if (spanChanged) {
-                    /* Only true for rich text (spans != null) */
-                    span = spans[++spanIndex];
-                    spanEnd += span.getText().length();
-                    font = (PGFont)span.getFont();
-                    if (font == null) {
-                        flags |= FLAGS_HAS_EMBEDDED;
-                    } else {
-                        FontResource fr = font.getFontResource();
-                        int requestedFeatures = font.getFeatures();
-                        int supportedFeatures = fr.getFeatures();
-                        feature = (requestedFeatures & supportedFeatures) != 0;
-                    }
-                }
-                if (levelChanged) {
-                    bidiIndex++;
-                    /* Temporary Code: See RT-26997 */
-//                    bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
-                    bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
-                    bidiEnd = bidi.getRunLimit(bidiIndex);
-                    if ((bidiLevel & 1) != 0) {
-                        flags |= FLAGS_HAS_BIDI;
-                    }
-                }
-
-                if (complexChanged) {
-                    if (delimiterChanged) {
-                        ch = chars[i]; /* update ch because of delimiterChanged */
-                        if (Character.isHighSurrogate(ch)) {
-                            /* Only merge surrogate when the pair is in the same span */
-                            if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
-                                codePoint = Character.toCodePoint(ch, chars[++i]);
-                            }
-                        }
-                        if (Character.isWhitespace(codePoint)) {
-                            complex = feature || complexRun;
-                        } else {
-                            complex = feature || ScriptMapper.isComplexCharCode(codePoint);
-                        }
-                    }
-                    complexRun = complex;
-                }
-            }
-            if (!delimiterChanged) i++;
-        }
-
-        /* Create final text run */
-        if (start < length) {
-            addTextRun(layout, chars, start, length - start,
-                       font, span, bidiLevel, complexRun);
-            if (complexRun) {
-                flags |= FLAGS_HAS_COMPLEX;
-            }
-        } else {
-            /* Ensure every lines has at least one run */
-            if (run == null || run.isLinebreak()) {
-                run = new TextRun(start, 0, (byte)0, false,
-                                  ScriptMapper.COMMON, span, 0, false);
-                layout.addTextRun(run);
-            }
-        }
-        if (bidi != null) {
-            if (!bidi.baseIsLeftToRight()) {
-                flags |= FLAGS_RTL_BASE;
-            }
-        }
-        flags |= FLAGS_ANALYSIS_VALID;
-        return flags;
-    }
-
 }
--- a/modules/graphics/src/main/java/com/sun/javafx/font/pango/PangoGlyphLayout.java	Mon Jul 08 20:36:12 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/font/pango/PangoGlyphLayout.java	Mon Jul 08 11:23:09 2013 -0700
@@ -25,21 +25,12 @@
 
 package com.sun.javafx.font.pango;
 
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_ANALYSIS_VALID;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_BIDI;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_COMPLEX;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_EMBEDDED;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_TABS;
-import static com.sun.javafx.scene.text.TextLayout.FLAGS_RTL_BASE;
-
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.CoderResult;
 import java.nio.charset.StandardCharsets;
-import java.text.Bidi;
-
 import com.sun.javafx.font.CompositeFontResource;
 import com.sun.javafx.font.FontResource;
 import com.sun.javafx.font.FontStrike;
@@ -48,7 +39,6 @@
 import com.sun.javafx.scene.text.TextSpan;
 import com.sun.javafx.text.GlyphLayout;
 import com.sun.javafx.text.PrismTextLayout;
-import com.sun.javafx.text.ScriptMapper;
 import com.sun.javafx.text.TextRun;
 
 class PangoGlyphLayout extends GlyphLayout {
@@ -80,279 +70,88 @@
         return slot;
     }
 
-    private TextRun addTextRun(PrismTextLayout layout, char[] chars, int start, int length,
-                               PGFont font, TextSpan span, byte level, boolean complex) {
+    protected TextRun addTextRun(PrismTextLayout layout, char[] chars, int start,
+                                 int length, PGFont font, TextSpan span, byte level) {
 
         TextRun textRun = null;
-        if (complex) {
-            Charset utf8 = StandardCharsets.UTF_8;
-            CharsetEncoder encoder = utf8.newEncoder();
-            CharBuffer in = CharBuffer.wrap(chars, start, length);
-            int capacity = (int)(length * (double)encoder.averageBytesPerChar());
-            ByteBuffer out = ByteBuffer.allocateDirect(capacity);
-            CoderResult result = encoder.encode(in, out, true);
-            if (result.isOverflow()) {
-                capacity = (int)(length * (double)encoder.maxBytesPerChar());
-                in.rewind();
-                out = ByteBuffer.allocateDirect(capacity);
-                encoder.encode(in, out, true);
-                if (PrismFontFactory.debugFonts) {
-                    System.err.println("[PANGO] ByteBuffer capacity increased " + out);
-                }
+        Charset utf8 = StandardCharsets.UTF_8;
+        CharsetEncoder encoder = utf8.newEncoder();
+        CharBuffer in = CharBuffer.wrap(chars, start, length);
+        int capacity = (int)(length * (double)encoder.averageBytesPerChar());
+        ByteBuffer out = ByteBuffer.allocateDirect(capacity);
+        CoderResult result = encoder.encode(in, out, true);
+        if (result.isOverflow()) {
+            capacity = (int)(length * (double)encoder.maxBytesPerChar());
+            in.rewind();
+            out = ByteBuffer.allocateDirect(capacity);
+            encoder.encode(in, out, true);
+            if (PrismFontFactory.debugFonts) {
+                System.err.println("[PANGO] ByteBuffer capacity increased " + out);
+            }
+        }
+
+        FontResource fr = font.getFontResource();
+        boolean composite = fr instanceof CompositeFontResource;
+        if (composite) {
+            fr = ((CompositeFontResource)fr).getSlotResource(0);
+        }
+
+        long fontmap = OS.pango_ft2_font_map_new();
+        long context = OS.pango_font_map_create_context(fontmap);
+        float size = font.getSize();
+        int style = fr.isItalic() ? OS.PANGO_STYLE_ITALIC : OS.PANGO_STYLE_NORMAL;
+        int weight = fr.isBold() ? OS.PANGO_WEIGHT_BOLD : OS.PANGO_WEIGHT_NORMAL;
+        long desc = OS.pango_font_description_new();
+        OS.pango_font_description_set_family(desc, fr.getFamilyName());
+        OS.pango_font_description_set_absolute_size(desc, size * OS.PANGO_SCALE);
+        OS.pango_font_description_set_stretch(desc, OS.PANGO_STRETCH_NORMAL);
+        OS.pango_font_description_set_style(desc, style);
+        OS.pango_font_description_set_weight(desc, weight);
+        long attrList = OS.pango_attr_list_new();
+        long attr = OS.pango_attr_font_desc_new(desc);
+        OS.pango_attr_list_insert(attrList, attr);
+        if (!composite) {
+            attr = OS.pango_attr_fallback_new(false);
+            OS.pango_attr_list_insert(attrList, attr);
+        }
+        long runs = OS.pango_itemize(context, out, 0, out.position(), attrList, 0);
+        int runsCount = OS.g_list_length(runs);
+        int runStart = start;
+        for (int i = 0; i < runsCount; i++) {
+            long pangoItem = OS.g_list_nth_data(runs, i);
+            PangoGlyphString glyphString = OS.pango_shape(out, pangoItem);
+            OS.pango_item_free(pangoItem);
+            int slot = composite ? getSlot(font, glyphString) : 0;
+            int glyphCount = glyphString.num_glyphs;
+            int[] glyphs = new int[glyphCount];
+            float[] pos = new float[glyphCount*2+2];
+            PangoGlyphInfo info = null;
+            int k = 2;
+            int width = 0;
+            for (int j = 0; j < glyphCount; j++) {
+                info = glyphString.glyphs[j];
+                glyphs[j] = slot | info.glyph;
+                width += info.width;
+                pos[k] = ((float)width) / OS.PANGO_SCALE;
+                k += 2;
             }
 
-            FontResource fr = font.getFontResource();
-            boolean composite = fr instanceof CompositeFontResource;
-            if (composite) {
-                fr = ((CompositeFontResource)fr).getSlotResource(0);
-            }
-
-            long fontmap = OS.pango_ft2_font_map_new();
-            long context = OS.pango_font_map_create_context(fontmap);
-            float size = font.getSize();
-            int style = fr.isItalic() ? OS.PANGO_STYLE_ITALIC : OS.PANGO_STYLE_NORMAL;
-            int weight = fr.isBold() ? OS.PANGO_WEIGHT_BOLD : OS.PANGO_WEIGHT_NORMAL;
-            long desc = OS.pango_font_description_new();
-            OS.pango_font_description_set_family(desc, fr.getFamilyName());
-            OS.pango_font_description_set_absolute_size(desc, size * OS.PANGO_SCALE);
-            OS.pango_font_description_set_stretch(desc, OS.PANGO_STRETCH_NORMAL);
-            OS.pango_font_description_set_style(desc, style);
-            OS.pango_font_description_set_weight(desc, weight);
-            long attrList = OS.pango_attr_list_new();
-            long attr = OS.pango_attr_font_desc_new(desc);
-            OS.pango_attr_list_insert(attrList, attr);
-            if (!composite) {
-                attr = OS.pango_attr_fallback_new(false);
-                OS.pango_attr_list_insert(attrList, attr);
-            }
-            long runs = OS.pango_itemize(context, out, 0, out.position(), attrList, 0);
-            int runsCount = OS.g_list_length(runs);
-            int runStart = start;
-            for (int i = 0; i < runsCount; i++) {
-                long pangoItem = OS.g_list_nth_data(runs, i);
-                PangoGlyphString glyphString = OS.pango_shape(out, pangoItem);
-                OS.pango_item_free(pangoItem);
-                int slot = composite ? getSlot(font, glyphString) : 0;
-                int glyphCount = glyphString.num_glyphs;
-                int[] glyphs = new int[glyphCount];
-                float[] pos = new float[glyphCount*2+2];
-                PangoGlyphInfo info = null;
-                int k = 2;
-                int width = 0;
-                for (int j = 0; j < glyphCount; j++) {
-                    info = glyphString.glyphs[j];
-                    glyphs[j] = slot | info.glyph;
-                    width += info.width;
-                    pos[k] = ((float)width) / OS.PANGO_SCALE;
-                    k += 2;
-                }
-
-                int runLength = glyphString.num_chars;
-                textRun = new TextRun(runStart, runLength, level, true, 0, span, 0, false);
-                textRun.shape(glyphCount, glyphs, pos, glyphString.log_clusters);
-                layout.addTextRun(textRun);
-                runStart += runLength;
-            }
-            OS.g_list_free(runs);
-            /* pango_attr_list_unref() also frees the attributes it contains */
-            OS.pango_attr_list_unref(attrList);
-            OS.pango_font_description_free(desc);
-            OS.g_object_unref(context);
-            OS.g_object_unref(fontmap);
-        } else {
-            textRun = new TextRun(start, length, level, false, 0, span, 0, false);
+            int runLength = glyphString.num_chars;
+            textRun = new TextRun(runStart, runLength, level, true, 0, span, 0, false);
+            textRun.shape(glyphCount, glyphs, pos, glyphString.log_clusters);
             layout.addTextRun(textRun);
+            runStart += runLength;
         }
+        OS.g_list_free(runs);
+        /* pango_attr_list_unref() also frees the attributes it contains */
+        OS.pango_attr_list_unref(attrList);
+        OS.pango_font_description_free(desc);
+        OS.g_object_unref(context);
+        OS.g_object_unref(fontmap);
         return textRun;
     }
 
     public void layout(TextRun run, PGFont font, FontStrike strike, char[] text) {
         // Nothing - complex run are analyzed by Pango during break run
     }
-
-    public int breakRuns(PrismTextLayout layout, char[] chars, int flags) {
-        int length = chars.length;
-        boolean complexRun = false;
-        boolean complex = false;
-        boolean feature = false;
-
-        boolean checkComplex = true;
-        boolean checkBidi = true;
-        if ((flags & FLAGS_ANALYSIS_VALID) != 0) {
-            /* Avoid work when it is known neither complex
-             * text nor bidi are not present. */
-            checkComplex = (flags & FLAGS_HAS_COMPLEX) != 0;
-            checkBidi = (flags & FLAGS_HAS_BIDI) != 0;
-        }
-
-        TextRun run = null;
-        Bidi bidi = null;
-        byte bidiLevel = 0;
-        int bidiEnd = length;
-        int bidiIndex = 0;
-        int spanIndex = 0;
-        TextSpan span = null;
-        int spanEnd = length;
-        PGFont font = null;
-        TextSpan[] spans = layout.getTextSpans();
-        if (spans != null) {
-            if (spans.length > 0) {
-                span = spans[spanIndex];
-                spanEnd = span.getText().length();
-                font = (PGFont)span.getFont();
-                if (font == null) {
-                    flags |= FLAGS_HAS_EMBEDDED;
-                }
-            }
-        } else {
-            font = layout.getFont();
-        }
-        if (font != null) {
-            FontResource fr = font.getFontResource();
-            int requestedFeatures = font.getFeatures();
-            int supportedFeatures = fr.getFeatures();
-            feature = (requestedFeatures & supportedFeatures) != 0;
-        }
-        if (checkBidi && length > 0) {
-            int direction = layout.getDirection();
-            bidi = new Bidi(chars, 0, null, 0, length, direction);
-            /* Temporary Code: See RT-26997 */
-//            bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
-            bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
-            bidiEnd = bidi.getRunLimit(bidiIndex);
-            if ((bidiLevel & 1) != 0) {
-                flags |= FLAGS_HAS_BIDI;
-            }
-        }
-
-        int start = 0;
-        int i = 0;
-        while (i < length) {
-            char ch = chars[i];
-            int codePoint = ch;
-
-            boolean delimiterChanged = ch == '\t' || ch == '\n' || ch == '\r';
-            boolean spanChanged = i >= spanEnd;
-            boolean levelChanged = i >= bidiEnd;
-            boolean complexChanged = false;
-
-            if (checkComplex) {
-                if (Character.isHighSurrogate(ch)) {
-                    /* Only merge surrogate when the pair is in the same span. */
-                    if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
-                        codePoint = Character.toCodePoint(ch, chars[++i]);
-                    }
-                }
-                if (Character.isWhitespace(codePoint)) {
-                    complex = feature || complexRun;
-                } else {
-                    complex = feature || ScriptMapper.isComplexCharCode(codePoint);
-                }
-                complexChanged = complex != complexRun;
-            }
-
-            if (delimiterChanged || spanChanged || levelChanged || complexChanged) {
-
-                /* Create text run */
-                if (i != start) {
-                    run = addTextRun(layout, chars, start, i - start,
-                                     font, span, bidiLevel, complexRun);
-                    if (complexRun) {
-                        flags |= FLAGS_HAS_COMPLEX;
-                    }
-                    start = i;
-                }
-
-                if (delimiterChanged) {
-                    i++;
-                    /* Only merge \r\n when the are in the same text span */
-                    if (ch == '\r' && i < spanEnd && chars[i] == '\n') {
-                        i++;
-                    }
-
-                    /* Create delimiter run */
-                    run = new TextRun(start, i - start, bidiLevel, false,
-                                      ScriptMapper.COMMON, span, 0, false);
-                    if (ch == '\t') {
-                        run.setTab();
-                        flags |= FLAGS_HAS_TABS;
-                    } else {
-                        run.setLinebreak();
-                    }
-                    layout.addTextRun(run);
-
-                    start = i;
-                    if (i == length) break;
-                    spanChanged = i >= spanEnd;
-                    levelChanged = i >= bidiEnd;
-                }
-                if (spanChanged) {
-                    /* Only true for rich text (spans != null) */
-                    span = spans[++spanIndex];
-                    spanEnd += span.getText().length();
-                    font = (PGFont)span.getFont();
-                    if (font == null) {
-                        flags |= FLAGS_HAS_EMBEDDED;
-                    } else {
-                        FontResource fr = font.getFontResource();
-                        int requestedFeatures = font.getFeatures();
-                        int supportedFeatures = fr.getFeatures();
-                        feature = (requestedFeatures & supportedFeatures) != 0;
-                    }
-                }
-                if (levelChanged) {
-                    bidiIndex++;
-                    /* Temporary Code: See RT-26997 */
-//                    bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
-                    bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
-                    bidiEnd = bidi.getRunLimit(bidiIndex);
-                    if ((bidiLevel & 1) != 0) {
-                        flags |= FLAGS_HAS_BIDI;
-                    }
-                }
-
-                if (complexChanged) {
-                    if (delimiterChanged) {
-                        ch = chars[i]; /* update ch because of delimiterChanged */
-                        if (Character.isHighSurrogate(ch)) {
-                            /* Only merge surrogate when the pair is in the same span */
-                            if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
-                                codePoint = Character.toCodePoint(ch, chars[++i]);
-                            }
-                        }
-                        if (Character.isWhitespace(codePoint)) {
-                            complex = feature || complexRun;
-                        } else {
-                            complex = feature || ScriptMapper.isComplexCharCode(codePoint);
-                        }
-                    }
-                    complexRun = complex;
-                }
-            }
-            if (!delimiterChanged) i++;
-        }
-
-        /* Create final text run */
-        if (start < length) {
-            addTextRun(layout, chars, start, length - start,
-                       font, span, bidiLevel, complexRun);
-            if (complexRun) {
-                flags |= FLAGS_HAS_COMPLEX;
-            }
-        } else {
-            /* Ensure every lines has at least one run */
-            if (run == null || run.isLinebreak()) {
-                run = new TextRun(start, 0, (byte)0, false,
-                                  ScriptMapper.COMMON, span, 0, false);
-                layout.addTextRun(run);
-            }
-        }
-        if (bidi != null) {
-            if (!bidi.baseIsLeftToRight()) {
-                flags |= FLAGS_RTL_BASE;
-            }
-        }
-        flags |= FLAGS_ANALYSIS_VALID;
-        return flags;
-    }
 }
--- a/modules/graphics/src/main/java/com/sun/javafx/text/GlyphLayout.java	Mon Jul 08 20:36:12 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/text/GlyphLayout.java	Mon Jul 08 11:23:09 2013 -0700
@@ -56,9 +56,20 @@
  */
 package com.sun.javafx.text;
 
+import static com.sun.javafx.scene.text.TextLayout.FLAGS_ANALYSIS_VALID;
+import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_BIDI;
+import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_COMPLEX;
+import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_EMBEDDED;
+import static com.sun.javafx.scene.text.TextLayout.FLAGS_HAS_TABS;
+import static com.sun.javafx.scene.text.TextLayout.FLAGS_RTL_BASE;
+
+import java.text.Bidi;
+
+import com.sun.javafx.font.FontResource;
 import com.sun.javafx.font.FontStrike;
 import com.sun.javafx.font.PGFont;
 import com.sun.javafx.font.PrismFontFactory;
+import com.sun.javafx.scene.text.TextSpan;
 
 public abstract class GlyphLayout {
 
@@ -84,7 +95,211 @@
 
     public static final int HINTING = 1 << 4;
 
-    public abstract int breakRuns(PrismTextLayout layout, char[] text, int flags);
+    protected TextRun addTextRun(PrismTextLayout layout, char[] chars,
+                                 int start, int length,
+                                 PGFont font, TextSpan span, byte level) {
+        /* subclass can overwrite this method in order to handle complex text */
+        TextRun run = new TextRun(start, length, level, false, 0, span, 0, false);
+        layout.addTextRun(run);
+        return run;
+    }
+
+    private TextRun addTextRun(PrismTextLayout layout, char[] chars,
+                               int start, int length, PGFont font,
+                               TextSpan span, byte level, boolean complex) {
+        if (complex) {
+            return addTextRun(layout, chars, start, length, font, span, level);
+        }
+        TextRun run = new TextRun(start, length, level, false, 0, span, 0, false);
+        layout.addTextRun(run);
+        return run;
+    }
+
+    public int breakRuns(PrismTextLayout layout, char[] chars, int flags) {
+        int length = chars.length;
+        boolean complexRun = false;
+        boolean complex = false;
+        boolean feature = false;
+
+        boolean checkComplex = true;
+        boolean checkBidi = true;
+        if ((flags & FLAGS_ANALYSIS_VALID) != 0) {
+            /* Avoid work when it is known neither complex
+             * text nor bidi are not present. */
+            checkComplex = (flags & FLAGS_HAS_COMPLEX) != 0;
+            checkBidi = (flags & FLAGS_HAS_BIDI) != 0;
+        }
+
+        TextRun run = null;
+        Bidi bidi = null;
+        byte bidiLevel = 0;
+        int bidiEnd = length;
+        int bidiIndex = 0;
+        int spanIndex = 0;
+        TextSpan span = null;
+        int spanEnd = length;
+        PGFont font = null;
+        TextSpan[] spans = layout.getTextSpans();
+        if (spans != null) {
+            if (spans.length > 0) {
+                span = spans[spanIndex];
+                spanEnd = span.getText().length();
+                font = (PGFont)span.getFont();
+                if (font == null) {
+                    flags |= FLAGS_HAS_EMBEDDED;
+                }
+            }
+        } else {
+            font = layout.getFont();
+        }
+        if (font != null) {
+            FontResource fr = font.getFontResource();
+            int requestedFeatures = font.getFeatures();
+            int supportedFeatures = fr.getFeatures();
+            feature = (requestedFeatures & supportedFeatures) != 0;
+        }
+        if (checkBidi && length > 0) {
+            int direction = layout.getDirection();
+            bidi = new Bidi(chars, 0, null, 0, length, direction);
+            /* Temporary Code: See RT-26997 */
+//            bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
+            bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
+            bidiEnd = bidi.getRunLimit(bidiIndex);
+            if ((bidiLevel & 1) != 0) {
+                flags |= FLAGS_HAS_BIDI;
+            }
+        }
+
+        int start = 0;
+        int i = 0;
+        while (i < length) {
+            char ch = chars[i];
+            int codePoint = ch;
+
+            boolean delimiterChanged = ch == '\t' || ch == '\n' || ch == '\r';
+            boolean spanChanged = i >= spanEnd;
+            boolean levelChanged = i >= bidiEnd;
+            boolean complexChanged = false;
+
+            if (checkComplex) {
+                if (Character.isHighSurrogate(ch)) {
+                    /* Only merge surrogate when the pair is in the same span. */
+                    if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
+                        codePoint = Character.toCodePoint(ch, chars[++i]);
+                    }
+                }
+                if (Character.isWhitespace(codePoint)) {
+                    complex = feature || complexRun;
+                } else {
+                    complex = feature || ScriptMapper.isComplexCharCode(codePoint);
+                }
+                complexChanged = complex != complexRun;
+            }
+
+            if (delimiterChanged || spanChanged || levelChanged || complexChanged) {
+
+                /* Create text run */
+                if (i != start) {
+                    run = addTextRun(layout, chars, start, i - start,
+                                     font, span, bidiLevel, complexRun);
+                    if (complexRun) {
+                        flags |= FLAGS_HAS_COMPLEX;
+                    }
+                    start = i;
+                }
+
+                if (delimiterChanged) {
+                    i++;
+                    /* Only merge \r\n when the are in the same text span */
+                    if (ch == '\r' && i < spanEnd && chars[i] == '\n') {
+                        i++;
+                    }
+
+                    /* Create delimiter run */
+                    run = new TextRun(start, i - start, bidiLevel, false,
+                                      ScriptMapper.COMMON, span, 0, false);
+                    if (ch == '\t') {
+                        run.setTab();
+                        flags |= FLAGS_HAS_TABS;
+                    } else {
+                        run.setLinebreak();
+                    }
+                    layout.addTextRun(run);
+
+                    start = i;
+                    if (i == length) break;
+                    spanChanged = i >= spanEnd;
+                    levelChanged = i >= bidiEnd;
+                }
+                if (spanChanged) {
+                    /* Only true for rich text (spans != null) */
+                    span = spans[++spanIndex];
+                    spanEnd += span.getText().length();
+                    font = (PGFont)span.getFont();
+                    if (font == null) {
+                        flags |= FLAGS_HAS_EMBEDDED;
+                    } else {
+                        FontResource fr = font.getFontResource();
+                        int requestedFeatures = font.getFeatures();
+                        int supportedFeatures = fr.getFeatures();
+                        feature = (requestedFeatures & supportedFeatures) != 0;
+                    }
+                }
+                if (levelChanged) {
+                    bidiIndex++;
+                    /* Temporary Code: See RT-26997 */
+//                    bidiLevel = (byte)bidi.getRunLevel(bidiIndex);
+                    bidiLevel = (byte)bidi.getLevelAt(bidi.getRunStart(bidiIndex));
+                    bidiEnd = bidi.getRunLimit(bidiIndex);
+                    if ((bidiLevel & 1) != 0) {
+                        flags |= FLAGS_HAS_BIDI;
+                    }
+                }
+
+                if (complexChanged) {
+                    if (delimiterChanged) {
+                        ch = chars[i]; /* update ch because of delimiterChanged */
+                        if (Character.isHighSurrogate(ch)) {
+                            /* Only merge surrogate when the pair is in the same span */
+                            if (i + 1 < spanEnd && Character.isLowSurrogate(chars[i + 1])) {
+                                codePoint = Character.toCodePoint(ch, chars[++i]);
+                            }
+                        }
+                        if (Character.isWhitespace(codePoint)) {
+                            complex = feature || complexRun;
+                        } else {
+                            complex = feature || ScriptMapper.isComplexCharCode(codePoint);
+                        }
+                    }
+                    complexRun = complex;
+                }
+            }
+            if (!delimiterChanged) i++;
+        }
+
+        /* Create final text run */
+        if (start < length) {
+            addTextRun(layout, chars, start, length - start,
+                       font, span, bidiLevel, complexRun);
+            if (complexRun) {
+                flags |= FLAGS_HAS_COMPLEX;
+            }
+        } else {
+            /* Ensure every lines has at least one run */
+            if (run == null || run.isLinebreak()) {
+                run = new TextRun(start, 0, (byte)0, false,
+                                  ScriptMapper.COMMON, span, 0, false);
+                layout.addTextRun(run);
+            }
+        }
+        if (bidi != null) {
+            if (!bidi.baseIsLeftToRight()) {
+                flags |= FLAGS_RTL_BASE;
+            }
+        }
+        flags |= FLAGS_ANALYSIS_VALID;
+        return flags;
+    }
 
     public abstract void layout(TextRun run, PGFont font,
                                 FontStrike strike, char[] text);