Пример #1
0
        private void PaintLineForeground(DeviceContext deviceContext, PaintOptions paintOptions,
            ScriptLine* scriptLine, ScriptParagraph* scriptParagraph,
            int scriptRunIndex, int scriptRunCount, int* visualToLogicalMap,
            int xStartPosition, int xEndPosition,
            Rectangle layoutRect, bool isSelected, bool skipObjects)
        {
            int yCurrentBaseline = scriptLine->Y + scriptLine->Ascent;
            int xCurrentPosition = scriptLine->X;
            for (int i = 0; i < scriptRunCount; i++)
            {
                int logicalScriptRunIndex = visualToLogicalMap[currentLayoutRightToLeft ? scriptRunCount - i - 1 : i];
                ScriptRun* scriptRun = scriptParagraph->ScriptRuns + logicalScriptRunIndex + scriptRunIndex;

                int measuredWidth;
                switch (scriptRun->RunKind)
                {
                    case RunKind.Text:
                    {
                        Style style = document.LookupStyle(scriptRun->StyleIndex);
                        ScriptMetrics scriptMetrics = deviceContext.SelectFont(style.Font);

                        int glyphIndexInParagraph, glyphCount;
                        measuredWidth = MeasureTextScriptRun(scriptParagraph, scriptRun,
                            logicalScriptRunIndex == 0 ? scriptLine->TruncatedLeadingCharsCount : 0,
                            logicalScriptRunIndex == scriptRunCount - 1 ? scriptLine->TruncatedTrailingCharsCount : 0,
                            out glyphIndexInParagraph, out glyphCount);

                        if (glyphCount > 0 && xCurrentPosition + measuredWidth > xStartPosition)
                        {
                            int x = currentLayoutRightToLeft
                                ? layoutRect.Right - xCurrentPosition - measuredWidth
                                : layoutRect.Left + xCurrentPosition;
                            int y = layoutRect.Top + yCurrentBaseline - scriptRun->Ascent;

                            deviceContext.SetTextColor(isSelected ? paintOptions.SelectedTextColor : style.Color);
                            ScriptTextOut(deviceContext.HDC, ref scriptMetrics.ScriptCache, x, y,
                                ExtTextOutOptions.NONE, null, &scriptRun->ScriptAnalysis,
                                scriptParagraph->Glyphs + glyphIndexInParagraph,
                                glyphCount,
                                scriptParagraph->GlyphAdvanceWidths + glyphIndexInParagraph,
                                null,
                                scriptParagraph->GlyphOffsets + glyphIndexInParagraph);
                        }
                        break;
                    }

                    case RunKind.Object:
                    {
                        measuredWidth = MeasureObjectScriptRun(scriptRun);

                        if (!skipObjects && xCurrentPosition + measuredWidth > xStartPosition)
                        {
                            int embeddedObjectCharIndex = scriptRun->CharIndexInParagraph + scriptParagraph->CharIndex;
                            EmbeddedObjectHost embeddedObjectHost;
                            if (embeddedObjectHosts.TryGetValue(embeddedObjectCharIndex, out embeddedObjectHost))
                            {
                                if (embeddedObjectHost.RequiresPaint)
                                {
                                    using (Graphics g = Graphics.FromHdc(deviceContext.HDC))
                                    {
                                        Rectangle bounds = GetDrawingBoundsOfEmbeddedObject(embeddedObjectHost, layoutRect.Location);
                                        embeddedObjectHost.Paint(g, paintOptions, bounds, currentLayoutRightToLeft);
                                    }
                                }
                            }
                        }
                        break;
                    }

                    case RunKind.Tab:
                    {
                        measuredWidth = MeasureTabScriptRun(scriptRun,
                            GetParagraphStyleAssumingItHasAtLeastOneRun(scriptParagraph),
                            xCurrentPosition, false);
                        break;
                    }

                    default:
                        throw new NotSupportedException();
                }

                xCurrentPosition += measuredWidth;

                if (xCurrentPosition >= xEndPosition)
                    break; // can't fit any more runs within the clip rectangle
            }
        }
Пример #2
0
        private void PaintLineBackgroundAndExcludeSelectedTextFromClipRegion(DeviceContext deviceContext, PaintOptions paintOptions,
            ScriptLine* scriptLine, ScriptParagraph* scriptParagraph,
            int scriptRunIndex, int scriptRunCount, int* visualToLogicalMap,
            int xStartPosition, int xEndPosition,
            Rectangle layoutRect, int selectedCharIndex, int selectedCharCount)
        {
            IntPtr brush = DeviceContext.GetStockObject(NativeConstants.DC_BRUSH);
            deviceContext.SetDCBrushColor(paintOptions.SelectedBackgroundColor);

            int yCurrentBaseline = scriptLine->Y + scriptLine->Ascent;
            int xCurrentPosition = scriptLine->X;
            for (int i = 0; i < scriptRunCount; i++)
            {
                int logicalScriptRunIndex = visualToLogicalMap[currentLayoutRightToLeft ? scriptRunCount - i - 1 : i];
                ScriptRun* scriptRun = scriptParagraph->ScriptRuns + logicalScriptRunIndex + scriptRunIndex;

                int measuredWidth;
                switch (scriptRun->RunKind)
                {
                    case RunKind.Text:
                    {
                        int glyphIndexInParagraph, glyphCount;
                        int truncatedLeadingCharsCount = logicalScriptRunIndex == 0
                            ? scriptLine->TruncatedLeadingCharsCount
                            : 0;
                        int truncatedTrailingCharsCount = logicalScriptRunIndex == scriptRunCount - 1
                            ? scriptLine->TruncatedTrailingCharsCount
                            : 0;
                        measuredWidth = MeasureTextScriptRun(scriptParagraph, scriptRun,
                            truncatedLeadingCharsCount, truncatedTrailingCharsCount,
                            out glyphIndexInParagraph, out glyphCount);

                        if (xCurrentPosition + measuredWidth > xStartPosition)
                        {
                            int scriptRunCharIndex = scriptParagraph->CharIndex + scriptRun->CharIndexInParagraph;
                            int leadingCharIndex = scriptRunCharIndex + truncatedLeadingCharsCount;
                            int trailingCharIndex = scriptRunCharIndex + scriptRun->CharCount -
                                truncatedTrailingCharsCount;
                            if (trailingCharIndex >= selectedCharIndex &&
                                leadingCharIndex <= selectedCharIndex + selectedCharCount)
                            {
                                int relativePositionOfSelection;
                                if (leadingCharIndex >= selectedCharIndex)
                                {
                                    relativePositionOfSelection = 0;
                                }
                                else
                                {
                                    relativePositionOfSelection = MeasureTextScriptRun(scriptParagraph, scriptRun,
                                        truncatedLeadingCharsCount,
                                        truncatedTrailingCharsCount + trailingCharIndex - selectedCharIndex,
                                        out glyphIndexInParagraph, out glyphCount);
                                }

                                int measuredWidthOfSelection;
                                if (trailingCharIndex <= selectedCharIndex + selectedCharCount)
                                {
                                    measuredWidthOfSelection = measuredWidth - relativePositionOfSelection;
                                }
                                else
                                {
                                    measuredWidthOfSelection = MeasureTextScriptRun(scriptParagraph, scriptRun,
                                        truncatedLeadingCharsCount + Math.Max(selectedCharIndex - leadingCharIndex, 0),
                                        truncatedTrailingCharsCount + trailingCharIndex - selectedCharIndex -
                                            selectedCharCount,
                                        out glyphIndexInParagraph, out glyphCount);
                                }

                                int x = currentLayoutRightToLeft
                                    ? layoutRect.Right - xCurrentPosition - measuredWidth
                                    : layoutRect.Left + xCurrentPosition;
                                int y = layoutRect.Top + yCurrentBaseline - scriptRun->Ascent;

                                if (scriptRun->ScriptAnalysis.fRTL)
                                    x += measuredWidth - relativePositionOfSelection - measuredWidthOfSelection;
                                else
                                    x += relativePositionOfSelection;

                                Rectangle selectedRect = new Rectangle(x, y, measuredWidthOfSelection, scriptRun->Height);
                                deviceContext.FillRect(selectedRect, brush);
                                deviceContext.ExcludeClipRect(selectedRect);
                            }
                        }
                        break;
                    }

                    case RunKind.Object:
                    case RunKind.Tab:
                    {
                        measuredWidth = scriptRun->RunKind == RunKind.Object
                            ? MeasureObjectScriptRun(scriptRun)
                            : MeasureTabScriptRun(scriptRun,
                                GetParagraphStyleAssumingItHasAtLeastOneRun(scriptParagraph),
                                xCurrentPosition, false);

                        if (xCurrentPosition + measuredWidth > xStartPosition)
                        {
                            int leadingCharIndex = scriptParagraph->CharIndex + scriptRun->CharIndexInParagraph;
                            int trailingCharIndex = leadingCharIndex + 1;
                            if (trailingCharIndex >= selectedCharIndex &&
                                leadingCharIndex <= selectedCharIndex + selectedCharCount)
                            {
                                int x = currentLayoutRightToLeft
                                    ? layoutRect.Right - xCurrentPosition - measuredWidth
                                    : layoutRect.Left + xCurrentPosition;
                                int y = layoutRect.Top + yCurrentBaseline - scriptRun->Ascent;

                                Rectangle selectedRect = new Rectangle(x, y, measuredWidth, scriptRun->Height);
                                deviceContext.FillRect(selectedRect, brush);
                                // Don't exclude clip rect for objects and tabs.
                                //deviceContext.ExcludeClipRect(selectedRect);
                            }
                        }
                        break;
                    }

                    default:
                        throw new NotSupportedException();
                }

                xCurrentPosition += measuredWidth;

                if (xCurrentPosition >= xEndPosition)
                    break; // can't fit any more runs within the clip rectangle
            }
        }
Пример #3
0
        private void PaintRegion(DeviceContext deviceContext, Rectangle layoutRect, Rectangle clipRect,
            IntPtr clipRegion, PaintOptions paintOptions, int selectedCharIndex, int selectedCharCount)
        {
            int yStartPosition = clipRect.Top - layoutRect.Top;
            int yEndPosition = clipRect.Bottom - layoutRect.Top;
            int firstScriptLineIndex;
            ScriptLine* scriptLine = GetScriptLineAtYPositionOrNullIfNone(yStartPosition, out firstScriptLineIndex);
            if (scriptLine == null)
                return;

            ScriptLine* endScriptLine = GetScriptLineZero() + scriptLineBuffer.Count;

            int xStartPosition, xEndPosition;
            if (currentLayoutRightToLeft)
            {
                xStartPosition = layoutRect.Right - clipRect.Right;
                xEndPosition = layoutRect.Right - clipRect.Left;
            }
            else
            {
                xStartPosition = clipRect.Left - layoutRect.Left;
                xEndPosition = clipRect.Right - layoutRect.Left;
            }

            // Note: Selection may extend beyond the range of the layout if improper index and count
            //       values were provided.  This is ok since we only care about the intersection of the
            //       selected range with actual range of characters on the lines.
            if (selectedCharCount < 0)
            {
                selectedCharIndex += selectedCharCount;
                selectedCharCount = -selectedCharCount;
            }

            deviceContext.SetBkMode(NativeConstants.TRANSPARENT);

            for (; scriptLine != endScriptLine && scriptLine->Y < yEndPosition; scriptLine++)
            {
                int scriptRunCount = scriptLine->ScriptRunCount;
                if (scriptRunCount != 0)
                {
                    ScriptParagraph* scriptParagraph = GetScriptParagraph(deviceContext, scriptLine->ParagraphIndex);
                    int scriptRunIndex = scriptLine->ScriptRunIndex;
                    int* visualToLogicalMap = GetTempVisualToLogicalMap(scriptParagraph->ScriptRuns + scriptRunIndex,
                        scriptRunCount);

                    if (selectedCharCount == 0
                        || selectedCharIndex >= scriptParagraph->CharIndex
                                + scriptParagraph->ScriptRuns[scriptRunIndex + scriptRunCount - 1].EndCharIndexInParagraph
                                - scriptLine->TruncatedTrailingCharsCount
                        || selectedCharIndex + selectedCharCount < scriptParagraph->CharIndex
                                + scriptParagraph->ScriptRuns[scriptRunIndex].CharIndexInParagraph
                                + scriptLine->TruncatedLeadingCharsCount)
                    {
                        deviceContext.SelectClipRegion(clipRegion);

                        // No selection.  Just paint the line as usual.
                        PaintLineForeground(deviceContext, paintOptions,
                            scriptLine, scriptParagraph, scriptRunIndex, scriptRunCount, visualToLogicalMap,
                            xStartPosition, xEndPosition,
                            layoutRect, false, false);
                    }
                    else
                    {
                        deviceContext.SelectClipRegion(clipRegion);

                        // Paint background.
                        PaintLineBackgroundAndExcludeSelectedTextFromClipRegion(deviceContext, paintOptions,
                            scriptLine, scriptParagraph, scriptRunIndex, scriptRunCount, visualToLogicalMap,
                            xStartPosition, xEndPosition,
                            layoutRect, selectedCharIndex, selectedCharCount);

                        // Paint all text and objects except in selected areas.
                        PaintLineForeground(deviceContext, paintOptions,
                            scriptLine, scriptParagraph, scriptRunIndex, scriptRunCount, visualToLogicalMap,
                            xStartPosition, xEndPosition,
                            layoutRect, false, false);

                        // Invert the clip region.
                        IntPtr lineRegion = DeviceContext.CreateRectRegion(clipRect);
                        deviceContext.XorClipRegion(lineRegion);
                        DeviceContext.DeleteObject(lineRegion);

                        // Paint all text in selected areas.
                        PaintLineForeground(deviceContext, paintOptions,
                            scriptLine, scriptParagraph, scriptRunIndex, scriptRunCount, visualToLogicalMap,
                            xStartPosition, xEndPosition,
                            layoutRect, true, true);
                    }
                }
            }
        }
Пример #4
0
        /// <summary>
        /// Analyzes a <see cref="Paragraph" /> to populate a <see cref="ScriptParagraph"/>.
        /// </summary>
        private void AnalyzeParagraph(DeviceContext deviceContext, Paragraph* paragraph, ScriptParagraph* scriptParagraph)
        {
            int paragraphCharIndex = paragraph->CharIndex;
            int paragraphCharCount = paragraph->CharCount;

            scriptParagraph->CharIndex = paragraphCharIndex;
            scriptParagraph->CharCount = paragraphCharCount;
            scriptParagraph->ScriptRunCount = 0;
            scriptParagraph->GlyphCount = 0;

            Run* runZero = document.GetRunZero();
            Run* runs = runZero + paragraph->RunIndex;
            int runCount = paragraph->RunCount;

            // Skip paragraphs with no runs (this is the sentinel at end of document)
            if (runCount == 0)
            {
                return;
            }

            // Handle paragraphs with exactly one empty run (simple newline)
            Debug.Assert(paragraphCharCount != 0, "A paragraph with at least one run should always have at least one character.");

            char* charZero = document.GetCharZero();
            char* chars = charZero + paragraphCharIndex;

            // Step 1. Itemize the script items within the paragraph.
            //         Each script item represents a chunk of text (such as a word)
            //         for Unicode rendering purposes.
            scriptParagraph->EnsureCharCapacity(paragraphCharCount);

            SCRIPT_ITEM* tempScriptItems;
            int tempScriptItemCount;
            ScriptItemize(chars, paragraphCharCount, currentLayoutRightToLeft, tempScriptItemBuffer, out tempScriptItems, out tempScriptItemCount);

            // Step 2. Compute logical attributes for characters in the paragraph for word-break purposes.
            ScriptBreak(scriptParagraph, chars, tempScriptItems, tempScriptItemCount);

            // Step 3. Split the Runs on SCRIPT_ITEM boundaries to produce ScriptRuns.
            SplitScriptRuns(scriptParagraph, runs, runCount, tempScriptItems, tempScriptItemCount);

            // Step 4. Shape and Place glyphs and Measure embedded objects one run at a time.
            //         We do this in linear order because it is simpler and we do not care about
            //         visual order until later when we pack runs into lines and draw them.
            ScriptRun* scriptRun = scriptParagraph->ScriptRuns;
            ScriptRun* endScriptRun = scriptRun + scriptParagraph->ScriptRunCount;
            for (; scriptRun != endScriptRun; scriptRun++)
            {
                Style scriptRunStyle = document.LookupStyle(scriptRun->StyleIndex);

                switch (scriptRun->RunKind)
                {
                    case RunKind.Text:
                    {
                        // Fill in basic metrics.
                        ScriptMetrics scriptMetrics = deviceContext.SelectFont(scriptRunStyle.Font);
                        scriptRun->Height = scriptMetrics.Height;
                        scriptRun->Descent = scriptMetrics.Descent;
                        scriptRun->TopMargin = 0;
                        scriptRun->BottomMargin = 0;

                        // Set glyphs.
                        ScriptShapeAndPlace(deviceContext.HDC, ref scriptMetrics.ScriptCache, scriptParagraph, scriptRun, chars);
                        break;
                    }

                    case RunKind.Object:
                    {
                        // No glyphs for an embedded object.
                        scriptRun->GlyphCount = 0;
                        scriptRun->GlyphIndexInParagraph = 0;

                        // Fill in object measurements.
                        int charIndex = scriptRun->CharIndexInParagraph + paragraphCharIndex;
                        EmbeddedObjectHost embeddedObjectHost;
                        EmbeddedObjectMeasurements embeddedObjectMeasurements;
                        if (embeddedObjectHosts.TryGetValue(charIndex, out embeddedObjectHost))
                            embeddedObjectMeasurements = embeddedObjectHost.Measure();
                        else
                            embeddedObjectMeasurements = EmbeddedObjectHost.CreateDefaultEmbeddedObjectMeasurements();

                        scriptRun->Height = embeddedObjectMeasurements.Size.Height;
                        scriptRun->Descent = embeddedObjectMeasurements.Descent;
                        scriptRun->TopMargin = embeddedObjectMeasurements.Margin.Top;
                        scriptRun->BottomMargin = embeddedObjectMeasurements.Margin.Bottom;
                        scriptRun->ABC.abcA = embeddedObjectMeasurements.Margin.Left;
                        scriptRun->ABC.abcB = embeddedObjectMeasurements.Size.Width;
                        scriptRun->ABC.abcC = embeddedObjectMeasurements.Margin.Right;

                        // Change the logical attributes of the character so that an object behaves like
                        // a word rather than whitespace when performing word wrap.
                        scriptParagraph->CharLogicalAttributes[scriptRun->CharIndexInParagraph].SetfSoftBreakfCharStopAndfWordStop();
                        break;
                    }

                    case RunKind.Tab:
                    {
                        // No glyphs for a tab.
                        scriptRun->GlyphCount = 0;
                        scriptRun->GlyphIndexInParagraph = 0;

                        // Use same metrics as inline text.
                        ScriptMetrics scriptMetrics = deviceContext.SelectFont(scriptRunStyle.Font);
                        scriptRun->Height = scriptMetrics.Height;
                        scriptRun->Descent = scriptMetrics.Descent;
                        scriptRun->TopMargin = 0;
                        scriptRun->BottomMargin = 0;
                        scriptRun->ABC.abcA = 0;
                        scriptRun->ABC.abcB = -1; // will be filled in when the TAB is measured
                        scriptRun->ABC.abcC = 0;

                        // Change the logical attributes of the character to make it whitespace since Uniscribe
                        // does not consider a TAB to be whitespace.
                        scriptParagraph->CharLogicalAttributes[scriptRun->CharIndexInParagraph].SetfWhiteSpace();
                        break;
                    }
                }
            }
        }
Пример #5
0
        /// <summary>
        /// Gets a fully analyzed <see cref="ScriptParagraph"/> by paragraph index.
        /// </summary>
        private ScriptParagraph* GetScriptParagraph(DeviceContext deviceContext, int paragraphIndex)
        {
            ScriptParagraph* scriptParagraph;
            if (!scriptParagraphCache.TryGetScriptParagraph(paragraphIndex, out scriptParagraph))
            {
                Paragraph* paragraph = document.GetParagraphZero() + paragraphIndex;
                AnalyzeParagraph(deviceContext, paragraph, scriptParagraph);
            }

            return scriptParagraph;
        }
Пример #6
0
        /// <summary>
        /// Computes the layout of a <see cref="ScriptParagraph"/> and appends one or
        /// more <see cref="ScriptLine"/>s to the <see cref="scriptLineBuffer"/>.
        /// </summary>
        /// <param name="deviceContext">The HDC state.</param>
        /// <param name="paragraphIndex">The paragraph index to layout.</param>
        private void AppendScriptLinesForScriptParagraph(DeviceContext deviceContext, int paragraphIndex)
        {
            ScriptParagraph* scriptParagraph = GetScriptParagraph(deviceContext, paragraphIndex);
            int scriptRunCount = scriptParagraph->ScriptRunCount;

            // Always allocate at least one script line for a paragraph.
            ScriptLine* scriptLine = AddScriptLine(paragraphIndex, 0, currentLayoutHeight);

            // Handle paragraph with no runs (sentinel).
            if (scriptRunCount == 0)
            {
                scriptLine->Height = MinimumScriptLineHeight;
                currentLayoutHeight += MinimumScriptLineHeight;
                return;
            }

            // Apply block-level style information for the paragraph from the first logical run.
            ScriptRun* scriptRuns = scriptParagraph->ScriptRuns;
            Style paragraphStyle = GetParagraphStyleAssumingItHasAtLeastOneRun(scriptParagraph);
            scriptLine->X = paragraphStyle.LeftMargin + paragraphStyle.FirstLineIndent;
            int maxX = currentLayoutWidth - paragraphStyle.RightMargin - paragraphStyle.LeftMargin;

            // Loop over all runs in logical order to determine whether everything fits on one line
            // so we can skip the expensive word wrap calculations.
            {
                bool mustLayoutObjects = false;
                int currentX = scriptLine->X;
                for (int scriptRunIndex = 0; scriptRunIndex < scriptRunCount; scriptRunIndex++)
                {
                    ScriptRun* scriptRun = scriptRuns + scriptRunIndex;

                    int measuredWidth;
                    switch (scriptRun->RunKind)
                    {
                        case RunKind.Text:
                        {
                            measuredWidth = scriptRun->ABC.TotalWidth;
                            break;
                        }

                        case RunKind.Object:
                        {
                            measuredWidth = MeasureObjectScriptRun(scriptRun);
                            mustLayoutObjects = true;
                            break;
                        }

                        case RunKind.Tab:
                        {
                            measuredWidth = MeasureTabScriptRun(scriptRun, paragraphStyle, currentX, true);
                            break;
                        }

                        default:
                            throw new NotSupportedException();
                    }

                    currentX += measuredWidth;
                    if (currentX > maxX && paragraphStyle.WordWrap)
                        goto ComplexLoop;
                }

                // Apply pending changes to the script line now that we're done.
                scriptLine->ScriptRunCount = scriptRunCount;
                FinishScriptLineLayout(scriptLine, scriptParagraph, mustLayoutObjects);
                currentLayoutHeight += scriptLine->Height;
                return;
            }

        // Loop over all runs in logical order and pack as many of them as will fit on each line
        // then wrap the rest.
        ComplexLoop:
            {
                Debug.Assert(scriptParagraph->CharCount != 0,
                    "Script paragraph should not have zero chars because that would have been handled by the simple case.");

                int currentX = scriptLine->X;
                int scriptRunIndex = 0;
                int breakCharIndex = 0;
                int breakScriptRunIndex = 0;
                bool mustLayoutObjects = false;
                while (scriptRunIndex < scriptRunCount)
                {
                    ScriptRun* firstRunOnLine = scriptRuns + scriptLine->ScriptRunIndex;
                    int firstCharIndexOnLine = firstRunOnLine->CharIndexInParagraph + scriptLine->TruncatedLeadingCharsCount;

                    // Sum glyph widths in logical order until an overflow is discovered.
                    {
                        ScriptRun* scriptRun = scriptRuns + scriptRunIndex;

                        switch (scriptRun->RunKind)
                        {
                            case RunKind.Text:
                            {
                                int firstCharIndex = scriptRun->CharIndexInParagraph;
                                int lastCharIndex = firstCharIndex + scriptRun->CharCount;
                                if (scriptRun == firstRunOnLine)
                                    firstCharIndex += scriptLine->TruncatedLeadingCharsCount;

                                bool runRightToLeft = scriptRun->ScriptAnalysis.fRTL;
                                int* glyphAdvanceWidths = scriptRun->GlyphAdvanceWidths(scriptParagraph);
                                int glyphIndex = scriptParagraph->CharLogicalClusters[firstCharIndex];
                                for (int charIndex = firstCharIndex; charIndex < lastCharIndex; charIndex++)
                                {
                                    if (scriptParagraph->CharLogicalAttributes[charIndex].fSoftBreakOrfWhitespace
                                        && (charIndex == firstCharIndexOnLine
                                            || !scriptParagraph->CharLogicalAttributes[charIndex - 1].fWhiteSpace))
                                    {
                                        breakCharIndex = charIndex;
                                        breakScriptRunIndex = scriptRunIndex;
                                    }

                                    int nextCharIndex = charIndex + 1;
                                    if (runRightToLeft)
                                    {
                                        int nextGlyphIndex = nextCharIndex != lastCharIndex
                                            ? scriptParagraph->CharLogicalClusters[nextCharIndex]
                                            : -1;
                                        while (glyphIndex > nextGlyphIndex)
                                            currentX += glyphAdvanceWidths[glyphIndex--];
                                    }
                                    else
                                    {
                                        int nextGlyphIndex = nextCharIndex != lastCharIndex
                                            ? scriptParagraph->CharLogicalClusters[nextCharIndex]
                                            : scriptRun->GlyphCount;
                                        while (glyphIndex < nextGlyphIndex)
                                            currentX += glyphAdvanceWidths[glyphIndex++];
                                    }

                                    if (currentX > maxX && paragraphStyle.WordWrap)
                                        goto BreakLine;
                                }
                                break;
                            }

                            case RunKind.Object:
                            {
                                mustLayoutObjects = true;
                                breakCharIndex = scriptRun->CharIndexInParagraph;
                                breakScriptRunIndex = scriptRunIndex;

                                currentX += MeasureObjectScriptRun(scriptRun);

                                if (currentX > maxX && paragraphStyle.WordWrap)
                                    goto BreakLine;
                                break;
                            }

                            case RunKind.Tab:
                            {
                                breakCharIndex = scriptRun->CharIndexInParagraph;
                                breakScriptRunIndex = scriptRunIndex;

                                currentX += MeasureTabScriptRun(scriptRun, paragraphStyle, currentX, true);

                                if (currentX > maxX && paragraphStyle.WordWrap)
                                    goto BreakLine;
                                break;
                            }
                        }
                    }

                    // No overflow.
                    scriptRunIndex++;
                    continue;

                BreakLine:
                    // Overflow.
                    {
                        // Introduce a hard break at the current script run when we do not find a useful breakpoint.
                        if (breakCharIndex <= firstCharIndexOnLine)
                        {
                            breakScriptRunIndex = scriptRunIndex;
                            breakCharIndex = scriptRuns[breakScriptRunIndex].CharIndexInParagraph;
                            if (breakCharIndex <= firstCharIndexOnLine)
                                breakCharIndex = firstCharIndexOnLine + 1; // ensure at least one char gets shown
                        }

                        // Fix the breakScriptRun pointer if the breakCharIndex is past the end of the run.
                        // Also advance the index beyond all remaining whitespace on the line so it does not
                        // get pushed into the beginning of the next line.
                        ScriptRun* breakScriptRun = scriptRuns + breakScriptRunIndex;
                        for (; ; )
                        {
                            int breakCharOffset = breakCharIndex - breakScriptRun->CharIndexInParagraph;
                            if (breakCharOffset == breakScriptRun->CharCount)
                            {
                                breakScriptRun++;
                                breakScriptRunIndex++;
                            }

                            if (breakCharIndex == scriptParagraph->CharCount)
                                goto Finish; // paragraph is completely finished!

                            // Skip past remaining whitespace if run is in same direction as layout.
                            // This will leave the whitespace hanging off the ragged end of the line.
                            if (breakScriptRun->ScriptAnalysis.fRTL != currentLayoutRightToLeft
                                || !scriptParagraph->CharLogicalAttributes[breakCharIndex].fWhiteSpace)
                                break;

                            breakCharIndex += 1;
                        }

                        // Finalize the script line properties.
                        {
                            int breakCharOffset = breakCharIndex - breakScriptRun->CharIndexInParagraph;
                            if (breakCharOffset == 0)
                            {
                                scriptLine->ScriptRunCount = breakScriptRunIndex - scriptLine->ScriptRunIndex;
                            }
                            else
                            {
                                scriptLine->ScriptRunCount = breakScriptRunIndex - scriptLine->ScriptRunIndex + 1;
                                scriptLine->TruncatedTrailingCharsCount = breakScriptRun->CharCount - breakCharOffset;
                            }
                            FinishScriptLineLayout(scriptLine, scriptParagraph, mustLayoutObjects);
                            mustLayoutObjects = false;
                            currentLayoutHeight += scriptLine->Height;

                            // Start a new script line.
                            scriptRunIndex = breakScriptRunIndex;
                            currentX = paragraphStyle.LeftMargin;

                            scriptLine = AddScriptLine(paragraphIndex, scriptRunIndex, currentLayoutHeight);
                            scriptLine->X = currentX;
                            scriptLine->TruncatedLeadingCharsCount = breakCharOffset;
                        }
                    }
                }

            Finish:
                // Finish the last script line.
                scriptLine->ScriptRunCount = scriptRunCount - scriptLine->ScriptRunIndex;
                FinishScriptLineLayout(scriptLine, scriptParagraph, mustLayoutObjects);
                currentLayoutHeight += scriptLine->Height;
            }
        }