2 * Copyright 2001-2013, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * Stephan Aßmus, superstippi@gmx.de
7 * Stefano Ceccherini, stefano.ceccherini@gmail.com
8 * Marc Flerackers, mflerackers@androme.be
9 * Hiroshi Lockheimer (BTextView is based on his STEEngine)
10 * Oliver Tappe, zooey@hirschkaefer.de
13 #include "ParagraphLayout.h"
18 #include <AutoDeleter.h>
19 #include <utf8_functions.h>
25 CHAR_CLASS_WHITESPACE
,
28 CHAR_CLASS_PUNCTUATION
,
29 CHAR_CLASS_PARENS_OPEN
,
30 CHAR_CLASS_PARENS_CLOSE
,
31 CHAR_CLASS_END_OF_TEXT
36 get_char_classification(uint32 charCode
)
38 // TODO: Should check against a list of characters containing also
39 // word breakers from other languages.
43 return CHAR_CLASS_END_OF_TEXT
;
48 return CHAR_CLASS_WHITESPACE
;
65 return CHAR_CLASS_GRAPHICAL
;
69 return CHAR_CLASS_QUOTE
;
78 return CHAR_CLASS_PUNCTUATION
;
83 return CHAR_CLASS_PARENS_OPEN
;
88 return CHAR_CLASS_PARENS_CLOSE
;
91 return CHAR_CLASS_DEFAULT
;
97 can_end_line(const GlyphInfoList
& glyphInfos
, int offset
)
99 int count
= glyphInfos
.CountItems();
101 if (offset
== count
- 1)
104 if (offset
< 0 || offset
> count
)
107 uint32 charCode
= glyphInfos
.ItemAtFast(offset
).charCode
;
108 uint32 classification
= get_char_classification(charCode
);
110 // wrapping is always allowed at end of text and at newlines
111 if (classification
== CHAR_CLASS_END_OF_TEXT
|| charCode
== '\n')
114 uint32 nextCharCode
= glyphInfos
.ItemAtFast(offset
+ 1).charCode
;
115 uint32 nextClassification
= get_char_classification(nextCharCode
);
117 // never separate a punctuation char from its preceding word
118 if (classification
== CHAR_CLASS_DEFAULT
119 && nextClassification
== CHAR_CLASS_PUNCTUATION
) {
123 if ((classification
== CHAR_CLASS_WHITESPACE
124 && nextClassification
!= CHAR_CLASS_WHITESPACE
)
125 || (classification
!= CHAR_CLASS_WHITESPACE
126 && nextClassification
== CHAR_CLASS_WHITESPACE
)) {
130 // allow wrapping after whitespace, unless more whitespace (except for
132 if (classification
== CHAR_CLASS_WHITESPACE
133 && (nextClassification
!= CHAR_CLASS_WHITESPACE
134 || nextCharCode
== '\n')) {
138 // allow wrapping after punctuation chars, unless more punctuation, closing
139 // parenthesis or quotes follow
140 if (classification
== CHAR_CLASS_PUNCTUATION
141 && nextClassification
!= CHAR_CLASS_PUNCTUATION
142 && nextClassification
!= CHAR_CLASS_PARENS_CLOSE
143 && nextClassification
!= CHAR_CLASS_QUOTE
) {
147 // allow wrapping after quotes, graphical chars and closing parenthesis only
148 // if whitespace follows (not perfect, but seems to do the right thing most
150 if ((classification
== CHAR_CLASS_QUOTE
151 || classification
== CHAR_CLASS_GRAPHICAL
152 || classification
== CHAR_CLASS_PARENS_CLOSE
)
153 && nextClassification
== CHAR_CLASS_WHITESPACE
) {
161 // #pragma mark - ParagraphLayout
164 ParagraphLayout::ParagraphLayout()
178 ParagraphLayout::ParagraphLayout(const Paragraph
& paragraph
)
180 fTextSpans(paragraph
.TextSpans()),
181 fParagraphStyle(paragraph
.Style()),
193 ParagraphLayout::ParagraphLayout(const ParagraphLayout
& other
)
195 fTextSpans(other
.fTextSpans
),
196 fParagraphStyle(other
.fParagraphStyle
),
198 fWidth(other
.fWidth
),
201 fGlyphInfos(other
.fGlyphInfos
),
207 ParagraphLayout::~ParagraphLayout()
213 ParagraphLayout::SetParagraph(const Paragraph
& paragraph
)
215 fTextSpans
= paragraph
.TextSpans();
216 fParagraphStyle
= paragraph
.Style();
220 fLayoutValid
= false;
225 ParagraphLayout::SetWidth(float width
)
227 if (fWidth
!= width
) {
229 fLayoutValid
= false;
235 ParagraphLayout::Height()
241 if (fLineInfos
.CountItems() > 0) {
242 const LineInfo
& lastLine
= fLineInfos
.LastItem();
243 height
= lastLine
.y
+ lastLine
.height
;
251 ParagraphLayout::Draw(BView
* view
, const BPoint
& offset
)
255 int lineCount
= fLineInfos
.CountItems();
256 for (int i
= 0; i
< lineCount
; i
++) {
257 const LineInfo
& line
= fLineInfos
.ItemAtFast(i
);
258 _DrawLine(view
, offset
, line
);
261 const Bullet
& bullet
= fParagraphStyle
.Bullet();
262 if (bullet
.Spacing() > 0.0f
&& bullet
.String().Length() > 0) {
263 // Draw bullet at offset
264 view
->SetHighUIColor(B_PANEL_TEXT_COLOR
);
265 BPoint
bulletPos(offset
);
266 bulletPos
.x
+= fParagraphStyle
.FirstLineInset()
267 + fParagraphStyle
.LineInset();
268 bulletPos
.y
+= fLineInfos
.ItemAt(0).maxAscent
;
269 view
->DrawString(bullet
.String(), bulletPos
);
275 ParagraphLayout::CountGlyphs() const
277 return fGlyphInfos
.CountItems();
282 ParagraphLayout::CountLines()
285 return fLineInfos
.CountItems();
290 ParagraphLayout::LineIndexForOffset(int32 textOffset
)
294 if (fGlyphInfos
.CountItems() == 0)
297 if (textOffset
>= fGlyphInfos
.CountItems()) {
298 const GlyphInfo
& glyph
= fGlyphInfos
.LastItem();
299 return glyph
.lineIndex
;
305 const GlyphInfo
& glyph
= fGlyphInfos
.ItemAtFast(textOffset
);
306 return glyph
.lineIndex
;
311 ParagraphLayout::FirstOffsetOnLine(int32 lineIndex
)
317 if (lineIndex
>= fLineInfos
.CountItems())
318 lineIndex
= fLineInfos
.CountItems() - 1;
320 return fLineInfos
.ItemAt(lineIndex
).textOffset
;
325 ParagraphLayout::LastOffsetOnLine(int32 lineIndex
)
332 if (lineIndex
>= fLineInfos
.CountItems() - 1)
333 return CountGlyphs() - 1;
335 return fLineInfos
.ItemAt(lineIndex
+ 1).textOffset
- 1;
340 ParagraphLayout::GetLineBounds(int32 lineIndex
, float& x1
, float& y1
,
341 float& x2
, float& y2
)
345 if (fGlyphInfos
.CountItems() == 0) {
346 _GetEmptyLayoutBounds(x1
, y1
, x2
, y2
);
352 if (lineIndex
>= fLineInfos
.CountItems())
353 lineIndex
= fLineInfos
.CountItems() - 1;
355 const LineInfo
& lineInfo
= fLineInfos
.ItemAt(lineIndex
);
356 int32 firstGlyphIndex
= lineInfo
.textOffset
;
358 int32 lastGlyphIndex
;
359 if (lineIndex
< fLineInfos
.CountItems() - 1)
360 lastGlyphIndex
= fLineInfos
.ItemAt(lineIndex
+ 1).textOffset
- 1;
362 lastGlyphIndex
= fGlyphInfos
.CountItems() - 1;
364 const GlyphInfo
& firstInfo
= fGlyphInfos
.ItemAtFast(firstGlyphIndex
);
365 const GlyphInfo
& lastInfo
= fGlyphInfos
.ItemAtFast(lastGlyphIndex
);
369 x2
= lastInfo
.x
+ lastInfo
.width
;
370 y2
= lineInfo
.y
+ lineInfo
.height
;
375 ParagraphLayout::GetTextBounds(int32 textOffset
, float& x1
, float& y1
,
376 float& x2
, float& y2
)
380 if (fGlyphInfos
.CountItems() == 0) {
381 _GetEmptyLayoutBounds(x1
, y1
, x2
, y2
);
385 if (textOffset
>= fGlyphInfos
.CountItems()) {
386 const GlyphInfo
& glyph
= fGlyphInfos
.LastItem();
387 const LineInfo
& line
= fLineInfos
.ItemAt(glyph
.lineIndex
);
389 x1
= glyph
.x
+ glyph
.width
;
392 y2
= y1
+ line
.height
;
400 const GlyphInfo
& glyph
= fGlyphInfos
.ItemAtFast(textOffset
);
401 const LineInfo
& line
= fLineInfos
.ItemAt(glyph
.lineIndex
);
404 x2
= x1
+ glyph
.width
;
406 y2
= y1
+ line
.height
;
411 ParagraphLayout::TextOffsetAt(float x
, float y
, bool& rightOfCenter
)
415 rightOfCenter
= false;
417 int32 lineCount
= fLineInfos
.CountItems();
418 if (fGlyphInfos
.CountItems() == 0 || lineCount
== 0
419 || fLineInfos
.ItemAtFast(0).y
> y
) {
420 // Above first line or empty text
425 if (floorf(fLineInfos
.LastItem().y
426 + fLineInfos
.LastItem().height
+ 0.5) > y
) {
427 // TODO: Optimize, can binary search line here:
428 for (; lineIndex
< lineCount
; lineIndex
++) {
429 const LineInfo
& line
= fLineInfos
.ItemAtFast(lineIndex
);
430 float lineBottom
= floorf(line
.y
+ line
.height
+ 0.5);
435 lineIndex
= lineCount
- 1;
439 const LineInfo
& line
= fLineInfos
.ItemAtFast(lineIndex
);
440 int32 textOffset
= line
.textOffset
;
442 if (lineIndex
< lineCount
- 1)
443 end
= fLineInfos
.ItemAtFast(lineIndex
+ 1).textOffset
- 1;
445 end
= fGlyphInfos
.CountItems() - 1;
447 // TODO: Optimize, can binary search offset here:
448 for (; textOffset
<= end
; textOffset
++) {
449 const GlyphInfo
& glyph
= fGlyphInfos
.ItemAtFast(textOffset
);
454 // x2 is the location at the right bounding box of the glyph
455 float x2
= x1
+ glyph
.width
;
457 // x3 is the location of the next glyph, which may be different from
458 // x2 in case the line is justified.
460 if (textOffset
< end
- 1)
461 x3
= fGlyphInfos
.ItemAtFast(textOffset
+ 1).x
;
466 rightOfCenter
= x
> (x1
+ x2
) / 2.0f
;
471 // Account for trailing line break at end of line, the
472 // returned offset should be before that.
473 rightOfCenter
= fGlyphInfos
.ItemAtFast(end
).charCode
!= '\n';
479 // #pragma mark - private
483 ParagraphLayout::_Init()
487 int spanCount
= fTextSpans
.CountItems();
488 for (int i
= 0; i
< spanCount
; i
++) {
489 const TextSpan
& span
= fTextSpans
.ItemAtFast(i
);
490 if (!_AppendGlyphInfos(span
)) {
491 fprintf(stderr
, "%p->ParagraphLayout::_Init() - Out of memory\n",
500 ParagraphLayout::_ValidateLayout()
510 ParagraphLayout::_Layout()
514 const Bullet
& bullet
= fParagraphStyle
.Bullet();
516 float x
= fParagraphStyle
.LineInset() + fParagraphStyle
.FirstLineInset()
522 int glyphCount
= fGlyphInfos
.CountItems();
523 for (int i
= 0; i
< glyphCount
; i
++) {
524 GlyphInfo glyph
= fGlyphInfos
.ItemAtFast(i
);
526 uint32 charClassification
= get_char_classification(glyph
.charCode
);
528 float advanceX
= glyph
.width
;
529 float advanceY
= 0.0f
;
531 bool nextLine
= false;
532 bool lineBreak
= false;
534 // if (glyph.charCode == '\t') {
535 // // Figure out tab width, it's the width between the last two tab
537 // float tabWidth = 0.0f;
538 // if (fTabCount > 0)
539 // tabWidth = fTabBuffer[fTabCount - 1];
540 // if (fTabCount > 1)
541 // tabWidth -= fTabBuffer[fTabCount - 2];
543 // // Try to find a tab stop that is farther than the current x
545 // double tabOffset = 0.0;
546 // for (unsigned tabIndex = 0; tabIndex < fTabCount; tabIndex++) {
547 // tabOffset = fTabBuffer[tabIndex];
548 // if (tabOffset > x)
552 // // If no tab stop has been found, make the tab stop a multiple of
554 // if (tabOffset <= x && tabWidth > 0.0)
555 // tabOffset = ((int) (x / tabWidth) + 1) * tabWidth;
557 // if (tabOffset - x > 0.0)
558 // advanceX = tabOffset - x;
561 if (glyph
.charCode
== '\n') {
565 fGlyphInfos
.Replace(i
, glyph
);
566 } else if (fWidth
> 0.0f
&& x
+ advanceX
> fWidth
) {
567 fGlyphInfos
.Replace(i
, glyph
);
568 if (charClassification
== CHAR_CLASS_WHITESPACE
) {
570 } else if (i
> lineStart
) {
572 // The current glyph extends outside the width, we need to wrap
573 // to the next line. See what previous offset can be the end
576 while (lineEnd
> lineStart
577 && !can_end_line(fGlyphInfos
, lineEnd
)) {
581 if (lineEnd
> lineStart
) {
582 // Found a place to perform a line break.
585 // Adjust the glyph info to point at the changed buffer
587 glyph
= fGlyphInfos
.ItemAtFast(i
);
588 advanceX
= glyph
.width
;
590 // Just break where we are.
596 // * Initialize the max ascent/descent of all preceding glyph infos
597 // on the current/last line
598 // * Adjust the baseline offset according to the max ascent
599 // * Fill in the line index.
606 float lineHeight
= 0.0;
607 _FinalizeLine(lineStart
, lineEnd
, lineIndex
, y
, lineHeight
);
609 // Start position of the next line
610 x
= fParagraphStyle
.LineInset() + bullet
.Spacing();
611 y
+= lineHeight
+ fParagraphStyle
.LineSpacing();
621 if (!lineBreak
&& i
< glyphCount
) {
623 fGlyphInfos
.Replace(i
, glyph
);
630 // The last line may not have been appended and initialized yet.
631 if (lineStart
<= glyphCount
- 1 || glyphCount
== 0) {
633 _FinalizeLine(lineStart
, glyphCount
- 1, lineIndex
, y
, lineHeight
);
641 ParagraphLayout::_ApplyAlignment()
643 Alignment alignment
= fParagraphStyle
.Alignment();
644 bool justify
= fParagraphStyle
.Justify();
646 if (alignment
== ALIGN_LEFT
&& !justify
)
649 int glyphCount
= fGlyphInfos
.CountItems();
654 float spaceLeft
= 0.0f
;
655 float charSpace
= 0.0f
;
656 float whiteSpace
= 0.0f
;
657 bool seenChar
= false;
659 // Iterate all glyphs backwards. On the last character of the next line,
660 // the position of the character determines the available space to be
661 // distributed (spaceLeft).
662 for (int i
= glyphCount
- 1; i
>= 0; i
--) {
663 GlyphInfo glyph
= fGlyphInfos
.ItemAtFast(i
);
665 if (glyph
.lineIndex
!= lineIndex
) {
666 bool lineBreak
= glyph
.charCode
== '\n' || i
== glyphCount
- 1;
667 lineIndex
= glyph
.lineIndex
;
669 // The position of the last character determines the available
671 spaceLeft
= fWidth
- glyph
.x
;
673 // If the character is visible, the width of the character needs to
674 // be subtracted from the available space, otherwise it would be
675 // pushed outside the line.
676 uint32 charClassification
= get_char_classification(glyph
.charCode
);
677 if (charClassification
!= CHAR_CLASS_WHITESPACE
)
678 spaceLeft
-= glyph
.width
;
684 if (lineBreak
|| !justify
) {
685 if (alignment
== ALIGN_CENTER
)
687 else if (alignment
== ALIGN_LEFT
)
690 // Figure out how much chars and white space chars are on the
691 // line. Don't count trailing white space.
694 for (int j
= i
; j
>= 0; j
--) {
695 const GlyphInfo
& previousGlyph
= fGlyphInfos
.ItemAtFast(j
);
696 if (previousGlyph
.lineIndex
!= lineIndex
) {
700 uint32 classification
= get_char_classification(
701 previousGlyph
.charCode
);
702 if (classification
== CHAR_CLASS_WHITESPACE
) {
706 spaceLeft
+= glyph
.width
;
712 // The first char is not shifted when justifying, so it doesn't
717 // Check if it looks better if both whitespace and chars get
718 // some space distributed, in case there are only 1 or two
719 // space chars on the line.
720 float spaceLeftForSpace
= spaceLeft
;
721 float spaceLeftForChars
= spaceLeft
;
723 if (spaceCount
> 0) {
724 float spaceCharRatio
= (float) spaceCount
/ charCount
;
725 if (spaceCount
< 3 && spaceCharRatio
< 0.4f
) {
726 spaceLeftForSpace
= spaceLeft
* 2.0f
* spaceCharRatio
;
727 spaceLeftForChars
= spaceLeft
- spaceLeftForSpace
;
729 spaceLeftForChars
= 0.0f
;
733 whiteSpace
= spaceLeftForSpace
/ spaceCount
;
735 charSpace
= spaceLeftForChars
/ charCount
;
737 LineInfo line
= fLineInfos
.ItemAtFast(lineIndex
);
738 line
.extraGlyphSpacing
= charSpace
;
739 line
.extraWhiteSpacing
= whiteSpace
;
741 fLineInfos
.Replace(lineIndex
, line
);
745 // Each character is pushed towards the right by the space that is
746 // still available. When justification is performed, the shift is
747 // gradually decreased. This works since the iteration is backwards
748 // and the characters on the right are pushed farthest.
749 glyph
.x
+= spaceLeft
;
751 unsigned classification
= get_char_classification(glyph
.charCode
);
753 if (i
< glyphCount
- 1) {
754 GlyphInfo nextGlyph
= fGlyphInfos
.ItemAtFast(i
+ 1);
755 if (nextGlyph
.lineIndex
== lineIndex
) {
756 uint32 nextClassification
757 = get_char_classification(nextGlyph
.charCode
);
758 if (nextClassification
== CHAR_CLASS_WHITESPACE
759 && classification
!= CHAR_CLASS_WHITESPACE
) {
760 // When a space character is right of a regular character,
761 // add the additional space to the space instead of the
763 float shift
= (nextGlyph
.x
- glyph
.x
) - glyph
.width
;
764 nextGlyph
.x
-= shift
;
765 fGlyphInfos
.Replace(i
+ 1, nextGlyph
);
770 fGlyphInfos
.Replace(i
, glyph
);
772 // The shift (spaceLeft) is reduced depending on the character
774 if (classification
== CHAR_CLASS_WHITESPACE
) {
776 spaceLeft
-= whiteSpace
;
779 spaceLeft
-= charSpace
;
786 ParagraphLayout::_AppendGlyphInfos(const TextSpan
& span
)
788 int charCount
= span
.CountChars();
792 const BString
& text
= span
.Text();
793 const BFont
& font
= span
.Style().Font();
796 float* escapementArray
= new (std::nothrow
) float[charCount
];
797 if (escapementArray
== NULL
)
799 ArrayDeleter
<float> escapementDeleter(escapementArray
);
801 // Fetch glyph spacing information
802 font
.GetEscapements(text
, charCount
, escapementArray
);
804 // Append to glyph buffer and convert escapement scale
805 float size
= font
.Size();
806 const char* c
= text
.String();
807 for (int i
= 0; i
< charCount
; i
++) {
808 if (!_AppendGlyphInfo(UTF8ToCharCode(&c
), escapementArray
[i
] * size
,
819 ParagraphLayout::_AppendGlyphInfo(uint32 charCode
, float width
,
820 const CharacterStyle
& style
)
822 if (style
.Width() >= 0.0f
) {
823 // Use the metrics provided by the CharacterStyle and override
824 // the font provided metrics passed in "width"
825 width
= style
.Width();
828 width
+= style
.GlyphSpacing();
830 return fGlyphInfos
.Add(GlyphInfo(charCode
, 0.0f
, width
, 0));
835 ParagraphLayout::_FinalizeLine(int lineStart
, int lineEnd
, int lineIndex
,
836 float y
, float& lineHeight
)
838 LineInfo
line(lineStart
, y
, 0.0f
, 0.0f
, 0.0f
);
844 for (int i
= lineStart
; i
<= lineEnd
; i
++) {
845 // Mark line index in glyph
846 GlyphInfo glyph
= fGlyphInfos
.ItemAtFast(i
);
847 glyph
.lineIndex
= lineIndex
;
848 fGlyphInfos
.Replace(i
, glyph
);
850 // See if the next sub-span needs to be added to the LineInfo
851 bool addSpan
= false;
853 while (i
>= spanEnd
) {
855 const TextSpan
& span
= fTextSpans
.ItemAt(spanIndex
);
857 spanEnd
+= span
.CountChars();
862 const TextSpan
& span
= fTextSpans
.ItemAt(spanIndex
);
863 TextSpan subSpan
= span
.SubSpan(i
- spanStart
,
864 (lineEnd
- spanStart
+ 1) - (i
- spanStart
));
865 line
.layoutedSpans
.Add(subSpan
);
866 _IncludeStyleInLine(line
, span
.Style());
870 if (fGlyphInfos
.CountItems() == 0 && fTextSpans
.CountItems() > 0) {
871 // When the layout contains no glyphs, but there is at least one
872 // TextSpan in the paragraph, use the font info from that span
873 // to calculate the height of the first LineInfo.
874 const TextSpan
& span
= fTextSpans
.ItemAtFast(0);
875 line
.layoutedSpans
.Add(span
);
876 _IncludeStyleInLine(line
, span
.Style());
879 lineHeight
= line
.height
;
881 return fLineInfos
.Add(line
);
886 ParagraphLayout::_IncludeStyleInLine(LineInfo
& line
,
887 const CharacterStyle
& style
)
889 float ascent
= style
.Ascent();
890 if (ascent
> line
.maxAscent
)
891 line
.maxAscent
= ascent
;
893 float descent
= style
.Descent();
894 if (descent
> line
.maxDescent
)
895 line
.maxDescent
= descent
;
897 float height
= ascent
+ descent
;
898 if (style
.Font().Size() > height
)
899 height
= style
.Font().Size();
901 if (height
> line
.height
)
902 line
.height
= height
;
907 ParagraphLayout::_DrawLine(BView
* view
, const BPoint
& offset
,
908 const LineInfo
& line
) const
910 int textOffset
= line
.textOffset
;
911 int spanCount
= line
.layoutedSpans
.CountItems();
912 for (int i
= 0; i
< spanCount
; i
++) {
913 const TextSpan
& span
= line
.layoutedSpans
.ItemAtFast(i
);
914 _DrawSpan(view
, offset
, span
, textOffset
);
915 textOffset
+= span
.CountChars();
921 ParagraphLayout::_DrawSpan(BView
* view
, BPoint offset
,
922 const TextSpan
& span
, int32 textOffset
) const
924 const BString
& text
= span
.Text();
925 if (text
.Length() == 0)
928 const GlyphInfo
& glyph
= fGlyphInfos
.ItemAtFast(textOffset
);
929 const LineInfo
& line
= fLineInfos
.ItemAtFast(glyph
.lineIndex
);
932 offset
.y
+= line
.y
+ line
.maxAscent
;
934 const CharacterStyle
& style
= span
.Style();
936 view
->SetFont(&style
.Font());
938 if (style
.WhichForegroundColor() != B_NO_COLOR
)
939 view
->SetHighUIColor(style
.WhichForegroundColor());
941 view
->SetHighColor(style
.ForegroundColor());
943 // TODO: Implement other style properties
945 escapement_delta delta
;
946 delta
.nonspace
= line
.extraGlyphSpacing
;
947 delta
.space
= line
.extraWhiteSpacing
;
949 view
->DrawString(span
.Text(), offset
, &delta
);
954 ParagraphLayout::_GetEmptyLayoutBounds(float& x1
, float& y1
, float& x2
,
957 if (fLineInfos
.CountItems() == 0) {
966 // If the paragraph had at least a single empty TextSpan, the layout
967 // can compute some meaningful bounds.
968 const Bullet
& bullet
= fParagraphStyle
.Bullet();
969 x1
= fParagraphStyle
.LineInset() + fParagraphStyle
.FirstLineInset()
972 const LineInfo
& lineInfo
= fLineInfos
.ItemAt(0);
974 y2
= lineInfo
.y
+ lineInfo
.height
;