<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[182236] trunk/Source/WebCore</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/182236">182236</a></dd>
<dt>Author</dt> <dd>mmaxfield@apple.com</dd>
<dt>Date</dt> <dd>2015-04-01 09:58:20 -0700 (Wed, 01 Apr 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Support forcing expansion opportunities at the beginning and ending of a run
https://bugs.webkit.org/show_bug.cgi?id=143292

Reviewed by David Hyatt.

Migrate ExpansionBehavior to an enum comprised of two values: one for leading behavior
and one for trailing behavior. Each of these two values can take on a value of
&quot;force,&quot; &quot;forbid,&quot; and neither. All calls that interact with justification are
migrated to use this new structure.

Note that in this terminology, &quot;leading&quot; and &quot;trailing&quot; are with respect to the order
that elements get laid out in a line. Therefore, leading is always on the left, since
lines get laid out that way regardless of their bidi attributes.

This is getting ready for https://bugs.webkit.org/show_bug.cgi?id=142608

No new tests because there is no behavior change.

* platform/graphics/FontCascade.cpp:
(WebCore::FontCascade::expansionOpportunityCountInternal): Migrate to these new
values.
(WebCore::FontCascade::expansionOpportunityCount): Ditto.
(WebCore::FontCascade::leadingExpansionOpportunity): Returns whether one is present
or not.
(WebCore::FontCascade::trailingExpansionOpportunity): Ditto.
* platform/graphics/FontCascade.h:
* platform/graphics/GlyphBuffer.h: New leading expansion field.
(WebCore::GlyphBuffer::setLeadingExpansion):
(WebCore::GlyphBuffer::leadingExpansion):
* platform/graphics/TextRun.h: m_expansionBehavior needs more bits.
(WebCore::TextRun::expansionBehavior):
(WebCore::TextRun::allowsLeadingExpansion): Deleted.
(WebCore::TextRun::allowsTrailingExpansion): Deleted.
* platform/graphics/WidthIterator.cpp: Update to support new type.
(WebCore::WidthIterator::WidthIterator):
(WebCore::expansionLocation): Where should we insert expansions?
(WebCore::WidthIterator::advanceInternal): Use expansionLocation()
* platform/graphics/cocoa/FontCascadeCocoa.mm:
(WebCore::FontCascade::adjustSelectionRectForComplexText):
(WebCore::FontCascade::getGlyphsAndAdvancesForComplexText):
* platform/graphics/mac/ComplexTextController.cpp: Same as WidthIterator
(WebCore::ComplexTextController::ComplexTextController):
(WebCore::ComplexTextController::advance):
(WebCore::expansionLocation):
(WebCore::ComplexTextController::adjustGlyphsAndAdvances):
* platform/graphics/mac/ComplexTextController.h:
(WebCore::ComplexTextController::leadingExpansion):
* platform/text/TextFlags.h: Add new enum values
* rendering/InlineBox.h: Update to include new values.
(WebCore::InlineBox::InlineBoxBitfields::InlineBoxBitfields):
(WebCore::InlineBox::canHaveTrailingExpansion):
(WebCore::InlineBox::setCanHaveTrailingExpansion):
(WebCore::InlineBox::setForceTrailingExpansion):
(WebCore::InlineBox::forceTrailingExpansion):
(WebCore::InlineBox::setForceLeadingExpansion):
(WebCore::InlineBox::forceLeadingExpansion):
* rendering/InlineTextBox.h:
* rendering/RenderBlockLineLayout.cpp: Update to use new FontCascade signatures.
(WebCore::expansionBehaviorForInlineTextBox):
(WebCore::applyExpansionBehavior):
(WebCore::RenderBlockFlow::computeInlineDirectionPositionsForSegment):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsFontCascadecpp">trunk/Source/WebCore/platform/graphics/FontCascade.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsFontCascadeh">trunk/Source/WebCore/platform/graphics/FontCascade.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsGlyphBufferh">trunk/Source/WebCore/platform/graphics/GlyphBuffer.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsTextRunh">trunk/Source/WebCore/platform/graphics/TextRun.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsWidthIteratorcpp">trunk/Source/WebCore/platform/graphics/WidthIterator.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicscocoaFontCascadeCocoamm">trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsmacComplexTextControllercpp">trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsmacComplexTextControllerh">trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.h</a></li>
<li><a href="#trunkSourceWebCoreplatformtextTextFlagsh">trunk/Source/WebCore/platform/text/TextFlags.h</a></li>
<li><a href="#trunkSourceWebCorerenderingInlineBoxh">trunk/Source/WebCore/rendering/InlineBox.h</a></li>
<li><a href="#trunkSourceWebCorerenderingInlineTextBoxh">trunk/Source/WebCore/rendering/InlineTextBox.h</a></li>
<li><a href="#trunkSourceWebCorerenderingRenderBlockLineLayoutcpp">trunk/Source/WebCore/rendering/RenderBlockLineLayout.cpp</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/ChangeLog        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -1,3 +1,67 @@
</span><ins>+2015-04-01  Myles C. Maxfield  &lt;mmaxfield@apple.com&gt;
+
+        Support forcing expansion opportunities at the beginning and ending of a run
+        https://bugs.webkit.org/show_bug.cgi?id=143292
+
+        Reviewed by David Hyatt.
+
+        Migrate ExpansionBehavior to an enum comprised of two values: one for leading behavior
+        and one for trailing behavior. Each of these two values can take on a value of
+        &quot;force,&quot; &quot;forbid,&quot; and neither. All calls that interact with justification are
+        migrated to use this new structure.
+
+        Note that in this terminology, &quot;leading&quot; and &quot;trailing&quot; are with respect to the order
+        that elements get laid out in a line. Therefore, leading is always on the left, since
+        lines get laid out that way regardless of their bidi attributes.
+
+        This is getting ready for https://bugs.webkit.org/show_bug.cgi?id=142608
+
+        No new tests because there is no behavior change.
+
+        * platform/graphics/FontCascade.cpp:
+        (WebCore::FontCascade::expansionOpportunityCountInternal): Migrate to these new
+        values.
+        (WebCore::FontCascade::expansionOpportunityCount): Ditto.
+        (WebCore::FontCascade::leadingExpansionOpportunity): Returns whether one is present
+        or not.
+        (WebCore::FontCascade::trailingExpansionOpportunity): Ditto.
+        * platform/graphics/FontCascade.h:
+        * platform/graphics/GlyphBuffer.h: New leading expansion field.
+        (WebCore::GlyphBuffer::setLeadingExpansion):
+        (WebCore::GlyphBuffer::leadingExpansion):
+        * platform/graphics/TextRun.h: m_expansionBehavior needs more bits.
+        (WebCore::TextRun::expansionBehavior):
+        (WebCore::TextRun::allowsLeadingExpansion): Deleted.
+        (WebCore::TextRun::allowsTrailingExpansion): Deleted.
+        * platform/graphics/WidthIterator.cpp: Update to support new type.
+        (WebCore::WidthIterator::WidthIterator):
+        (WebCore::expansionLocation): Where should we insert expansions?
+        (WebCore::WidthIterator::advanceInternal): Use expansionLocation()
+        * platform/graphics/cocoa/FontCascadeCocoa.mm:
+        (WebCore::FontCascade::adjustSelectionRectForComplexText):
+        (WebCore::FontCascade::getGlyphsAndAdvancesForComplexText):
+        * platform/graphics/mac/ComplexTextController.cpp: Same as WidthIterator
+        (WebCore::ComplexTextController::ComplexTextController):
+        (WebCore::ComplexTextController::advance):
+        (WebCore::expansionLocation):
+        (WebCore::ComplexTextController::adjustGlyphsAndAdvances):
+        * platform/graphics/mac/ComplexTextController.h:
+        (WebCore::ComplexTextController::leadingExpansion):
+        * platform/text/TextFlags.h: Add new enum values
+        * rendering/InlineBox.h: Update to include new values.
+        (WebCore::InlineBox::InlineBoxBitfields::InlineBoxBitfields):
+        (WebCore::InlineBox::canHaveTrailingExpansion):
+        (WebCore::InlineBox::setCanHaveTrailingExpansion):
+        (WebCore::InlineBox::setForceTrailingExpansion):
+        (WebCore::InlineBox::forceTrailingExpansion):
+        (WebCore::InlineBox::setForceLeadingExpansion):
+        (WebCore::InlineBox::forceLeadingExpansion):
+        * rendering/InlineTextBox.h:
+        * rendering/RenderBlockLineLayout.cpp: Update to use new FontCascade signatures.
+        (WebCore::expansionBehaviorForInlineTextBox):
+        (WebCore::applyExpansionBehavior):
+        (WebCore::RenderBlockFlow::computeInlineDirectionPositionsForSegment):
+
</ins><span class="cx"> 2015-04-01  Zalan Bujtas  &lt;zalan@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Lots of time spent querying table cell borders, when there are none.
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsFontCascadecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/FontCascade.cpp (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/FontCascade.cpp        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/FontCascade.cpp        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -1005,9 +1005,14 @@
</span><span class="cx">     return isCJKIdeograph(c);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-unsigned FontCascade::expansionOpportunityCountInternal(const LChar* characters, size_t length, TextDirection direction, bool&amp; isAfterExpansion)
</del><ins>+std::pair&lt;unsigned, bool&gt; FontCascade::expansionOpportunityCountInternal(const LChar* characters, size_t length, TextDirection direction, ExpansionBehavior expansionBehavior)
</ins><span class="cx"> {
</span><span class="cx">     unsigned count = 0;
</span><ins>+    bool isAfterExpansion = (expansionBehavior &amp; LeadingExpansionMask) == ForbidLeadingExpansion;
+    if ((expansionBehavior &amp; LeadingExpansionMask) == ForceLeadingExpansion) {
+        ++count;
+        isAfterExpansion = true;
+    }
</ins><span class="cx">     if (direction == LTR) {
</span><span class="cx">         for (size_t i = 0; i &lt; length; ++i) {
</span><span class="cx">             if (treatAsSpace(characters[i])) {
</span><span class="lines">@@ -1025,13 +1030,25 @@
</span><span class="cx">                 isAfterExpansion = false;
</span><span class="cx">         }
</span><span class="cx">     }
</span><del>-    return count;
</del><ins>+    if (!isAfterExpansion &amp;&amp; (expansionBehavior &amp; TrailingExpansionMask) == ForceTrailingExpansion) {
+        ++count;
+        isAfterExpansion = true;
+    } else if (isAfterExpansion &amp;&amp; (expansionBehavior &amp; TrailingExpansionMask) == ForbidTrailingExpansion) {
+        --count;
+        isAfterExpansion = false;
+    }
+    return std::make_pair(count, isAfterExpansion);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-unsigned FontCascade::expansionOpportunityCountInternal(const UChar* characters, size_t length, TextDirection direction, bool&amp; isAfterExpansion)
</del><ins>+std::pair&lt;unsigned, bool&gt; FontCascade::expansionOpportunityCountInternal(const UChar* characters, size_t length, TextDirection direction, ExpansionBehavior expansionBehavior)
</ins><span class="cx"> {
</span><span class="cx">     static bool expandAroundIdeographs = canExpandAroundIdeographsInComplexText();
</span><span class="cx">     unsigned count = 0;
</span><ins>+    bool isAfterExpansion = (expansionBehavior &amp; LeadingExpansionMask) == ForbidLeadingExpansion;
+    if ((expansionBehavior &amp; LeadingExpansionMask) == ForceLeadingExpansion) {
+        ++count;
+        isAfterExpansion = true;
+    }
</ins><span class="cx">     if (direction == LTR) {
</span><span class="cx">         for (size_t i = 0; i &lt; length; ++i) {
</span><span class="cx">             UChar32 character = characters[i];
</span><span class="lines">@@ -1075,16 +1092,65 @@
</span><span class="cx">             isAfterExpansion = false;
</span><span class="cx">         }
</span><span class="cx">     }
</span><del>-    return count;
</del><ins>+    if (!isAfterExpansion &amp;&amp; (expansionBehavior &amp; TrailingExpansionMask) == ForceTrailingExpansion) {
+        ++count;
+        isAfterExpansion = true;
+    } else if (isAfterExpansion &amp;&amp; (expansionBehavior &amp; TrailingExpansionMask) == ForbidTrailingExpansion) {
+        --count;
+        isAfterExpansion = false;
+    }
+    return std::make_pair(count, isAfterExpansion);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-unsigned FontCascade::expansionOpportunityCount(const StringView&amp; stringView, TextDirection direction, bool&amp; isAfterExpansion)
</del><ins>+std::pair&lt;unsigned, bool&gt; FontCascade::expansionOpportunityCount(const StringView&amp; stringView, TextDirection direction, ExpansionBehavior expansionBehavior)
</ins><span class="cx"> {
</span><ins>+    // For each character, iterating from left to right:
+    //   If it is recognized as a space, insert an opportunity after it
+    //   If it is an ideograph, insert one opportunity before it and one opportunity after it
+    // Do this such a way so that there are not two opportunities next to each other.
</ins><span class="cx">     if (stringView.is8Bit())
</span><del>-        return expansionOpportunityCountInternal(stringView.characters8(), stringView.length(), direction, isAfterExpansion);
-    return expansionOpportunityCountInternal(stringView.characters16(), stringView.length(), direction, isAfterExpansion);
</del><ins>+        return expansionOpportunityCountInternal(stringView.characters8(), stringView.length(), direction, expansionBehavior);
+    return expansionOpportunityCountInternal(stringView.characters16(), stringView.length(), direction, expansionBehavior);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+bool FontCascade::leadingExpansionOpportunity(const StringView&amp; stringView, TextDirection direction)
+{
+    if (!stringView.length())
+        return false;
+
+    UChar32 initialCharacter;
+    if (direction == LTR) {
+        initialCharacter = stringView[0];
+        if (!stringView.is8Bit() &amp;&amp; U16_IS_LEAD(initialCharacter) &amp;&amp; stringView.length() &gt; 1 &amp;&amp; U16_IS_TRAIL(stringView[1]))
+            initialCharacter = U16_GET_SUPPLEMENTARY(initialCharacter, stringView[1]);
+    } else {
+        initialCharacter = stringView[stringView.length() - 1];
+        if (!stringView.is8Bit() &amp;&amp; U16_IS_TRAIL(initialCharacter) &amp;&amp; stringView.length() &gt; 1 &amp;&amp; U16_IS_LEAD(stringView[stringView.length() - 2]))
+            initialCharacter = U16_GET_SUPPLEMENTARY(stringView[stringView.length() - 2], initialCharacter);
+    }
+
+    return canExpandAroundIdeographsInComplexText() &amp;&amp; isCJKIdeographOrSymbol(initialCharacter);
+}
+
+bool FontCascade::trailingExpansionOpportunity(const StringView&amp; stringView, TextDirection direction)
+{
+    if (!stringView.length())
+        return false;
+
+    UChar32 finalCharacter;
+    if (direction == LTR) {
+        finalCharacter = stringView[stringView.length() - 1];
+        if (!stringView.is8Bit() &amp;&amp; U16_IS_TRAIL(finalCharacter) &amp;&amp; stringView.length() &gt; 1 &amp;&amp; U16_IS_LEAD(stringView[stringView.length() - 2]))
+            finalCharacter = U16_GET_SUPPLEMENTARY(stringView[stringView.length() - 2], finalCharacter);
+    } else {
+        finalCharacter = stringView[0];
+        if (!stringView.is8Bit() &amp;&amp; U16_IS_LEAD(finalCharacter) &amp;&amp; stringView.length() &gt; 1 &amp;&amp; U16_IS_TRAIL(stringView[1]))
+            finalCharacter = U16_GET_SUPPLEMENTARY(finalCharacter, stringView[1]);
+    }
+
+    return treatAsSpace(finalCharacter) || (canExpandAroundIdeographsInComplexText() &amp;&amp; isCJKIdeographOrSymbol(finalCharacter));
+}
+
</ins><span class="cx"> bool FontCascade::canReceiveTextEmphasis(UChar32 c)
</span><span class="cx"> {
</span><span class="cx">     if (U_GET_GC_MASK(c) &amp; (U_GC_Z_MASK | U_GC_CN_MASK | U_GC_CC_MASK | U_GC_CF_MASK))
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsFontCascadeh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/FontCascade.h (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/FontCascade.h        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/FontCascade.h        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -194,11 +194,17 @@
</span><span class="cx">     static bool isCJKIdeograph(UChar32);
</span><span class="cx">     static bool isCJKIdeographOrSymbol(UChar32);
</span><span class="cx"> 
</span><del>-    // BEWARE: If isAfterExpansion is true after this function call, then the returned value includes a trailing opportunity
-    // which may or may not actually be present. RenderBlockFlow::computeInlineDirectionPositionsForSegment() compensates
-    // for this by decrementing the returned value if isAfterExpansion is true at the end of a line.
-    static unsigned expansionOpportunityCount(const StringView&amp;, TextDirection, bool&amp; isAfterExpansion);
</del><ins>+    // Returns (the number of opportunities, whether the last expansion is a trailing expansion)
+    // If there are no opportunities, the bool will be true iff we are forbidding leading expansions.
+    static std::pair&lt;unsigned, bool&gt; expansionOpportunityCount(const StringView&amp;, TextDirection, ExpansionBehavior);
</ins><span class="cx"> 
</span><ins>+    // Whether or not there is an expansion opportunity just before the first character
+    // Note that this does not take a isAfterExpansion flag; this assumes that isAfterExpansion is false
+    // Here, &quot;Leading&quot; and &quot;Trailing&quot; are relevant after the line has been rearranged for bidi.
+    // (&quot;Leading&quot; means &quot;left&quot; and &quot;Trailing&quot; means &quot;right.&quot;)
+    static bool leadingExpansionOpportunity(const StringView&amp;, TextDirection);
+    static bool trailingExpansionOpportunity(const StringView&amp;, TextDirection);
+
</ins><span class="cx">     WEBCORE_EXPORT static void setShouldUseSmoothing(bool);
</span><span class="cx">     WEBCORE_EXPORT static bool shouldUseSmoothing();
</span><span class="cx"> 
</span><span class="lines">@@ -237,8 +243,8 @@
</span><span class="cx">     int offsetForPositionForComplexText(const TextRun&amp;, float position, bool includePartialGlyphs) const;
</span><span class="cx">     void adjustSelectionRectForComplexText(const TextRun&amp;, LayoutRect&amp; selectionRect, int from, int to) const;
</span><span class="cx"> 
</span><del>-    static unsigned expansionOpportunityCountInternal(const LChar*, size_t length, TextDirection, bool&amp; isAfterExpansion);
-    static unsigned expansionOpportunityCountInternal(const UChar*, size_t length, TextDirection, bool&amp; isAfterExpansion);
</del><ins>+    static std::pair&lt;unsigned, bool&gt; expansionOpportunityCountInternal(const LChar*, size_t length, TextDirection, ExpansionBehavior);
+    static std::pair&lt;unsigned, bool&gt; expansionOpportunityCountInternal(const UChar*, size_t length, TextDirection, ExpansionBehavior);
</ins><span class="cx"> 
</span><span class="cx">     friend struct WidthIterator;
</span><span class="cx">     friend class SVGTextRunRenderingContext;
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsGlyphBufferh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/GlyphBuffer.h (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/GlyphBuffer.h        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/GlyphBuffer.h        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -100,6 +100,9 @@
</span><span class="cx"> 
</span><span class="cx">     void setInitialAdvance(GlyphBufferAdvance initialAdvance) { m_initialAdvance = initialAdvance; }
</span><span class="cx">     const GlyphBufferAdvance&amp; initialAdvance() const { return m_initialAdvance; }
</span><ins>+
+    void setLeadingExpansion(float leadingExpansion) { m_leadingExpansion = leadingExpansion; }
+    float leadingExpansion() const { return m_leadingExpansion; }
</ins><span class="cx">     
</span><span class="cx">     Glyph glyphAt(int index) const
</span><span class="cx">     {
</span><span class="lines">@@ -244,6 +247,7 @@
</span><span class="cx"> #if PLATFORM(WIN)
</span><span class="cx">     Vector&lt;FloatSize, 2048&gt; m_offsets;
</span><span class="cx"> #endif
</span><ins>+    float m_leadingExpansion;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsTextRunh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/TextRun.h (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/TextRun.h        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/TextRun.h        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -165,8 +165,7 @@
</span><span class="cx">     float xPos() const { return m_xpos; }
</span><span class="cx">     void setXPos(float xPos) { m_xpos = xPos; }
</span><span class="cx">     float expansion() const { return m_expansion; }
</span><del>-    bool allowsLeadingExpansion() const { return m_expansionBehavior &amp; AllowLeadingExpansion; }
-    bool allowsTrailingExpansion() const { return m_expansionBehavior &amp; AllowTrailingExpansion; }
</del><ins>+    ExpansionBehavior expansionBehavior() const { return m_expansionBehavior; }
</ins><span class="cx">     TextDirection direction() const { return static_cast&lt;TextDirection&gt;(m_direction); }
</span><span class="cx">     bool rtl() const { return m_direction == RTL; }
</span><span class="cx">     bool ltr() const { return m_direction == LTR; }
</span><span class="lines">@@ -219,7 +218,7 @@
</span><span class="cx">     float m_horizontalGlyphStretch;
</span><span class="cx"> 
</span><span class="cx">     float m_expansion;
</span><del>-    ExpansionBehavior m_expansionBehavior : 2;
</del><ins>+    unsigned m_expansionBehavior : 4;
</ins><span class="cx">     unsigned m_allowTabs : 1;
</span><span class="cx">     unsigned m_direction : 1;
</span><span class="cx">     unsigned m_directionalOverride : 1; // Was this direction set by an override character.
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsWidthIteratorcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/WidthIterator.cpp (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/WidthIterator.cpp        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/WidthIterator.cpp        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -39,7 +39,7 @@
</span><span class="cx">     , m_run(run)
</span><span class="cx">     , m_currentCharacter(0)
</span><span class="cx">     , m_runWidthSoFar(0)
</span><del>-    , m_isAfterExpansion(!run.allowsLeadingExpansion())
</del><ins>+    , m_isAfterExpansion((run.expansionBehavior() &amp; LeadingExpansionMask) == ForbidLeadingExpansion)
</ins><span class="cx">     , m_finalRoundingWidth(0)
</span><span class="cx">     , m_typesettingFeatures(font-&gt;typesettingFeatures())
</span><span class="cx">     , m_fallbackFonts(fallbackFonts)
</span><span class="lines">@@ -56,10 +56,7 @@
</span><span class="cx">     if (!m_expansion)
</span><span class="cx">         m_expansionPerOpportunity = 0;
</span><span class="cx">     else {
</span><del>-        bool isAfterExpansion = m_isAfterExpansion;
-        unsigned expansionOpportunityCount = FontCascade::expansionOpportunityCount(m_run.text(), m_run.ltr() ? LTR : RTL, isAfterExpansion);
-        if (isAfterExpansion &amp;&amp; !m_run.allowsTrailingExpansion())
-            expansionOpportunityCount--;
</del><ins>+        unsigned expansionOpportunityCount = FontCascade::expansionOpportunityCount(m_run.text(), m_run.ltr() ? LTR : RTL, run.expansionBehavior()).first;
</ins><span class="cx"> 
</span><span class="cx">         if (!expansionOpportunityCount)
</span><span class="cx">             m_expansionPerOpportunity = 0;
</span><span class="lines">@@ -168,13 +165,47 @@
</span><span class="cx">     return widthDifference;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static inline std::pair&lt;bool, bool&gt; expansionLocation(bool ideograph, bool treatAsSpace, bool ltr, bool isAfterExpansion, bool forbidLeadingExpansion, bool forbidTrailingExpansion, bool forceLeadingExpansion, bool forceTrailingExpansion)
+{
+    bool expandLeft = ideograph;
+    bool expandRight = ideograph;
+    if (treatAsSpace) {
+        if (ltr)
+            expandRight = true;
+        else
+            expandLeft = true;
+    }
+    if (isAfterExpansion) {
+        if (ltr)
+            expandLeft = false;
+        else
+            expandRight = false;
+    }
+    ASSERT(!forbidLeadingExpansion || !forceLeadingExpansion);
+    ASSERT(!forbidTrailingExpansion || !forceTrailingExpansion);
+    if (forbidLeadingExpansion)
+        expandLeft = false;
+    if (forbidTrailingExpansion)
+        expandRight = false;
+    if (forceLeadingExpansion)
+        expandLeft = true;
+    if (forceTrailingExpansion)
+        expandRight = true;
+    return std::make_pair(expandLeft, expandRight);
+}
+
</ins><span class="cx"> template &lt;typename TextIterator&gt;
</span><span class="cx"> inline unsigned WidthIterator::advanceInternal(TextIterator&amp; textIterator, GlyphBuffer* glyphBuffer)
</span><span class="cx"> {
</span><span class="cx">     bool rtl = m_run.rtl();
</span><span class="cx">     bool hasExtraSpacing = (m_font-&gt;letterSpacing() || m_font-&gt;wordSpacing() || m_expansion) &amp;&amp; !m_run.spacingDisabled();
</span><span class="cx"> 
</span><ins>+    bool runForcesLeadingExpansion = (m_run.expansionBehavior() &amp; LeadingExpansionMask) == ForceLeadingExpansion;
+    bool runForcesTrailingExpansion = (m_run.expansionBehavior() &amp; TrailingExpansionMask) == ForceTrailingExpansion;
+    bool runForbidsLeadingExpansion = (m_run.expansionBehavior() &amp; LeadingExpansionMask) == ForbidLeadingExpansion;
+    bool runForbidsTrailingExpansion = (m_run.expansionBehavior() &amp; TrailingExpansionMask) == ForbidTrailingExpansion;
</ins><span class="cx">     float widthSinceLastRounding = m_runWidthSoFar;
</span><ins>+    float leftoverJustificationWidth = 0;
</ins><span class="cx">     m_runWidthSoFar = floorf(m_runWidthSoFar);
</span><span class="cx">     widthSinceLastRounding -= m_runWidthSoFar;
</span><span class="cx"> 
</span><span class="lines">@@ -189,6 +220,7 @@
</span><span class="cx">     unsigned clusterLength = 0;
</span><span class="cx">     CharactersTreatedAsSpace charactersTreatedAsSpace;
</span><span class="cx">     String normalizedSpacesStringCache;
</span><ins>+    // We are iterating in string order, not glyph order. Compare this to ComplexTextController::adjustGlyphsAndAdvances()
</ins><span class="cx">     while (textIterator.consume(character, clusterLength)) {
</span><span class="cx">         unsigned advanceLength = clusterLength;
</span><span class="cx">         int currentCharacter = textIterator.currentCharacter();
</span><span class="lines">@@ -242,36 +274,63 @@
</span><span class="cx"> 
</span><span class="cx">         if (hasExtraSpacing) {
</span><span class="cx">             // Account for letter-spacing.
</span><del>-            if (width &amp;&amp; m_font-&gt;letterSpacing())
</del><ins>+            if (width) {
</ins><span class="cx">                 width += m_font-&gt;letterSpacing();
</span><ins>+                width += leftoverJustificationWidth;
+                leftoverJustificationWidth = 0;
+            }
</ins><span class="cx"> 
</span><span class="cx">             static bool expandAroundIdeographs = FontCascade::canExpandAroundIdeographsInComplexText();
</span><span class="cx">             bool treatAsSpace = FontCascade::treatAsSpace(character);
</span><del>-            if (treatAsSpace || (expandAroundIdeographs &amp;&amp; FontCascade::isCJKIdeographOrSymbol(character))) {
</del><ins>+            bool currentIsLastCharacter = currentCharacter + advanceLength == static_cast&lt;size_t&gt;(m_run.length());
+            bool forceLeadingExpansion = false; // On the left, regardless of m_run.ltr()
+            bool forceTrailingExpansion = false; // On the right, regardless of m_run.ltr()
+            bool forbidLeadingExpansion = false;
+            bool forbidTrailingExpansion = false;
+            if (runForcesLeadingExpansion)
+                forceLeadingExpansion = m_run.ltr() ? !currentCharacter : currentIsLastCharacter;
+            if (runForcesTrailingExpansion)
+                forceTrailingExpansion = m_run.ltr() ? currentIsLastCharacter : !currentCharacter;
+            if (runForbidsLeadingExpansion)
+                forbidLeadingExpansion = m_run.ltr() ? !currentCharacter : currentIsLastCharacter;
+            if (runForbidsTrailingExpansion)
+                forbidTrailingExpansion = m_run.ltr() ? currentIsLastCharacter : !currentCharacter;
+            bool ideograph = (expandAroundIdeographs &amp;&amp; FontCascade::isCJKIdeographOrSymbol(character));
+            if (treatAsSpace || ideograph || forceLeadingExpansion || forceTrailingExpansion) {
</ins><span class="cx">                 // Distribute the run's total expansion evenly over all expansion opportunities in the run.
</span><span class="cx">                 if (m_expansion) {
</span><ins>+                    bool expandLeft, expandRight;
+                    std::tie(expandLeft, expandRight) = expansionLocation(ideograph, treatAsSpace, m_run.ltr(), m_isAfterExpansion, forbidLeadingExpansion, forbidTrailingExpansion, forceLeadingExpansion, forceTrailingExpansion);
</ins><span class="cx">                     float previousExpansion = m_expansion;
</span><del>-                    if (!treatAsSpace &amp;&amp; !m_isAfterExpansion) {
-                        // Take the expansion opportunity before this ideograph.
-                        m_expansion -= m_expansionPerOpportunity;
-                        float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
-                        m_runWidthSoFar += expansionAtThisOpportunity;
-                        if (glyphBuffer) {
-                            if (glyphBuffer-&gt;isEmpty()) {
-                                if (m_forTextEmphasis)
-                                    glyphBuffer-&gt;add(font-&gt;zeroWidthSpaceGlyph(), font, m_expansionPerOpportunity, currentCharacter);
-                                else
-                                    glyphBuffer-&gt;add(font-&gt;spaceGlyph(), font, expansionAtThisOpportunity, currentCharacter);
-                            } else
-                                glyphBuffer-&gt;expandLastAdvance(expansionAtThisOpportunity);
</del><ins>+                    if (expandLeft) {
+                        if (m_run.ltr()) {
+                            // Increase previous width
+                            m_expansion -= m_expansionPerOpportunity;
+                            float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
+                            m_runWidthSoFar += expansionAtThisOpportunity;
+                            if (glyphBuffer) {
+                                if (glyphBuffer-&gt;isEmpty()) {
+                                    if (m_forTextEmphasis)
+                                        glyphBuffer-&gt;add(font-&gt;zeroWidthSpaceGlyph(), font, m_expansionPerOpportunity, currentCharacter);
+                                    else
+                                        glyphBuffer-&gt;add(font-&gt;spaceGlyph(), font, expansionAtThisOpportunity, currentCharacter);
+                                } else
+                                    glyphBuffer-&gt;expandLastAdvance(expansionAtThisOpportunity);
+                            }
+                        } else {
+                            // Increase next width
+                            float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion - m_expansionPerOpportunity);
+                            leftoverJustificationWidth += expansionAtThisOpportunity;
+                            m_isAfterExpansion = true;
</ins><span class="cx">                         }
</span><span class="cx">                         previousExpansion = m_expansion;
</span><span class="cx">                     }
</span><del>-                    if (m_run.allowsTrailingExpansion() || (m_run.ltr() &amp;&amp; currentCharacter + advanceLength &lt; static_cast&lt;size_t&gt;(m_run.length()))
-                        || (m_run.rtl() &amp;&amp; currentCharacter)) {
</del><ins>+                    if (expandRight) {
</ins><span class="cx">                         m_expansion -= m_expansionPerOpportunity;
</span><del>-                        width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
-                        m_isAfterExpansion = true;
</del><ins>+                        float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
+                        width += expansionAtThisOpportunity;
+                        if (m_run.ltr())
+                            m_isAfterExpansion = true;
</ins><span class="cx">                     }
</span><span class="cx">                 } else
</span><span class="cx">                     m_isAfterExpansion = false;
</span><span class="lines">@@ -341,6 +400,13 @@
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    if (leftoverJustificationWidth) {
+        if (m_forTextEmphasis)
+            glyphBuffer-&gt;add(lastFontData-&gt;zeroWidthSpaceGlyph(), lastFontData, leftoverJustificationWidth, m_run.length());
+        else
+            glyphBuffer-&gt;add(lastFontData-&gt;spaceGlyph(), lastFontData, leftoverJustificationWidth, m_run.length());
+    }
+
</ins><span class="cx">     if (shouldApplyFontTransforms()) {
</span><span class="cx">         m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, *this, m_typesettingFeatures, charactersTreatedAsSpace);
</span><span class="cx">         if (glyphBuffer)
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicscocoaFontCascadeCocoamm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/cocoa/FontCascadeCocoa.mm        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -602,7 +602,7 @@
</span><span class="cx">     float afterWidth = controller.runWidthSoFar();
</span><span class="cx"> 
</span><span class="cx">     if (run.rtl())
</span><del>-        selectionRect.move(controller.totalWidth() - afterWidth, 0);
</del><ins>+        selectionRect.move(controller.totalWidth() - afterWidth + controller.leadingExpansion(), 0);
</ins><span class="cx">     else
</span><span class="cx">         selectionRect.move(beforeWidth, 0);
</span><span class="cx">     selectionRect.setWidth(afterWidth - beforeWidth);
</span><span class="lines">@@ -623,7 +623,7 @@
</span><span class="cx">     float afterWidth = controller.runWidthSoFar();
</span><span class="cx"> 
</span><span class="cx">     if (run.rtl()) {
</span><del>-        initialAdvance = controller.totalWidth() + controller.finalRoundingWidth() - afterWidth;
</del><ins>+        initialAdvance = controller.totalWidth() + controller.finalRoundingWidth() - afterWidth + controller.leadingExpansion();
</ins><span class="cx">         glyphBuffer.reverse(0, glyphBuffer.size());
</span><span class="cx">     } else
</span><span class="cx">         initialAdvance = beforeWidth;
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsmacComplexTextControllercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.cpp (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.cpp        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.cpp        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -133,7 +133,6 @@
</span><span class="cx">     , m_finalRoundingWidth(0)
</span><span class="cx">     , m_expansion(run.expansion())
</span><span class="cx">     , m_leadingExpansion(0)
</span><del>-    , m_afterExpansion(!run.allowsLeadingExpansion())
</del><span class="cx">     , m_fallbackFonts(fallbackFonts)
</span><span class="cx">     , m_minGlyphBoundingBoxX(std::numeric_limits&lt;float&gt;::max())
</span><span class="cx">     , m_maxGlyphBoundingBoxX(std::numeric_limits&lt;float&gt;::min())
</span><span class="lines">@@ -144,10 +143,7 @@
</span><span class="cx">     if (!m_expansion)
</span><span class="cx">         m_expansionPerOpportunity = 0;
</span><span class="cx">     else {
</span><del>-        bool isAfterExpansion = m_afterExpansion;
-        unsigned expansionOpportunityCount = FontCascade::expansionOpportunityCount(m_run.text(), m_run.ltr() ? LTR : RTL, isAfterExpansion);
-        if (isAfterExpansion &amp;&amp; !m_run.allowsTrailingExpansion())
-            expansionOpportunityCount--;
</del><ins>+        unsigned expansionOpportunityCount = FontCascade::expansionOpportunityCount(m_run.text(), m_run.ltr() ? LTR : RTL, run.expansionBehavior()).first;
</ins><span class="cx"> 
</span><span class="cx">         if (!expansionOpportunityCount)
</span><span class="cx">             m_expansionPerOpportunity = 0;
</span><span class="lines">@@ -519,8 +515,10 @@
</span><span class="cx">         // We must store the initial advance for the first glyph we are going to draw.
</span><span class="cx">         // When leftmostGlyph is 0, it represents the first glyph to draw, taking into
</span><span class="cx">         // account the text direction.
</span><del>-        if (glyphBuffer &amp;&amp; !leftmostGlyph)
</del><ins>+        if (glyphBuffer &amp;&amp; !leftmostGlyph) {
</ins><span class="cx">             glyphBuffer-&gt;setInitialAdvance(complexTextRun.initialAdvance());
</span><ins>+            glyphBuffer-&gt;setLeadingExpansion(m_leadingExpansion);
+        }
</ins><span class="cx"> 
</span><span class="cx">         while (m_glyphInCurrentRun &lt; glyphCount) {
</span><span class="cx">             unsigned glyphStartOffset = complexTextRun.indexAt(g);
</span><span class="lines">@@ -577,11 +575,42 @@
</span><span class="cx">         m_runWidthSoFar += m_finalRoundingWidth;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static inline std::pair&lt;bool, bool&gt; expansionLocation(bool ideograph, bool treatAsSpace, bool ltr, bool isAfterExpansion, bool forbidLeadingExpansion, bool forbidTrailingExpansion, bool forceLeadingExpansion, bool forceTrailingExpansion)
+{
+    bool expandLeft = ideograph;
+    bool expandRight = ideograph;
+    if (treatAsSpace) {
+        if (ltr)
+            expandRight = true;
+        else
+            expandLeft = true;
+    }
+    if (isAfterExpansion)
+        expandLeft = false;
+    ASSERT(!forbidLeadingExpansion || !forceLeadingExpansion);
+    ASSERT(!forbidTrailingExpansion || !forceTrailingExpansion);
+    if (forbidLeadingExpansion)
+        expandLeft = false;
+    if (forbidTrailingExpansion)
+        expandRight = false;
+    if (forceLeadingExpansion)
+        expandLeft = true;
+    if (forceTrailingExpansion)
+        expandRight = true;
+    return std::make_pair(expandLeft, expandRight);
+}
+
</ins><span class="cx"> void ComplexTextController::adjustGlyphsAndAdvances()
</span><span class="cx"> {
</span><ins>+    bool afterExpansion = (m_run.expansionBehavior() &amp; LeadingExpansionMask) == ForbidLeadingExpansion;
</ins><span class="cx">     CGFloat widthSinceLastCommit = 0;
</span><span class="cx">     size_t runCount = m_complexTextRuns.size();
</span><span class="cx">     bool hasExtraSpacing = (m_font.letterSpacing() || m_font.wordSpacing() || m_expansion) &amp;&amp; !m_run.spacingDisabled();
</span><ins>+    bool runForcesLeadingExpansion = (m_run.expansionBehavior() &amp; LeadingExpansionMask) == ForceLeadingExpansion;
+    bool runForcesTrailingExpansion = (m_run.expansionBehavior() &amp; TrailingExpansionMask) == ForceTrailingExpansion;
+    bool runForbidsLeadingExpansion = (m_run.expansionBehavior() &amp; LeadingExpansionMask) == ForbidLeadingExpansion;
+    bool runForbidsTrailingExpansion = (m_run.expansionBehavior() &amp; TrailingExpansionMask) == ForbidTrailingExpansion;
+    // We are iterating in glyph order, not string order. Compare this to WidthIterator::advanceInternal()
</ins><span class="cx">     for (size_t r = 0; r &lt; runCount; ++r) {
</span><span class="cx">         ComplexTextRun&amp; complexTextRun = *m_complexTextRuns[r];
</span><span class="cx">         unsigned glyphCount = complexTextRun.glyphCount();
</span><span class="lines">@@ -662,16 +691,33 @@
</span><span class="cx">             if (hasExtraSpacing) {
</span><span class="cx">                 // If we're a glyph with an advance, go ahead and add in letter-spacing.
</span><span class="cx">                 // That way we weed out zero width lurkers.  This behavior matches the fast text code path.
</span><del>-                if (advance.width &amp;&amp; m_font.letterSpacing())
</del><ins>+                if (advance.width)
</ins><span class="cx">                     advance.width += m_font.letterSpacing();
</span><span class="cx"> 
</span><ins>+                bool lastCharacter = static_cast&lt;unsigned&gt;(characterIndex + 1) == m_run.length() || (U16_IS_SURROGATE_LEAD(ch) &amp;&amp; static_cast&lt;unsigned&gt;(characterIndex + 2) == m_run.length() &amp;&amp; U16_IS_SURROGATE_TRAIL(*(cp + characterIndex + 1)));
+
+                bool forceLeadingExpansion = false; // On the left, regardless of m_run.ltr()
+                bool forceTrailingExpansion = false; // On the right, regardless of m_run.ltr()
+                bool forbidLeadingExpansion = false;
+                bool forbidTrailingExpansion = false;
+                if (runForcesLeadingExpansion)
+                    forceLeadingExpansion = m_run.ltr() ? !characterIndex : lastCharacter;
+                if (runForcesTrailingExpansion)
+                    forceTrailingExpansion = m_run.ltr() ? lastCharacter : !characterIndex;
+                if (runForbidsLeadingExpansion)
+                    forbidLeadingExpansion = m_run.ltr() ? !characterIndex : lastCharacter;
+                if (runForbidsTrailingExpansion)
+                    forbidTrailingExpansion = m_run.ltr() ? lastCharacter : !characterIndex;
</ins><span class="cx">                 // Handle justification and word-spacing.
</span><del>-                if (treatAsSpace || FontCascade::isCJKIdeographOrSymbol(ch)) {
</del><ins>+                bool ideograph = FontCascade::isCJKIdeographOrSymbol(ch);
+                if (treatAsSpace || ideograph || forceLeadingExpansion || forceTrailingExpansion) {
</ins><span class="cx">                     // Distribute the run's total expansion evenly over all expansion opportunities in the run.
</span><span class="cx">                     if (m_expansion) {
</span><ins>+                        bool expandLeft, expandRight;
+                        std::tie(expandLeft, expandRight) = expansionLocation(ideograph, treatAsSpace, m_run.ltr(), afterExpansion, forbidLeadingExpansion, forbidTrailingExpansion, forceLeadingExpansion, forceTrailingExpansion);
</ins><span class="cx">                         float previousExpansion = m_expansion;
</span><del>-                        if (!treatAsSpace &amp;&amp; !m_afterExpansion) {
-                            // Take the expansion opportunity before this ideograph.
</del><ins>+                        if (expandLeft) {
+                            // Increase previous width
</ins><span class="cx">                             m_expansion -= m_expansionPerOpportunity;
</span><span class="cx">                             float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
</span><span class="cx">                             m_totalWidth += expansionAtThisOpportunity;
</span><span class="lines">@@ -681,19 +727,20 @@
</span><span class="cx">                                 m_adjustedAdvances.last().width += expansionAtThisOpportunity;
</span><span class="cx">                             previousExpansion = m_expansion;
</span><span class="cx">                         }
</span><del>-                        if (!lastGlyph || m_run.allowsTrailingExpansion()) {
</del><ins>+                        if (expandRight) {
</ins><span class="cx">                             m_expansion -= m_expansionPerOpportunity;
</span><del>-                            advance.width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
-                            m_afterExpansion = true;
</del><ins>+                            float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
+                            advance.width += expansionAtThisOpportunity;
+                            afterExpansion = true;
</ins><span class="cx">                         }
</span><span class="cx">                     } else
</span><del>-                        m_afterExpansion = false;
</del><ins>+                        afterExpansion = false;
</ins><span class="cx"> 
</span><span class="cx">                     // Account for word-spacing.
</span><span class="cx">                     if (treatAsSpace &amp;&amp; (ch != '\t' || !m_run.allowTabs()) &amp;&amp; (characterIndex &gt; 0 || r &gt; 0) &amp;&amp; m_font.wordSpacing())
</span><span class="cx">                         advance.width += m_font.wordSpacing();
</span><span class="cx">                 } else
</span><del>-                    m_afterExpansion = false;
</del><ins>+                    afterExpansion = false;
</ins><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             // Apply rounding hacks if needed.
</span><span class="lines">@@ -746,6 +793,7 @@
</span><span class="cx">         if (!isMonotonic)
</span><span class="cx">             complexTextRun.setIsNonMonotonic();
</span><span class="cx">     }
</span><ins>+
</ins><span class="cx">     m_totalWidth += widthSinceLastCommit;
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsmacComplexTextControllerh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.h (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.h        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/graphics/mac/ComplexTextController.h        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -69,6 +69,8 @@
</span><span class="cx">     float maxGlyphBoundingBoxX() const { return m_maxGlyphBoundingBoxX; }
</span><span class="cx">     float minGlyphBoundingBoxY() const { return m_minGlyphBoundingBoxY; }
</span><span class="cx">     float maxGlyphBoundingBoxY() const { return m_maxGlyphBoundingBoxY; }
</span><ins>+
+    float leadingExpansion() const { return m_leadingExpansion; }
</ins><span class="cx">     
</span><span class="cx"> private:
</span><span class="cx">     class ComplexTextRun : public RefCounted&lt;ComplexTextRun&gt; {
</span><span class="lines">@@ -167,7 +169,6 @@
</span><span class="cx">     float m_expansion;
</span><span class="cx">     float m_expansionPerOpportunity;
</span><span class="cx">     float m_leadingExpansion;
</span><del>-    bool m_afterExpansion;
</del><span class="cx"> 
</span><span class="cx">     HashSet&lt;const Font*&gt;* m_fallbackFonts;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformtextTextFlagsh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/text/TextFlags.h (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/text/TextFlags.h        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/platform/text/TextFlags.h        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -32,11 +32,18 @@
</span><span class="cx"> 
</span><span class="cx"> inline bool isLeftToRightDirection(TextDirection direction) { return direction == LTR; }
</span><span class="cx"> 
</span><ins>+// Here, &quot;Leading&quot; and &quot;Trailing&quot; are relevant after the line has been rearranged for bidi.
+// (&quot;Leading&quot; means &quot;left&quot; and &quot;Trailing&quot; means &quot;right.&quot;)
</ins><span class="cx"> enum ExpansionBehaviorFlags {
</span><span class="cx">     ForbidTrailingExpansion = 0 &lt;&lt; 0,
</span><span class="cx">     AllowTrailingExpansion = 1 &lt;&lt; 0,
</span><del>-    ForbidLeadingExpansion = 0 &lt;&lt; 1,
-    AllowLeadingExpansion = 1 &lt;&lt; 1,
</del><ins>+    ForceTrailingExpansion = 2 &lt;&lt; 0,
+    TrailingExpansionMask = 3 &lt;&lt; 0,
+
+    ForbidLeadingExpansion = 0 &lt;&lt; 2,
+    AllowLeadingExpansion = 1 &lt;&lt; 2,
+    ForceLeadingExpansion = 2 &lt;&lt; 2,
+    LeadingExpansionMask = 3 &lt;&lt; 2,
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> typedef unsigned ExpansionBehavior;
</span></span></pre></div>
<a id="trunkSourceWebCorerenderingInlineBoxh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/rendering/InlineBox.h (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/rendering/InlineBox.h        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/rendering/InlineBox.h        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -315,10 +315,13 @@
</span><span class="cx">             , m_isHorizontal(isHorizontal)
</span><span class="cx">             , m_endsWithBreak(false)
</span><span class="cx">             , m_hasSelectedChildrenOrCanHaveLeadingExpansion(false)
</span><ins>+            , m_canHaveTrailingExpansion(false)
</ins><span class="cx">             , m_knownToHaveNoOverflow(true)  
</span><span class="cx">             , m_hasEllipsisBoxOrHyphen(false)
</span><span class="cx">             , m_dirOverride(false)
</span><span class="cx">             , m_behavesLikeText(false)
</span><ins>+            , m_forceTrailingExpansion(false)
+            , m_forceLeadingExpansion(false)
</ins><span class="cx">             , m_determinedIfNextOnLineExists(false)
</span><span class="cx">             , m_nextOnLineExists(false)
</span><span class="cx">         {
</span><span class="lines">@@ -344,11 +347,14 @@
</span><span class="cx">         ADD_BOOLEAN_BITFIELD(endsWithBreak, EndsWithBreak); // Whether the line ends with a &lt;br&gt;.
</span><span class="cx">         // shared between RootInlineBox and InlineTextBox
</span><span class="cx">         ADD_BOOLEAN_BITFIELD(hasSelectedChildrenOrCanHaveLeadingExpansion, HasSelectedChildrenOrCanHaveLeadingExpansion);
</span><ins>+        ADD_BOOLEAN_BITFIELD(canHaveTrailingExpansion, CanHaveTrailingExpansion);
</ins><span class="cx">         ADD_BOOLEAN_BITFIELD(knownToHaveNoOverflow, KnownToHaveNoOverflow);
</span><span class="cx">         ADD_BOOLEAN_BITFIELD(hasEllipsisBoxOrHyphen, HasEllipsisBoxOrHyphen);
</span><span class="cx">         // for InlineTextBox
</span><span class="cx">         ADD_BOOLEAN_BITFIELD(dirOverride, DirOverride);
</span><span class="cx">         ADD_BOOLEAN_BITFIELD(behavesLikeText, BehavesLikeText); // Whether or not this object represents text with a non-zero height. Includes non-image list markers, text boxes, br.
</span><ins>+        ADD_BOOLEAN_BITFIELD(forceTrailingExpansion, ForceTrailingExpansion);
+        ADD_BOOLEAN_BITFIELD(forceLeadingExpansion, ForceLeadingExpansion);
</ins><span class="cx"> 
</span><span class="cx">     private:
</span><span class="cx">         mutable unsigned m_determinedIfNextOnLineExists : 1;
</span><span class="lines">@@ -414,6 +420,12 @@
</span><span class="cx">     void setHasHyphen(bool hasHyphen) { m_bitfields.setHasEllipsisBoxOrHyphen(hasHyphen); }    
</span><span class="cx">     bool canHaveLeadingExpansion() const { return m_bitfields.hasSelectedChildrenOrCanHaveLeadingExpansion(); }
</span><span class="cx">     void setCanHaveLeadingExpansion(bool canHaveLeadingExpansion) { m_bitfields.setHasSelectedChildrenOrCanHaveLeadingExpansion(canHaveLeadingExpansion); }
</span><ins>+    bool canHaveTrailingExpansion() const { return m_bitfields.canHaveTrailingExpansion(); }
+    void setCanHaveTrailingExpansion(bool canHaveTrailingExpansion) { m_bitfields.setCanHaveTrailingExpansion(canHaveTrailingExpansion); }
+    void setForceTrailingExpansion() { m_bitfields.setForceTrailingExpansion(true); }
+    bool forceTrailingExpansion() const { return m_bitfields.forceTrailingExpansion(); }
+    void setForceLeadingExpansion() { m_bitfields.setForceLeadingExpansion(true); }
+    bool forceLeadingExpansion() const { return m_bitfields.forceLeadingExpansion(); }
</ins><span class="cx">     
</span><span class="cx">     // For InlineFlowBox and InlineTextBox
</span><span class="cx">     bool extracted() const { return m_bitfields.extracted(); }
</span></span></pre></div>
<a id="trunkSourceWebCorerenderingInlineTextBoxh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/rendering/InlineTextBox.h (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/rendering/InlineTextBox.h        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/rendering/InlineTextBox.h        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -77,6 +77,12 @@
</span><span class="cx">     using InlineBox::setHasHyphen;
</span><span class="cx">     using InlineBox::canHaveLeadingExpansion;
</span><span class="cx">     using InlineBox::setCanHaveLeadingExpansion;
</span><ins>+    using InlineBox::canHaveTrailingExpansion;
+    using InlineBox::setCanHaveTrailingExpansion;
+    using InlineBox::forceTrailingExpansion;
+    using InlineBox::setForceTrailingExpansion;
+    using InlineBox::forceLeadingExpansion;
+    using InlineBox::setForceLeadingExpansion;
</ins><span class="cx"> 
</span><span class="cx">     static inline bool compareByStart(const InlineTextBox* first, const InlineTextBox* second) { return first-&gt;start() &lt; second-&gt;start(); }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCorerenderingRenderBlockLineLayoutcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/rendering/RenderBlockLineLayout.cpp (182235 => 182236)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/rendering/RenderBlockLineLayout.cpp        2015-04-01 15:56:34 UTC (rev 182235)
+++ trunk/Source/WebCore/rendering/RenderBlockLineLayout.cpp        2015-04-01 16:58:20 UTC (rev 182236)
</span><span class="lines">@@ -722,6 +722,45 @@
</span><span class="cx">     lineBox-&gt;placeBoxesInInlineDirection(lineLogicalLeft, needsWordSpacing);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static inline ExpansionBehavior expansionBehaviorForInlineTextBox(bool isAfterExpansion)
+{
+    ExpansionBehavior result = AllowTrailingExpansion;
+    result |= isAfterExpansion ? ForbidLeadingExpansion : AllowLeadingExpansion;
+    return result;
+}
+
+static inline void applyExpansionBehavior(InlineTextBox&amp; textBox, ExpansionBehavior expansionBehavior)
+{
+    switch (expansionBehavior &amp; LeadingExpansionMask) {
+    case ForceLeadingExpansion:
+        textBox.setForceLeadingExpansion();
+        break;
+    case ForbidLeadingExpansion:
+        textBox.setCanHaveLeadingExpansion(false);
+        break;
+    case AllowLeadingExpansion:
+        textBox.setCanHaveLeadingExpansion(true);
+        break;
+    default:
+        ASSERT_NOT_REACHED();
+        break;
+    }
+    switch (expansionBehavior &amp; TrailingExpansionMask) {
+    case ForceTrailingExpansion:
+        textBox.setForceTrailingExpansion();
+        break;
+    case ForbidTrailingExpansion:
+        textBox.setCanHaveTrailingExpansion(false);
+        break;
+    case AllowTrailingExpansion:
+        textBox.setCanHaveTrailingExpansion(true);
+        break;
+    default:
+        ASSERT_NOT_REACHED();
+        break;
+    }
+}
+
</ins><span class="cx"> BidiRun* RenderBlockFlow::computeInlineDirectionPositionsForSegment(RootInlineBox* lineBox, const LineInfo&amp; lineInfo, ETextAlign textAlign, float&amp; logicalLeft, 
</span><span class="cx">     float&amp; availableLogicalWidth, BidiRun* firstRun, BidiRun* trailingSpaceRun, GlyphOverflowAndFallbackFontsMap&amp; textBoxDataMap, VerticalPositionCache&amp; verticalPositionCache,
</span><span class="cx">     WordMeasurements&amp; wordMeasurements)
</span><span class="lines">@@ -742,10 +781,12 @@
</span><span class="cx">         }
</span><span class="cx">         if (is&lt;RenderText&gt;(run-&gt;renderer())) {
</span><span class="cx">             auto&amp; renderText = downcast&lt;RenderText&gt;(run-&gt;renderer());
</span><ins>+            auto&amp; textBox = downcast&lt;InlineTextBox&gt;(*run-&gt;box());
</ins><span class="cx">             if (textAlign == JUSTIFY &amp;&amp; run != trailingSpaceRun) {
</span><del>-                if (!isAfterExpansion)
-                    downcast&lt;InlineTextBox&gt;(*run-&gt;box()).setCanHaveLeadingExpansion(true);
-                unsigned opportunitiesInRun = FontCascade::expansionOpportunityCount(renderText.stringView(run-&gt;m_start, run-&gt;m_stop), run-&gt;box()-&gt;direction(), isAfterExpansion);
</del><ins>+                ExpansionBehavior expansionBehavior = expansionBehaviorForInlineTextBox(isAfterExpansion);
+                applyExpansionBehavior(textBox, expansionBehavior);
+                unsigned opportunitiesInRun;
+                std::tie(opportunitiesInRun, isAfterExpansion) = FontCascade::expansionOpportunityCount(renderText.stringView(run-&gt;m_start, run-&gt;m_stop), run-&gt;box()-&gt;direction(), expansionBehavior);
</ins><span class="cx">                 expansionOpportunities.append(opportunitiesInRun);
</span><span class="cx">                 expansionOpportunityCount += opportunitiesInRun;
</span><span class="cx">             }
</span><span class="lines">@@ -766,11 +807,13 @@
</span><span class="cx">                     for (auto* leafChild = rubyBase-&gt;firstRootBox()-&gt;firstLeafChild(); leafChild; leafChild = leafChild-&gt;nextLeafChild()) {
</span><span class="cx">                         if (!is&lt;InlineTextBox&gt;(*leafChild))
</span><span class="cx">                             continue;
</span><del>-                        if (!isAfterExpansion)
-                            downcast&lt;InlineTextBox&gt;(*leafChild).setCanHaveLeadingExpansion(true);
</del><ins>+                        auto&amp; textBox = downcast&lt;InlineTextBox&gt;(*leafChild);
</ins><span class="cx">                         encounteredJustifiedRuby = true;
</span><span class="cx">                         auto&amp; renderText = downcast&lt;RenderText&gt;(leafChild-&gt;renderer());
</span><del>-                        unsigned opportunitiesInRun = FontCascade::expansionOpportunityCount(renderText.stringView(), leafChild-&gt;direction(), isAfterExpansion);
</del><ins>+                        ExpansionBehavior expansionBehavior = expansionBehaviorForInlineTextBox(isAfterExpansion);
+                        applyExpansionBehavior(textBox, expansionBehavior);
+                        unsigned opportunitiesInRun;
+                        std::tie(opportunitiesInRun, isAfterExpansion) = FontCascade::expansionOpportunityCount(renderText.stringView(), leafChild-&gt;direction(), expansionBehavior);
</ins><span class="cx">                         expansionOpportunities.append(opportunitiesInRun);
</span><span class="cx">                         expansionOpportunityCount += opportunitiesInRun;
</span><span class="cx">                     }
</span></span></pre>
</div>
</div>

</body>
</html>