Build font data structure by walking the necessary text. (Bug 706193, patch 3) r=roc

Compute the amount of text in the scope of an nsFontInflationData
object.  This walks the text that's inside of the block formatting
context at which this object is rooted, excluding the text that's inside
any nested BFC.  Using the amount of text, the font sizes of the text,
and the line threshold preference, we compute whether to enable font
size inflation within that block formatting context.
This commit is contained in:
L. David Baron 2012-04-16 15:32:12 -07:00
parent 90733eea84
commit 1f0b12c8b8
6 changed files with 380 additions and 2 deletions

View File

@ -39,8 +39,13 @@
#include "nsFontInflationData.h"
#include "FramePropertyTable.h"
#include "nsTextFragment.h"
#include "nsIFormControlFrame.h"
#include "nsHTMLReflowState.h"
#include "nsTextFrameUtils.h"
using namespace mozilla;
using namespace mozilla::layout;
static void
DestroyFontInflationData(void *aPropertyValue)
@ -55,7 +60,283 @@ nsFontInflationData::FindFontInflationDataFor(const nsIFrame *aFrame)
{
// We have one set of font inflation data per block formatting context.
const nsIFrame *bfc = FlowRootFor(aFrame);
NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
"should have found a flow root");
return static_cast<nsFontInflationData*>(
bfc->Properties().Get(FontInflationDataProperty()));
}
/* static */ void
nsFontInflationData::UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState)
{
nsIFrame *bfc = aReflowState.frame;
NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
"should have been given a flow root");
FrameProperties bfcProps(bfc->Properties());
nsFontInflationData *data = static_cast<nsFontInflationData*>(
bfcProps.Get(FontInflationDataProperty()));
if (!data) {
data = new nsFontInflationData(bfc);
bfcProps.Set(FontInflationDataProperty(), data);
}
data->UpdateWidth(aReflowState);
}
/* static */ void
nsFontInflationData::MarkFontInflationDataTextDirty(nsIFrame *aBFCFrame)
{
NS_ASSERTION(aBFCFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
"should have been given a flow root");
FrameProperties bfcProps(aBFCFrame->Properties());
nsFontInflationData *data = static_cast<nsFontInflationData*>(
bfcProps.Get(FontInflationDataProperty()));
if (data) {
data->MarkTextDirty();
}
}
nsFontInflationData::nsFontInflationData(nsIFrame *aBFCFrame)
: mBFCFrame(aBFCFrame)
, mTextAmount(0)
, mTextThreshold(0)
, mInflationEnabled(false)
, mTextDirty(true)
{
}
/**
* Find the closest common ancestor between aFrame1 and aFrame2, except
* treating the parent of a frame as the first-in-flow of its parent (so
* the result doesn't change when breaking changes).
*
* aKnownCommonAncestor is a known common ancestor of both.
*/
static nsIFrame*
NearestCommonAncestorFirstInFlow(nsIFrame *aFrame1, nsIFrame *aFrame2,
nsIFrame *aKnownCommonAncestor)
{
aFrame1 = aFrame1->GetFirstInFlow();
aFrame2 = aFrame2->GetFirstInFlow();
aKnownCommonAncestor = aKnownCommonAncestor->GetFirstInFlow();
nsAutoTArray<nsIFrame*, 32> ancestors1, ancestors2;
for (nsIFrame *f = aFrame1; f != aKnownCommonAncestor;
(f = f->GetParent()) && (f = f->GetFirstInFlow())) {
ancestors1.AppendElement(f);
}
for (nsIFrame *f = aFrame2; f != aKnownCommonAncestor;
(f = f->GetParent()) && (f = f->GetFirstInFlow())) {
ancestors2.AppendElement(f);
}
nsIFrame *result = aKnownCommonAncestor;
PRUint32 i1 = ancestors1.Length(),
i2 = ancestors2.Length();
while (i1-- != 0 && i2-- != 0) {
if (ancestors1[i1] != ancestors2[i2]) {
break;
}
result = ancestors1[i1];
}
return result;
}
static nscoord
ComputeDescendantWidth(const nsHTMLReflowState& aAncestorReflowState,
nsIFrame *aDescendantFrame)
{
nsIFrame *ancestorFrame = aAncestorReflowState.frame->GetFirstInFlow();
if (aDescendantFrame == ancestorFrame) {
return aAncestorReflowState.ComputedWidth();
}
AutoInfallibleTArray<nsIFrame*, 16> frames;
for (nsIFrame *f = aDescendantFrame; f != ancestorFrame;
f = f->GetParent()) {
frames.AppendElement(f);
}
PRUint32 len = frames.Length();
nsHTMLReflowState *reflowStates = static_cast<nsHTMLReflowState*>
(moz_xmalloc(sizeof(nsHTMLReflowState) * len));
nsPresContext *presContext = aDescendantFrame->PresContext();
for (PRUint32 i = 0; i < len; ++i) {
const nsHTMLReflowState &parentReflowState =
(i == 0) ? aAncestorReflowState : reflowStates[i - 1];
nsSize availSize(parentReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE);
nsIFrame *frame = frames[len - i - 1];
NS_ABORT_IF_FALSE(frame->GetParent()->GetFirstInFlow() ==
parentReflowState.frame->GetFirstInFlow(),
"bad logic in this function");
new (reflowStates + i) nsHTMLReflowState(presContext, parentReflowState,
frame, availSize);
}
NS_ABORT_IF_FALSE(reflowStates[len - 1].frame == aDescendantFrame,
"bad logic in this function");
nscoord result = reflowStates[len - 1].ComputedWidth();
for (PRUint32 i = len; i-- != 0; ) {
reflowStates[i].~nsHTMLReflowState();
}
moz_free(reflowStates);
return result;
}
void
nsFontInflationData::UpdateWidth(const nsHTMLReflowState &aReflowState)
{
nsIFrame *bfc = aReflowState.frame;
NS_ASSERTION(bfc->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT,
"must be block formatting context");
nsIFrame *firstInflatableDescendant =
FindEdgeInflatableFrameIn(bfc, eFromStart);
if (!firstInflatableDescendant) {
mTextAmount = 0;
mTextThreshold = 0; // doesn't matter
mTextDirty = false;
mInflationEnabled = false;
return;
}
nsIFrame *lastInflatableDescendant =
FindEdgeInflatableFrameIn(bfc, eFromEnd);
NS_ABORT_IF_FALSE(!firstInflatableDescendant == !lastInflatableDescendant,
"null-ness should match; NearestCommonAncestorFirstInFlow"
" will crash when passed null");
// Particularly when we're computing for the root BFC, the width of
// nca might differ significantly for the width of bfc.
nsIFrame *nca = NearestCommonAncestorFirstInFlow(firstInflatableDescendant,
lastInflatableDescendant,
bfc);
while (!nsLayoutUtils::IsContainerForFontSizeInflation(nca)) {
nca = nca->GetParent();
}
nscoord newNCAWidth = ComputeDescendantWidth(aReflowState, nca);
// See comment above "font.size.inflation.lineThreshold" in
// modules/libpref/src/init/all.js .
PRUint32 lineThreshold = nsLayoutUtils::FontSizeInflationLineThreshold();
nscoord newTextThreshold = (newNCAWidth * lineThreshold) / 100;
if (mTextThreshold <= mTextAmount && mTextAmount < newTextThreshold) {
// Because we truncate our scan when we hit sufficient text, we now
// need to rescan.
mTextDirty = true;
}
mTextThreshold = newTextThreshold;
mInflationEnabled = mTextAmount >= mTextThreshold;
}
/* static */ nsIFrame*
nsFontInflationData::FindEdgeInflatableFrameIn(nsIFrame* aFrame,
SearchDirection aDirection)
{
// NOTE: This function has a similar structure to ScanTextIn!
// FIXME: Should probably only scan the text that's actually going to
// be inflated!
nsIFormControlFrame* fcf = do_QueryFrame(aFrame);
if (fcf) {
return aFrame;
}
// FIXME: aDirection!
nsAutoTArray<FrameChildList, 4> lists;
aFrame->GetChildLists(&lists);
for (PRUint32 i = 0, len = lists.Length(); i < len; ++i) {
const nsFrameList& list =
lists[(aDirection == eFromStart) ? i : len - i - 1].mList;
for (nsIFrame *kid = (aDirection == eFromStart) ? list.FirstChild()
: list.LastChild();
kid;
kid = (aDirection == eFromStart) ? kid->GetNextSibling()
: kid->GetPrevSibling()) {
if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
// Goes in a different set of inflation data.
continue;
}
if (kid->GetType() == nsGkAtoms::textFrame) {
nsIContent *content = kid->GetContent();
if (content && kid == content->GetPrimaryFrame()) {
PRUint32 len = nsTextFrameUtils::
ComputeApproximateLengthWithWhitespaceCompression(
content, kid->GetStyleText());
if (len != 0) {
return kid;
}
}
} else {
nsIFrame *kidResult =
FindEdgeInflatableFrameIn(kid, aDirection);
if (kidResult) {
return kidResult;
}
}
}
}
return nsnull;
}
void
nsFontInflationData::ScanText()
{
mTextDirty = false;
mTextAmount = 0;
ScanTextIn(mBFCFrame);
mInflationEnabled = mTextAmount >= mTextThreshold;
}
void
nsFontInflationData::ScanTextIn(nsIFrame *aFrame)
{
// NOTE: This function has a similar structure to FindEdgeInflatableFrameIn!
// FIXME: Should probably only scan the text that's actually going to
// be inflated!
nsIFrame::ChildListIterator lists(aFrame);
for (; !lists.IsDone(); lists.Next()) {
nsFrameList::Enumerator kids(lists.CurrentList());
for (; !kids.AtEnd(); kids.Next()) {
nsIFrame *kid = kids.get();
if (kid->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
// Goes in a different set of inflation data.
continue;
}
if (kid->GetType() == nsGkAtoms::textFrame) {
nsIContent *content = kid->GetContent();
if (content && kid == content->GetPrimaryFrame()) {
PRUint32 len = nsTextFrameUtils::
ComputeApproximateLengthWithWhitespaceCompression(
content, kid->GetStyleText());
if (len != 0) {
nscoord fontSize = kid->GetStyleFont()->mFont.size;
if (fontSize > 0) {
mTextAmount += fontSize * len;
}
}
}
} else {
// recursive step
ScanTextIn(kid);
}
if (mTextAmount >= mTextThreshold) {
return;
}
}
}
}

View File

@ -44,14 +44,47 @@
#include "nsLayoutUtils.h"
#include "nsBlockFrame.h"
struct nsHTMLReflowState;
class nsFontInflationData
{
public:
static nsFontInflationData* FindFontInflationDataFor(const nsIFrame *aFrame);
static void
UpdateFontInflationDataWidthFor(const nsHTMLReflowState& aReflowState);
static void MarkFontInflationDataTextDirty(nsIFrame *aFrame);
bool InflationEnabled() {
if (mTextDirty) {
ScanText();
}
return mInflationEnabled;
}
private:
nsFontInflationData(nsIFrame* aBFCFrame);
nsFontInflationData(const nsFontInflationData&) MOZ_DELETE;
void operator=(const nsFontInflationData&) MOZ_DELETE;
void UpdateWidth(const nsHTMLReflowState &aReflowState);
enum SearchDirection { eFromStart, eFromEnd };
static nsIFrame* FindEdgeInflatableFrameIn(nsIFrame *aFrame,
SearchDirection aDirection);
void MarkTextDirty() { mTextDirty = true; }
void ScanText();
// Scan text in the subtree rooted at aFrame. Increment mTextAmount
// by multiplying the number of characters found by the font size
// (yielding the width that would be occupied by the characters if
// they were all em squares). But stop scanning if mTextAmount
// crosses mTextThreshold.
void ScanTextIn(nsIFrame *aFrame);
static const nsIFrame* FlowRootFor(const nsIFrame *aFrame)
{
while (!(aFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT)) {
@ -60,6 +93,10 @@ private:
return aFrame;
}
nsIFrame *mBFCFrame;
nscoord mTextAmount, mTextThreshold;
bool mInflationEnabled; // for this BFC
bool mTextDirty;
};
#endif /* !defined(nsFontInflationData_h_) */

View File

@ -127,6 +127,7 @@
#include "nsRenderingContext.h"
#include "CSSCalc.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsFontInflationData.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
@ -3518,6 +3519,10 @@ nsFrame::MarkIntrinsicWidthsDirty()
CoordNeedsRecalc(metrics->mFlex);
CoordNeedsRecalc(metrics->mAscent);
}
if (GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
nsFontInflationData::MarkFontInflationDataTextDirty(this);
}
}
/* virtual */ nscoord

View File

@ -56,9 +56,8 @@
#include "nsIPercentHeightObserver.h"
#include "nsLayoutUtils.h"
#include "mozilla/Preferences.h"
#ifdef IBMBIDI
#include "nsBidiUtils.h"
#endif
#include "nsFontInflationData.h"
#ifdef NS_DEBUG
#undef NOISY_VERTICAL_ALIGN
@ -316,6 +315,12 @@ nsHTMLReflowState::Init(nsPresContext* aPresContext,
"have unconstrained width; this should only result from "
"very large sizes, not attempts at intrinsic width "
"calculation");
if (frame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) {
// Create our font inflation data if we don't have it already, and
// give it our current width information.
nsFontInflationData::UpdateFontInflationDataWidthFor(*this);
}
}
void nsHTMLReflowState::InitCBReflowState()

View File

@ -43,6 +43,8 @@
#include "gfxFont.h"
#include "nsUnicharUtils.h"
#include "nsBidiUtils.h"
#include "nsIContent.h"
#include "nsStyleStruct.h"
// XXX TODO implement transform of backslash to yen that nsTextTransform does
// when requested by PresContext->LanguageSpecificTransformType(). Do it with
@ -248,6 +250,47 @@ nsTextFrameUtils::TransformText(const PRUint8* aText, PRUint32 aLength,
return aOutput;
}
PRUint32
nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
nsIContent *aContent, const nsStyleText *aStyleText)
{
const nsTextFragment *frag = aContent->GetText();
// This is an approximation so we don't really need anything
// too fancy here.
PRUint32 len;
if (aStyleText->WhiteSpaceIsSignificant()) {
len = frag->GetLength();
} else {
bool is2b = frag->Is2b();
union {
const char *s1b;
const PRUnichar *s2b;
} u;
if (is2b) {
u.s2b = frag->Get2b();
} else {
u.s1b = frag->Get1b();
}
bool prevWS = true; // more important to ignore blocks with
// only whitespace than get inline boundaries
// exactly right
len = 0;
for (PRUint32 i = 0, i_end = frag->GetLength(); i < i_end; ++i) {
PRUnichar c = is2b ? u.s2b[i] : u.s1b[i];
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
if (!prevWS) {
++len;
}
prevWS = true;
} else {
++len;
prevWS = false;
}
}
}
return len;
}
bool nsSkipCharsRunIterator::NextRun() {
do {
if (mRunLength) {

View File

@ -43,6 +43,9 @@
#include "gfxSkipChars.h"
#include "nsTextFragment.h"
class nsIContent;
struct nsStyleText;
#define BIG_TEXT_NODE_SIZE 4096
#define CH_NBSP 160
@ -147,6 +150,10 @@ public:
aArray->AppendElement(aOffset);
}
static PRUint32
ComputeApproximateLengthWithWhitespaceCompression(nsIContent *aContent,
const nsStyleText
*aStyleText);
};
class nsSkipCharsRunIterator {