HaikuDepot: ParagraphLayout: Alignment and justify support.
This commit is contained in:
@@ -263,6 +263,23 @@ ParagraphLayout::Draw(BView* view, const BPoint& offset)
|
|||||||
// #pragma mark - private
|
// #pragma mark - private
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ParagraphLayout::_Init()
|
||||||
|
{
|
||||||
|
fGlyphInfos.Clear();
|
||||||
|
|
||||||
|
int spanCount = fTextSpans.CountItems();
|
||||||
|
for (int i = 0; i < spanCount; i++) {
|
||||||
|
const TextSpan& span = fTextSpans.ItemAtFast(i);
|
||||||
|
if (!_AppendGlyphInfos(span)) {
|
||||||
|
fprintf(stderr, "%p->ParagraphLayout::_Init() - Out of memory\n",
|
||||||
|
this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
ParagraphLayout::_ValidateLayout()
|
ParagraphLayout::_ValidateLayout()
|
||||||
{
|
{
|
||||||
@@ -394,21 +411,151 @@ ParagraphLayout::_Layout()
|
|||||||
float lineHeight;
|
float lineHeight;
|
||||||
_FinalizeLine(lineStart, glyphCount - 1, lineIndex, y, lineHeight);
|
_FinalizeLine(lineStart, glyphCount - 1, lineIndex, y, lineHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ApplyAlignment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
ParagraphLayout::_Init()
|
ParagraphLayout::_ApplyAlignment()
|
||||||
{
|
{
|
||||||
fGlyphInfos.Clear();
|
Alignment alignment = fParagraphStyle.Alignment();
|
||||||
|
bool justify = fParagraphStyle.Justify();
|
||||||
|
|
||||||
int spanCount = fTextSpans.CountItems();
|
if (alignment == ALIGN_LEFT && !justify)
|
||||||
for (int i = 0; i < spanCount; i++) {
|
return;
|
||||||
const TextSpan& span = fTextSpans.ItemAtFast(i);
|
|
||||||
if (!_AppendGlyphInfos(span)) {
|
int glyphCount = fGlyphInfos.CountItems();
|
||||||
fprintf(stderr, "%p->ParagraphLayout::_Init() - Out of memory\n",
|
if (glyphCount == 0)
|
||||||
this);
|
return;
|
||||||
return;
|
|
||||||
|
int lineIndex = -1;
|
||||||
|
float spaceLeft = 0.0f;
|
||||||
|
float charSpace = 0.0f;
|
||||||
|
float whiteSpace = 0.0f;
|
||||||
|
bool seenChar = false;
|
||||||
|
|
||||||
|
// Iterate all glyphs backwards. On the last character of the next line,
|
||||||
|
// the position of the character determines the available space to be
|
||||||
|
// distributed (spaceLeft).
|
||||||
|
for (int i = glyphCount - 1; i >= 0; i--) {
|
||||||
|
GlyphInfo glyph = fGlyphInfos.ItemAtFast(i);
|
||||||
|
|
||||||
|
if (glyph.lineIndex != lineIndex) {
|
||||||
|
bool lineBreak = glyph.charCode == '\n' || i == glyphCount - 1;
|
||||||
|
lineIndex = glyph.lineIndex;
|
||||||
|
|
||||||
|
// The position of the last character determines the available
|
||||||
|
// space.
|
||||||
|
spaceLeft = fWidth - glyph.x;
|
||||||
|
|
||||||
|
// If the character is visible, the width of the character needs to
|
||||||
|
// be subtracted from the available space, otherwise it would be
|
||||||
|
// pushed outside the line.
|
||||||
|
uint32 charClassification = get_char_classification(glyph.charCode);
|
||||||
|
if (charClassification != CHAR_CLASS_WHITESPACE)
|
||||||
|
spaceLeft -= glyph.width;
|
||||||
|
|
||||||
|
charSpace = 0.0f;
|
||||||
|
whiteSpace = 0.0f;
|
||||||
|
seenChar = false;
|
||||||
|
|
||||||
|
if (lineBreak || !justify) {
|
||||||
|
if (alignment == ALIGN_CENTER)
|
||||||
|
spaceLeft /= 2.0f;
|
||||||
|
else if (alignment == ALIGN_LEFT)
|
||||||
|
spaceLeft = 0.0f;
|
||||||
|
} else {
|
||||||
|
// Figure out how much chars and white space chars are on the
|
||||||
|
// line. Don't count trailing white space.
|
||||||
|
int charCount = 0;
|
||||||
|
int spaceCount = 0;
|
||||||
|
for (int j = i; j >= 0; j--) {
|
||||||
|
const GlyphInfo& previousGlyph = fGlyphInfos.ItemAtFast(j);
|
||||||
|
if (previousGlyph.lineIndex != lineIndex) {
|
||||||
|
j++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint32 classification = get_char_classification(
|
||||||
|
previousGlyph.charCode);
|
||||||
|
if (classification == CHAR_CLASS_WHITESPACE) {
|
||||||
|
if (charCount > 0)
|
||||||
|
spaceCount++;
|
||||||
|
else if (j < i)
|
||||||
|
spaceLeft += glyph.width;
|
||||||
|
} else {
|
||||||
|
charCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first char is not shifted when justifying, so it doesn't
|
||||||
|
// contribute.
|
||||||
|
if (charCount > 0)
|
||||||
|
charCount--;
|
||||||
|
|
||||||
|
// Check if it looks better if both whitespace and chars get
|
||||||
|
// some space distributed, in case there are only 1 or two
|
||||||
|
// space chars on the line.
|
||||||
|
float spaceLeftForSpace = spaceLeft;
|
||||||
|
float spaceLeftForChars = spaceLeft;
|
||||||
|
|
||||||
|
if (spaceCount > 0) {
|
||||||
|
float spaceCharRatio = (float) spaceCount / charCount;
|
||||||
|
if (spaceCount < 3 && spaceCharRatio < 0.4f) {
|
||||||
|
spaceLeftForSpace = spaceLeft * 2.0f * spaceCharRatio;
|
||||||
|
spaceLeftForChars = spaceLeft - spaceLeftForSpace;
|
||||||
|
} else
|
||||||
|
spaceLeftForChars = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spaceCount > 0)
|
||||||
|
whiteSpace = spaceLeftForSpace / spaceCount;
|
||||||
|
if (charCount > 0)
|
||||||
|
charSpace = spaceLeftForChars / charCount;
|
||||||
|
|
||||||
|
LineInfo line = fLineInfos.ItemAtFast(lineIndex);
|
||||||
|
line.extraGlyphSpacing = charSpace;
|
||||||
|
line.extraWhiteSpacing = whiteSpace;
|
||||||
|
|
||||||
|
fLineInfos.Replace(lineIndex, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each character is pushed towards the right by the space that is
|
||||||
|
// still available. When justification is performed, the shift is
|
||||||
|
// gradually decreased. This works since the iteration is backwards
|
||||||
|
// and the characters on the right are pushed farthest.
|
||||||
|
glyph.x += spaceLeft;
|
||||||
|
|
||||||
|
unsigned classification = get_char_classification(glyph.charCode);
|
||||||
|
|
||||||
|
if (i < glyphCount - 1) {
|
||||||
|
GlyphInfo nextGlyph = fGlyphInfos.ItemAtFast(i + 1);
|
||||||
|
if (nextGlyph.lineIndex == lineIndex) {
|
||||||
|
uint32 nextClassification
|
||||||
|
= get_char_classification(nextGlyph.charCode);
|
||||||
|
if (nextClassification == CHAR_CLASS_WHITESPACE
|
||||||
|
&& classification != CHAR_CLASS_WHITESPACE) {
|
||||||
|
// When a space character is right of a regular character,
|
||||||
|
// add the additional space to the space instead of the
|
||||||
|
// character
|
||||||
|
float shift = (nextGlyph.x - glyph.x) - glyph.width;
|
||||||
|
nextGlyph.x -= shift;
|
||||||
|
fGlyphInfos.Replace(i + 1, nextGlyph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fGlyphInfos.Replace(i, glyph);
|
||||||
|
|
||||||
|
// The shift (spaceLeft) is reduced depending on the character
|
||||||
|
// classification.
|
||||||
|
if (classification == CHAR_CLASS_WHITESPACE) {
|
||||||
|
if (seenChar)
|
||||||
|
spaceLeft -= whiteSpace;
|
||||||
|
} else {
|
||||||
|
seenChar = true;
|
||||||
|
spaceLeft -= charSpace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,7 +702,13 @@ ParagraphLayout::_DrawSpan(BView* view, const TextSpan& span,
|
|||||||
view->SetHighColor(style.ForegroundColor());
|
view->SetHighColor(style.ForegroundColor());
|
||||||
|
|
||||||
// TODO: Implement other style properties
|
// TODO: Implement other style properties
|
||||||
// TODO: Implement correct glyph spacing
|
|
||||||
|
|
||||||
view->DrawString(span.Text(), BPoint(x, y));
|
escapement_delta delta;
|
||||||
|
delta.nonspace = line.extraGlyphSpacing;
|
||||||
|
delta.space = line.extraWhiteSpacing;
|
||||||
|
|
||||||
|
// TODO: Fix in app_server: First glyph should not be shifted by delta.
|
||||||
|
x -= delta.nonspace;
|
||||||
|
|
||||||
|
view->DrawString(span.Text(), BPoint(x, y), &delta);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ public:
|
|||||||
height(0.0f),
|
height(0.0f),
|
||||||
maxAscent(0.0f),
|
maxAscent(0.0f),
|
||||||
maxDescent(0.0f),
|
maxDescent(0.0f),
|
||||||
|
extraGlyphSpacing(0.0f),
|
||||||
|
extraWhiteSpacing(0.0f),
|
||||||
layoutedSpans()
|
layoutedSpans()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -101,6 +103,8 @@ public:
|
|||||||
height(height),
|
height(height),
|
||||||
maxAscent(maxAscent),
|
maxAscent(maxAscent),
|
||||||
maxDescent(maxDescent),
|
maxDescent(maxDescent),
|
||||||
|
extraGlyphSpacing(0.0f),
|
||||||
|
extraWhiteSpacing(0.0f),
|
||||||
layoutedSpans()
|
layoutedSpans()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -112,6 +116,8 @@ public:
|
|||||||
height(other.height),
|
height(other.height),
|
||||||
maxAscent(other.maxAscent),
|
maxAscent(other.maxAscent),
|
||||||
maxDescent(other.maxDescent),
|
maxDescent(other.maxDescent),
|
||||||
|
extraGlyphSpacing(other.extraGlyphSpacing),
|
||||||
|
extraWhiteSpacing(other.extraWhiteSpacing),
|
||||||
layoutedSpans(other.layoutedSpans)
|
layoutedSpans(other.layoutedSpans)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -123,6 +129,8 @@ public:
|
|||||||
height = other.height;
|
height = other.height;
|
||||||
maxAscent = other.maxAscent;
|
maxAscent = other.maxAscent;
|
||||||
maxDescent = other.maxDescent;
|
maxDescent = other.maxDescent;
|
||||||
|
extraGlyphSpacing = other.extraGlyphSpacing;
|
||||||
|
extraWhiteSpacing = other.extraWhiteSpacing;
|
||||||
layoutedSpans = other.layoutedSpans;
|
layoutedSpans = other.layoutedSpans;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -134,6 +142,8 @@ public:
|
|||||||
&& height == other.height
|
&& height == other.height
|
||||||
&& maxAscent == other.maxAscent
|
&& maxAscent == other.maxAscent
|
||||||
&& maxDescent == other.maxDescent
|
&& maxDescent == other.maxDescent
|
||||||
|
&& extraGlyphSpacing == other.extraGlyphSpacing
|
||||||
|
&& extraWhiteSpacing == other.extraWhiteSpacing
|
||||||
&& layoutedSpans == other.layoutedSpans;
|
&& layoutedSpans == other.layoutedSpans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +154,16 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
int32 textOffset;
|
int32 textOffset;
|
||||||
|
|
||||||
float y;
|
float y;
|
||||||
float height;
|
float height;
|
||||||
|
|
||||||
float maxAscent;
|
float maxAscent;
|
||||||
float maxDescent;
|
float maxDescent;
|
||||||
|
|
||||||
|
float extraGlyphSpacing;
|
||||||
|
float extraWhiteSpacing;
|
||||||
|
|
||||||
TextSpanList layoutedSpans;
|
TextSpanList layoutedSpans;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -173,10 +188,12 @@ public:
|
|||||||
void Draw(BView* view, const BPoint& offset);
|
void Draw(BView* view, const BPoint& offset);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void _Init();
|
||||||
|
|
||||||
void _ValidateLayout();
|
void _ValidateLayout();
|
||||||
void _Layout();
|
void _Layout();
|
||||||
|
void _ApplyAlignment();
|
||||||
|
|
||||||
void _Init();
|
|
||||||
bool _AppendGlyphInfos(const TextSpan& span);
|
bool _AppendGlyphInfos(const TextSpan& span);
|
||||||
bool _AppendGlyphInfo(uint32 charCode,
|
bool _AppendGlyphInfo(uint32 charCode,
|
||||||
float advanceX,
|
float advanceX,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ TextDocumentTest::ReadyToRun()
|
|||||||
;
|
;
|
||||||
|
|
||||||
ParagraphStyle paragraphStyle;
|
ParagraphStyle paragraphStyle;
|
||||||
|
paragraphStyle.SetJustify(true);
|
||||||
|
|
||||||
CharacterStyle regularStyle;
|
CharacterStyle regularStyle;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user