Diff
Modified: trunk/Source/WebCore/ChangeLog (253923 => 253924)
--- trunk/Source/WebCore/ChangeLog 2019-12-27 17:58:02 UTC (rev 253923)
+++ trunk/Source/WebCore/ChangeLog 2019-12-28 16:07:58 UTC (rev 253924)
@@ -1,3 +1,52 @@
+2019-12-28 Zalan Bujtas <[email protected]>
+
+ [LFC][IFC] Keep the LineBreaker object around until after the line is closed.
+ https://bugs.webkit.org/show_bug.cgi?id=205613
+ <rdar://problem/58222870>
+
+ Reviewed by Antti Koivisto.
+
+ In order to be able to point back to an earlier line wrap opportunity on the line e.g.
+ <div style="white-space: pre"><span style="white-space: normal">earlier_wrap opportunities</span> <span>can't_wrap_this content</span></div>
+ the LineBreaker class needs more context.
+ Currently (taking the example above), if the available space runs out somewhere around the second <span> we would just simply
+ overflow the line since the overflowing content has a style saying "do not wrap".
+ However the line has multiple earlier wrap opportunities inside the first <span>.
+ Since we construct a LineBreaker object for each continuous run
+
+ 1. [container start][earlier_wrap]
+ 2. [ ]
+ 3. [opportunities][container end]
+ 4. [ ]
+ 5. [container start][can't_wrap_this]
+ 6. [ ]
+ 7. [content][container end]
+
+ the LineBreaker does not have enough context to point back to the last line wrap opportunity (after run #3).
+
+ This patch is in preparation for supporting such content.
+ 1. Rename couple of functions and structs
+ 2. Move the LineBreaker construction to LineLayoutContext::layoutLine so that we can keep it around for multiple set of runs.
+
+ * layout/inlineformatting/InlineLineBreaker.cpp:
+ (WebCore::Layout::isContentWrappingAllowed):
+ (WebCore::Layout::isContentSplitAllowed):
+ (WebCore::Layout::LineBreaker::isTextContentWrappingAllowed const):
+ (WebCore::Layout::LineBreaker::shouldKeepEndOfLineWhitespace const):
+ (WebCore::Layout::LineBreaker::shouldWrapInlineContent):
+ (WebCore::Layout::isTextContentWrappingAllowed): Deleted.
+ (WebCore::Layout::shouldKeepEndOfLineWhitespace): Deleted.
+ (WebCore::Layout::LineBreaker::breakingContextForInlineContent): Deleted.
+ * layout/inlineformatting/InlineLineBreaker.h:
+ (WebCore::Layout::LineBreaker::setHyphenationDisabled):
+ * layout/inlineformatting/LineLayoutContext.cpp:
+ (WebCore::Layout::isLineConsideredEmpty):
+ (WebCore::Layout::LineLayoutContext::layoutLine):
+ (WebCore::Layout::LineLayoutContext::checkForLineWrapAndCommit):
+ (WebCore::Layout::LineLayoutContext::commitContent):
+ (WebCore::Layout::LineLayoutContext::placeInlineContentOnCurrentLine): Deleted.
+ * layout/inlineformatting/LineLayoutContext.h:
+
2019-12-23 Darin Adler <[email protected]>
Refactor to simplify broadcasting to all media elements
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp (253923 => 253924)
--- trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp 2019-12-27 17:58:02 UTC (rev 253923)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp 2019-12-28 16:07:58 UTC (rev 253924)
@@ -37,32 +37,21 @@
namespace WebCore {
namespace Layout {
-static inline bool isTextContentWrappingAllowed(const RenderStyle& style)
+static inline bool isWrappingAllowed(const RenderStyle& style)
{
// Do not try to push overflown 'pre' and 'no-wrap' content to next line.
return style.whiteSpace() != WhiteSpace::Pre && style.whiteSpace() != WhiteSpace::NoWrap;
}
-static inline bool isTextContentWrappingAllowed(const LineBreaker::ContinousContent& candidateRuns)
+bool LineBreaker::isContentWrappingAllowed(const ContinousContent& candidateRuns) const
{
// Use the last inline item with content (where we would be wrapping) to decide if content wrapping is allowed.
auto runIndex = candidateRuns.lastContentRunIndex().valueOr(candidateRuns.size() - 1);
- return isTextContentWrappingAllowed(candidateRuns.runs()[runIndex].inlineItem.style());
+ return isWrappingAllowed(candidateRuns.runs()[runIndex].inlineItem.style());
}
-static inline bool isContentSplitAllowed(const LineBreaker::Run& run)
+bool LineBreaker::shouldKeepEndOfLineWhitespace(const ContinousContent& candidateRuns) const
{
- ASSERT(run.inlineItem.isText() || run.inlineItem.isContainerStart() || run.inlineItem.isContainerEnd());
- if (!run.inlineItem.isText()) {
- // Can't split horizontal spacing -> e.g. <span style="padding-right: 100px;">textcontent</span>, if the [container end] is the overflown inline item
- // we need to check if there's another inline item beyond the [container end] to split.
- return false;
- }
- return isTextContentWrappingAllowed(run.inlineItem.style());
-}
-
-static inline bool shouldKeepEndOfLineWhitespace(const LineBreaker::ContinousContent& candidateRuns)
-{
// Grab the style and check for white-space property to decided whether we should let this whitespace content overflow the current line.
// Note that the "keep" in the context means we let the whitespace content sit on the current line.
// It might very well get collapsed when we close the line (normal/nowrap/pre-line).
@@ -71,36 +60,37 @@
return whitespace == WhiteSpace::Normal || whitespace == WhiteSpace::NoWrap || whitespace == WhiteSpace::PreWrap || whitespace == WhiteSpace::PreLine;
}
-LineBreaker::BreakingContext LineBreaker::breakingContextForInlineContent(const ContinousContent& candidateRuns, const LineStatus& lineStatus)
+LineBreaker::Result LineBreaker::shouldWrapInlineContent(const RunList& candidateRuns, const LineStatus& lineStatus)
{
- ASSERT(!candidateRuns.isEmpty());
- if (candidateRuns.width() <= lineStatus.availableWidth)
- return { BreakingContext::ContentWrappingRule::Keep, IsEndOfLine::No, { } };
- if (candidateRuns.hasTrailingCollapsibleContent()) {
- ASSERT(candidateRuns.hasTextContentOnly());
- auto IsEndOfLine = isTextContentWrappingAllowed(candidateRuns) ? IsEndOfLine::Yes : IsEndOfLine::No;
+ auto candidateContent = ContinousContent { candidateRuns };
+ ASSERT(!candidateContent.isEmpty());
+ if (candidateContent.width() <= lineStatus.availableWidth)
+ return { Result::Action::Keep, IsEndOfLine::No, { } };
+ if (candidateContent.hasTrailingCollapsibleContent()) {
+ ASSERT(candidateContent.hasTextContentOnly());
+ auto IsEndOfLine = isContentWrappingAllowed(candidateContent) ? IsEndOfLine::Yes : IsEndOfLine::No;
// First check if the content fits without the trailing collapsible part.
- if (candidateRuns.nonCollapsibleWidth() <= lineStatus.availableWidth)
- return { BreakingContext::ContentWrappingRule::Keep, IsEndOfLine, { } };
+ if (candidateContent.nonCollapsibleWidth() <= lineStatus.availableWidth)
+ return { Result::Action::Keep, IsEndOfLine, { } };
// Now check if we can trim the line too.
- if (lineStatus.lineHasFullyCollapsibleTrailingRun && candidateRuns.isTrailingContentFullyCollapsible()) {
+ if (lineStatus.lineHasFullyCollapsibleTrailingRun && candidateContent.isTrailingContentFullyCollapsible()) {
// If this new content is fully collapsible, it shoud surely fit.
- return { BreakingContext::ContentWrappingRule::Keep, IsEndOfLine, { } };
+ return { Result::Action::Keep, IsEndOfLine, { } };
}
- } else if (lineStatus.collapsibleWidth && candidateRuns.hasNonContentRunsOnly()) {
+ } else if (lineStatus.collapsibleWidth && candidateContent.hasNonContentRunsOnly()) {
// Let's see if the non-content runs fit when the line has trailing collapsible content.
// "text content <span style="padding: 1px"></span>" <- the <span></span> runs could fit after collapsing the trailing whitespace.
- if (candidateRuns.width() <= lineStatus.availableWidth + lineStatus.collapsibleWidth)
- return { BreakingContext::ContentWrappingRule::Keep, IsEndOfLine::No, { } };
+ if (candidateContent.width() <= lineStatus.availableWidth + lineStatus.collapsibleWidth)
+ return { Result::Action::Keep, IsEndOfLine::No, { } };
}
- if (candidateRuns.isVisuallyEmptyWhitespaceContentOnly() && shouldKeepEndOfLineWhitespace(candidateRuns)) {
+ if (candidateContent.isVisuallyEmptyWhitespaceContentOnly() && shouldKeepEndOfLineWhitespace(candidateContent)) {
// This overflowing content apparently falls into the remove/hang end-of-line-spaces catergory.
// see https://www.w3.org/TR/css-text-3/#white-space-property matrix
- return { BreakingContext::ContentWrappingRule::Keep, IsEndOfLine::No, { } };
+ return { Result::Action::Keep, IsEndOfLine::No, { } };
}
- if (candidateRuns.hasTextContentOnly()) {
- auto& runs = candidateRuns.runs();
+ if (candidateContent.hasTextContentOnly()) {
+ auto& runs = candidateContent.runs();
if (auto wrappedTextContent = wrapTextContent(runs, lineStatus)) {
if (!wrappedTextContent->trailingRunIndex && wrappedTextContent->contentOverflows) {
// We tried to split the content but the available space can't even accommodate the first character.
@@ -107,26 +97,26 @@
// 1. Push the content over to the next line when we've got content on the line already.
// 2. Keep the first character on the empty line (or keep the whole run if it has only one character).
if (!lineStatus.lineIsEmpty)
- return { BreakingContext::ContentWrappingRule::Push, IsEndOfLine::Yes, { } };
- auto firstTextRunIndex = *candidateRuns.firstTextRunIndex();
+ return { Result::Action::Push, IsEndOfLine::Yes, { } };
+ auto firstTextRunIndex = *candidateContent.firstTextRunIndex();
auto& inlineTextItem = downcast<InlineTextItem>(runs[firstTextRunIndex].inlineItem);
ASSERT(inlineTextItem.length());
if (inlineTextItem.length() == 1)
- return { BreakingContext::ContentWrappingRule::Keep, IsEndOfLine::Yes, { } };
+ return { Result::Action::Keep, IsEndOfLine::Yes, { } };
auto firstCharacterWidth = TextUtil::width(inlineTextItem, inlineTextItem.start(), inlineTextItem.start() + 1);
auto firstCharacterRun = PartialRun { 1, firstCharacterWidth, false };
- return { BreakingContext::ContentWrappingRule::Split, IsEndOfLine::Yes, BreakingContext::PartialTrailingContent { firstTextRunIndex, firstCharacterRun } };
+ return { Result::Action::Split, IsEndOfLine::Yes, Result::PartialTrailingContent { firstTextRunIndex, firstCharacterRun } };
}
- auto splitContent = BreakingContext::PartialTrailingContent { wrappedTextContent->trailingRunIndex, wrappedTextContent->partialTrailingRun };
- return { BreakingContext::ContentWrappingRule::Split, IsEndOfLine::Yes, splitContent };
+ auto splitContent = Result::PartialTrailingContent { wrappedTextContent->trailingRunIndex, wrappedTextContent->partialTrailingRun };
+ return { Result::Action::Split, IsEndOfLine::Yes, splitContent };
}
}
// If we are not allowed to break this content, we still need to decide whether keep it or push it to the next line.
- auto isWrappingAllowed = isTextContentWrappingAllowed(candidateRuns);
+ auto isWrappingAllowed = isContentWrappingAllowed(candidateContent);
auto contentOverflows = lineStatus.lineIsEmpty || !isWrappingAllowed;
if (!contentOverflows)
- return { BreakingContext::ContentWrappingRule::Push, IsEndOfLine::Yes, { } };
- return { BreakingContext::ContentWrappingRule::Keep, isWrappingAllowed ? IsEndOfLine::Yes : IsEndOfLine::No, { } };
+ return { Result::Action::Push, IsEndOfLine::Yes, { } };
+ return { Result::Action::Keep, isWrappingAllowed ? IsEndOfLine::Yes : IsEndOfLine::No, { } };
}
bool LineBreaker::shouldWrapFloatBox(InlineLayoutUnit floatLogicalWidth, InlineLayoutUnit availableWidth, bool lineIsEmpty)
@@ -136,6 +126,16 @@
Optional<LineBreaker::WrappedTextContent> LineBreaker::wrapTextContent(const RunList& runs, const LineStatus& lineStatus) const
{
+ auto isContentSplitAllowed = [] (auto& run) {
+ ASSERT(run.inlineItem.isText() || run.inlineItem.isContainerStart() || run.inlineItem.isContainerEnd());
+ if (!run.inlineItem.isText()) {
+ // Can't split horizontal spacing -> e.g. <span style="padding-right: 100px;">textcontent</span>, if the [container end] is the overflown inline item
+ // we need to check if there's another inline item beyond the [container end] to split.
+ return false;
+ }
+ return isWrappingAllowed(run.inlineItem.style());
+ };
+
// Check where the overflow occurs and use the corresponding style to figure out the breaking behaviour.
// <span style="word-break: normal">first</span><span style="word-break: break-all">second</span><span style="word-break: normal">third</span>
InlineLayoutUnit accumulatedRunWidth = 0;
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h (253923 => 253924)
--- trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h 2019-12-27 17:58:02 UTC (rev 253923)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h 2019-12-28 16:07:58 UTC (rev 253924)
@@ -43,14 +43,14 @@
bool needsHyphen { false };
};
enum class IsEndOfLine { No, Yes };
- struct BreakingContext {
- enum class ContentWrappingRule {
+ struct Result {
+ enum class Action {
Keep, // Keep content on the current line.
Split, // Partial content is on the current line.
Push // Content is pushed to the next line.
};
- ContentWrappingRule contentWrappingRule;
- IsEndOfLine isEndOfLine { IsEndOfLine::No };
+ Action action;
+ IsEndOfLine isEndOfLine { IsEndOfLine::No };
struct PartialTrailingContent {
unsigned trailingRunIndex { 0 };
Optional<PartialRun> partialRun; // nullopt partial run means the trailing run is a complete run.
@@ -69,6 +69,18 @@
using RunList = Vector<Run, 30>;
static size_t nextWrapOpportunity(const InlineItems&, unsigned startIndex);
+ struct LineStatus {
+ InlineLayoutUnit availableWidth { 0 };
+ InlineLayoutUnit collapsibleWidth { 0 };
+ bool lineHasFullyCollapsibleTrailingRun { false };
+ bool lineIsEmpty { true };
+ };
+ Result shouldWrapInlineContent(const RunList& candidateRuns, const LineStatus&);
+ bool shouldWrapFloatBox(InlineLayoutUnit floatLogicalWidth, InlineLayoutUnit availableWidth, bool lineIsEmpty);
+
+ void setHyphenationDisabled() { n_hyphenationIsDisabled = true; }
+
+private:
// This struct represents the amount of content committed to line breaking at a time e.g.
// text content <span>span1</span>between<span>span2</span>
// [text][ ][content][ ][container start][span1][container end][between][container start][span2][container end]
@@ -108,18 +120,6 @@
InlineLayoutUnit m_width { 0 };
};
- struct LineStatus {
- InlineLayoutUnit availableWidth { 0 };
- InlineLayoutUnit collapsibleWidth { 0 };
- bool lineHasFullyCollapsibleTrailingRun { false };
- bool lineIsEmpty { true };
- };
- BreakingContext breakingContextForInlineContent(const ContinousContent& candidateRuns, const LineStatus&);
- bool shouldWrapFloatBox(InlineLayoutUnit floatLogicalWidth, InlineLayoutUnit availableWidth, bool lineIsEmpty);
-
- void setHyphenationDisabled() { n_hyphenationIsDisabled = true; }
-
-private:
struct WrappedTextContent {
unsigned trailingRunIndex { 0 };
bool contentOverflows { false };
@@ -134,6 +134,8 @@
OnlyHyphenationAllowed
};
WordBreakRule wordBreakBehavior(const RenderStyle&, bool lineIsEmpty) const;
+ bool shouldKeepEndOfLineWhitespace(const ContinousContent&) const;
+ bool isContentWrappingAllowed(const ContinousContent&) const;
bool n_hyphenationIsDisabled { false };
};
Modified: trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp (253923 => 253924)
--- trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp 2019-12-27 17:58:02 UTC (rev 253923)
+++ trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp 2019-12-28 16:07:58 UTC (rev 253924)
@@ -96,6 +96,11 @@
return boxGeometry.width();
}
+static inline bool isLineConsideredEmpty(const LineBuilder& line)
+{
+ return line.isVisuallyEmpty() && !line.hasIntrusiveFloat();
+}
+
LineLayoutContext::LineLayoutContext(const InlineFormattingContext& inlineFormattingContext, const Container& formattingContextRoot, const InlineItems& inlineItems)
: m_inlineFormattingContext(inlineFormattingContext)
, m_formattingContextRoot(formattingContextRoot)
@@ -111,6 +116,7 @@
m_partialLeadingTextItem = { };
};
reset();
+ auto lineBreaker = LineBreaker { };
auto currentItemIndex = leadingInlineItemIndex;
unsigned committedInlineItemCount = 0;
while (currentItemIndex < m_inlineItems.size()) {
@@ -134,7 +140,7 @@
}
if (!candidateContent.runs().isEmpty()) {
// Now check if we can put this content on the current line.
- auto committedContent = placeInlineContentOnCurrentLine(line, candidateContent.runs());
+ auto committedContent = checkForLineWrapAndCommit(lineBreaker, line, candidateContent.runs());
committedInlineItemCount += committedContent.count;
if (committedContent.isEndOfLine == LineBreaker::IsEndOfLine::Yes) {
// We can't place any more items on the current line.
@@ -245,7 +251,7 @@
return { LineBreaker::IsEndOfLine::No, committedFloatItemCount, { } };
}
-LineLayoutContext::CommittedContent LineLayoutContext::placeInlineContentOnCurrentLine(LineBuilder& line, const LineBreaker::RunList& runs)
+LineLayoutContext::CommittedContent LineLayoutContext::checkForLineWrapAndCommit(LineBreaker& lineBreaker, LineBuilder& line, const LineBreaker::RunList& candidateRuns)
{
auto shouldDisableHyphenation = [&] {
auto& style = root().style();
@@ -253,46 +259,42 @@
return m_successiveHyphenatedLineCount >= limitLines;
};
// Check if this new content fits.
- auto lineIsConsideredEmpty = line.isVisuallyEmpty() && !line.hasIntrusiveFloat();
- auto lineStatus = LineBreaker::LineStatus { line.availableWidth(), line.trailingCollapsibleWidth(), line.isTrailingRunFullyCollapsible(), lineIsConsideredEmpty };
- auto lineBreaker = LineBreaker { };
+ auto lineStatus = LineBreaker::LineStatus { line.availableWidth(), line.trailingCollapsibleWidth(), line.isTrailingRunFullyCollapsible(), isLineConsideredEmpty(line) };
if (shouldDisableHyphenation())
lineBreaker.setHyphenationDisabled();
- auto breakingContext = lineBreaker.breakingContextForInlineContent(LineBreaker::ContinousContent { runs }, lineStatus);
- if (breakingContext.contentWrappingRule == LineBreaker::BreakingContext::ContentWrappingRule::Keep) {
+ auto result = lineBreaker.shouldWrapInlineContent(candidateRuns, lineStatus);
+ if (result.action == LineBreaker::Result::Action::Keep) {
// This continuous content can be fully placed on the current line.
- commitContent(line, runs, { });
- return { breakingContext.isEndOfLine, runs.size(), { } };
+ commitContent(line, candidateRuns, { });
+ return { result.isEndOfLine, candidateRuns.size(), { } };
}
-
- if (breakingContext.contentWrappingRule == LineBreaker::BreakingContext::ContentWrappingRule::Push) {
+ if (result.action == LineBreaker::Result::Action::Push) {
// This continuous content can't be placed on the current line. Nothing to commit at this time.
- return { breakingContext.isEndOfLine, 0, { } };
+ return { result.isEndOfLine, 0, { } };
}
-
- if (breakingContext.contentWrappingRule == LineBreaker::BreakingContext::ContentWrappingRule::Split) {
+ if (result.action == LineBreaker::Result::Action::Split) {
// Commit the combination of full and partial content on the current line.
- ASSERT(breakingContext.partialTrailingContent);
- commitContent(line, runs, breakingContext.partialTrailingContent);
+ ASSERT(result.partialTrailingContent);
+ commitContent(line, candidateRuns, result.partialTrailingContent);
// When splitting multiple runs <span style="word-break: break-all">text</span><span>content</span>, we might end up splitting them at run boundary.
// It simply means we don't really have a partial run. Partial content yes, but not partial run.
- auto trailingRunIndex = breakingContext.partialTrailingContent->trailingRunIndex;
+ auto trailingRunIndex = result.partialTrailingContent->trailingRunIndex;
auto committedInlineItemCount = trailingRunIndex + 1;
- if (!breakingContext.partialTrailingContent->partialRun)
- return { breakingContext.isEndOfLine, committedInlineItemCount, { } };
+ if (!result.partialTrailingContent->partialRun)
+ return { result.isEndOfLine, committedInlineItemCount, { } };
- auto partialRun = *breakingContext.partialTrailingContent->partialRun;
- auto& trailingInlineTextItem = downcast<InlineTextItem>(runs[trailingRunIndex].inlineItem);
+ auto partialRun = *result.partialTrailingContent->partialRun;
+ auto& trailingInlineTextItem = downcast<InlineTextItem>(candidateRuns[trailingRunIndex].inlineItem);
auto overflowLength = trailingInlineTextItem.length() - partialRun.length;
- return { breakingContext.isEndOfLine, committedInlineItemCount, LineContent::PartialContent { partialRun.needsHyphen, overflowLength } };
+ return { result.isEndOfLine, committedInlineItemCount, LineContent::PartialContent { partialRun.needsHyphen, overflowLength } };
}
ASSERT_NOT_REACHED();
return { LineBreaker::IsEndOfLine::No, 0, { } };
}
-void LineLayoutContext::commitContent(LineBuilder& line, const LineBreaker::RunList& runs, Optional<LineBreaker::BreakingContext::PartialTrailingContent> partialTrailingContent)
+void LineLayoutContext::commitContent(LineBuilder& line, const LineBreaker::RunList& runs, Optional<LineBreaker::Result::PartialTrailingContent> partialTrailingContent)
{
for (size_t index = 0; index < runs.size(); ++index) {
auto& run = runs[index];
Modified: trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.h (253923 => 253924)
--- trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.h 2019-12-27 17:58:02 UTC (rev 253923)
+++ trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.h 2019-12-28 16:07:58 UTC (rev 253924)
@@ -61,8 +61,8 @@
};
LineCandidateContent nextContentForLine(unsigned inlineItemIndex, Optional<unsigned> overflowLength, InlineLayoutUnit currentLogicalRight);
CommittedContent addFloatItems(LineBuilder&, const FloatList&);
- CommittedContent placeInlineContentOnCurrentLine(LineBuilder&, const LineBreaker::RunList&);
- void commitContent(LineBuilder&, const LineBreaker::RunList&, Optional<LineBreaker::BreakingContext::PartialTrailingContent>);
+ CommittedContent checkForLineWrapAndCommit(LineBreaker&, LineBuilder&, const LineBreaker::RunList&);
+ void commitContent(LineBuilder&, const LineBreaker::RunList&, Optional<LineBreaker::Result::PartialTrailingContent>);
LineContent close(LineBuilder&, unsigned leadingInlineItemIndex, unsigned committedInlineItemCount, Optional<LineContent::PartialContent>);
const InlineFormattingContext& formattingContext() const { return m_inlineFormattingContext; }