2 * (C) Copyright IBM Corp. 1998-2007. All Rights Reserved.
\r
4 * The program is provided "as is" without any warranty express or
\r
5 * implied, including the warranty of non-infringement and the implied
\r
6 * warranties of merchantibility and fitness for a particular purpose.
\r
7 * IBM will not be liable for any damages suffered by you as a result
\r
8 * of using the Program. In no event will IBM be liable for any
\r
9 * special, indirect or consequential damages or lost profits even if
\r
10 * IBM has been advised of the possibility of their occurrence. IBM
\r
11 * will not be liable for any third party claims against you.
\r
14 package com.ibm.richtext.textformat;
\r
16 import java.awt.Color;
\r
17 import java.awt.Rectangle;
\r
18 import java.awt.Shape;
\r
20 import java.util.Vector;
\r
22 import com.ibm.richtext.styledtext.MConstText;
\r
23 import com.ibm.richtext.styledtext.MTabRuler;
\r
24 import com.ibm.richtext.styledtext.TabStop;
\r
26 import com.ibm.richtext.textlayout.attributes.AttributeMap;
\r
27 import com.ibm.richtext.textlayout.attributes.TextAttribute;
\r
29 import com.ibm.richtext.textlayout.Graphics2DConversion;
\r
32 import java.awt.Graphics2D;
\r
34 import java.awt.font.FontRenderContext;
\r
35 import java.awt.font.TextLayout;
\r
36 import java.awt.font.LineBreakMeasurer;
\r
37 import java.awt.font.TextHitInfo;
\r
39 import java.awt.geom.AffineTransform;
\r
40 import java.awt.geom.GeneralPath;
\r
41 import java.awt.geom.Rectangle2D;
\r
44 import com.ibm.richtext.textlayout.Graphics2D;
\r
46 import com.ibm.richtext.textlayout.FontRenderContext;
\r
47 import com.ibm.richtext.textlayout.TextLayout;
\r
48 import com.ibm.richtext.textlayout.LineBreakMeasurer;
\r
49 import com.ibm.richtext.textlayout.TextHitInfo;
\r
51 import com.ibm.richtext.textlayout.AffineTransform;
\r
52 import com.ibm.richtext.textlayout.GeneralPath;
\r
53 import com.ibm.richtext.textlayout.Rectangle2D;
\r
56 final class BidiParagraphRenderer extends ParagraphRenderer {
\r
58 static final String COPYRIGHT =
\r
59 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
\r
60 private final class BidiSegment {
\r
62 Rectangle2D.Float fBounds;
\r
63 int fDistanceFromLeadingMargin;
\r
66 private final class BidiLayoutInfo extends LayoutInfo
\r
68 int fCharLength; // number of characters on line (was fLength)
\r
72 int fVisibleAdvance; // distance along line direction ie width
\r
73 int fTotalAdvance; // distance along line direction including trailing whitespace
\r
75 int fLeadingMargin; // screen distance from leading margin
\r
77 boolean fLeftToRight; // true iff the orientation is left-to-right
\r
79 final Vector fSegments = new Vector(); // segments to render, in logical order
\r
81 public int getCharLength() {
\r
85 public int getAscent() {
\r
89 public int getDescent() {
\r
93 public int getLeading() {
\r
97 public int getVisibleAdvance() {
\r
98 return fVisibleAdvance;
\r
101 public int getTotalAdvance() {
\r
102 return fTotalAdvance;
\r
105 public int getLeadingMargin() {
\r
106 return fLeadingMargin;
\r
109 public boolean isLeftToRight() {
\r
110 return fLeftToRight;
\r
113 public int getHeight() {
\r
114 return fAscent + fDescent + fLeading;
\r
117 public String toString()
\r
119 return "LayoutInfo(charStart: " + getCharStart(0) +
\r
120 ", fCharLength: " + fCharLength +
\r
121 ", fAscent: " + fAscent +
\r
122 ", fDescent: " + fDescent +
\r
123 ", fVisibleAdvance: " + fVisibleAdvance +
\r
124 ", fTotalAdvance: " + fTotalAdvance +
\r
125 ", fLeadingMargin: " + fLeadingMargin +
\r
129 BidiParagraphRenderer fRenderer;
\r
131 // just delegate to renderer for now
\r
133 public void renderWithHighlight(int lengthBasis,
\r
138 TextOffset selStart,
\r
139 TextOffset selStop,
\r
140 Color highlightColor) {
\r
142 fRenderer.renderWithHighlight(this,
\r
153 public void render(int lengthBasis,
\r
158 fRenderer.render(this, lengthBasis, g, lineBound, x, y);
\r
161 public void renderCaret(MConstText text,
\r
168 Color strongCaretColor,
\r
169 Color weakCaretColor) {
\r
170 fRenderer.renderCaret(this, text, lengthBasis, g, lineBound, x, y, charOffset,
\r
171 strongCaretColor, weakCaretColor);
\r
174 public TextOffset pixelToOffset(int lengthBasis,
\r
179 return fRenderer.pixelToOffset(this, lengthBasis, result, lineBound, x, y);
\r
182 public Rectangle caretBounds(MConstText text,
\r
188 return fRenderer.caretBounds(this, text, lengthBasis, lineBound, charOffset, x, y);
\r
191 public int strongCaretBaselinePosition(int lengthBasis,
\r
195 return fRenderer.strongCaretBaselinePosition(this, lengthBasis, lineBound, charOffset);
\r
198 public int getNextOffset(int lengthBasis,
\r
202 return fRenderer.getNextOffset(this, lengthBasis, charOffset, dir);
\r
206 //private static final int FLUSH_LEADING = TextAttribute.FLUSH_LEADING.intValue();
\r
207 private static final int FLUSH_CENTER = TextAttribute.FLUSH_CENTER.intValue();
\r
208 private static final int FLUSH_TRAILING = TextAttribute.FLUSH_TRAILING.intValue();
\r
209 private static final int FULLY_JUSTIFIED = TextAttribute.FULLY_JUSTIFIED.intValue();
\r
211 private AttributeMap cacheStyle = null;
\r
213 private float fLeadingMargin;
\r
214 private float fTrailingMargin;
\r
215 private float fFirstLineIndent;
\r
216 private float fMinLineSpacing;
\r
217 private float fExtraLineSpacing;
\r
219 private int fFlush = -1;
\r
220 private MTabRuler fTabRuler;
\r
222 private boolean fLtrDefault;
\r
223 private DefaultCharacterMetric fDefaultCharMetric;
\r
225 BidiParagraphRenderer(AttributeMap pStyle, DefaultCharacterMetric defaultCharMetric) {
\r
227 fDefaultCharMetric = defaultCharMetric;
\r
228 initRenderer(pStyle);
\r
231 private float getFloatValue(Object key, AttributeMap style) {
\r
232 return ((Float)style.get(key)).floatValue();
\r
235 private int getIntValue(Object key, AttributeMap style) {
\r
236 return ((Integer)style.get(key)).intValue();
\r
240 * NOTE: it is illegal to initialize a StandardParagraphRenderer for any style
\r
241 * other than the one it was created with.
\r
243 public void initRenderer(AttributeMap pStyle) {
\r
245 if (cacheStyle == null) {
\r
247 fLeadingMargin = getFloatValue(TextAttribute.LEADING_MARGIN, pStyle);
\r
248 fTrailingMargin = getFloatValue(TextAttribute.TRAILING_MARGIN, pStyle);
\r
249 fFirstLineIndent = getFloatValue(TextAttribute.FIRST_LINE_INDENT, pStyle);
\r
250 fMinLineSpacing = getFloatValue(TextAttribute.MIN_LINE_SPACING, pStyle);
\r
251 fExtraLineSpacing = getFloatValue(TextAttribute.EXTRA_LINE_SPACING, pStyle);
\r
253 fFlush = getIntValue(TextAttribute.LINE_FLUSH, pStyle);
\r
255 fTabRuler = (MTabRuler) pStyle.get(TextAttribute.TAB_RULER);
\r
257 Object runDir = pStyle.get(TextAttribute.RUN_DIRECTION);
\r
258 fLtrDefault = !TextAttribute.RUN_DIRECTION_RTL.equals(runDir);
\r
260 cacheStyle = pStyle;
\r
262 else if (pStyle != cacheStyle) {
\r
263 if (!pStyle.equals(cacheStyle)) {
\r
264 throw new Error("Attempt to share BidiParagraphRenderer between styles!");
\r
267 cacheStyle = pStyle;
\r
272 private static boolean isTab(char ch) {
\r
277 * Fill in info with the next line.
\r
278 * @param measurer the LineBreakMeasurer for this paragraph.
\r
279 * Current position should be the first character on the line.
\r
280 * If null, a 0-length line is generated. If measurer is null
\r
281 * then paragraphStart and paragraphLimit should be equal.
\r
283 // Usually totalFormatWidth and lineBound will be the same.
\r
284 // totalFormatWidth is used for wrapping, but lineBound is
\r
285 // for flushing. These may be different for unwrapped text,
\r
287 public LayoutInfo layout(MConstText text,
\r
288 LayoutInfo layoutToReuse,
\r
289 LineBreakMeasurer measurer,
\r
290 FontRenderContext frc,
\r
291 int paragraphStart,
\r
292 int paragraphLimit,
\r
293 int totalFormatWidth,
\r
296 if ((measurer==null) != (paragraphStart==paragraphLimit)) {
\r
297 throw new IllegalArgumentException(
\r
298 "measurer, paragraphStart, paragraphLimit are wrong.");
\r
300 BidiLayoutInfo line = null;
\r
303 line = (BidiLayoutInfo) layoutToReuse;
\r
305 catch(ClassCastException e) {
\r
308 if (line == null) {
\r
309 line = new BidiLayoutInfo();
\r
312 line.fRenderer = this;
\r
314 final int lineCharStart = measurer==null? paragraphStart : measurer.getPosition();
\r
315 line.setCharStart(lineCharStart);
\r
317 final int lineIndent = (lineCharStart==paragraphStart)? (int) fFirstLineIndent : 0;
\r
319 int formatWidth = totalFormatWidth - (int) (fLeadingMargin + fTrailingMargin);
\r
320 computeLineMetrics(text, line, measurer, frc,
\r
321 paragraphStart, paragraphLimit, formatWidth, lineIndent);
\r
323 // position the line according to the line flush
\r
324 if (fFlush == FLUSH_TRAILING || fFlush == FLUSH_CENTER) {
\r
325 int lineArea = lineBound - (int) (fLeadingMargin + fTrailingMargin);
\r
326 int advanceDifference = lineArea - line.fVisibleAdvance;
\r
328 if (fFlush == FLUSH_TRAILING) {
\r
329 line.fLeadingMargin = ((int) (fLeadingMargin)) + advanceDifference;
\r
331 else if (fFlush == FLUSH_CENTER) {
\r
332 line.fLeadingMargin = (int) (fLeadingMargin + advanceDifference/2);
\r
336 line.fLeadingMargin = (int) fLeadingMargin;
\r
343 * Fill in the following fields in line:
\r
344 * fCharLength, fAscent, fDescent, fLeading, fVisibleAdvance,
\r
346 * Uses: line.fLeadingMargin
\r
347 * @param formatWidth the width to fit the line into.
\r
349 private void computeLineMetrics(MConstText text,
\r
350 BidiLayoutInfo line,
\r
351 LineBreakMeasurer measurer,
\r
352 FontRenderContext frc,
\r
353 final int paragraphStart,
\r
354 final int paragraphLimit,
\r
355 final int formatWidth,
\r
356 final int lineIndent) {
\r
358 int segmentCount = 0;
\r
359 /* variable not used boolean firstLine = measurer==null ||
\r
360 measurer.getPosition() == paragraphStart; */
\r
362 if (measurer != null) {
\r
363 computeSegments(text, line, measurer, paragraphLimit, formatWidth, lineIndent);
\r
365 // iterate through segments and accumulate ascent, descent,
\r
366 // leading, char length
\r
369 float descentPlusLeading = 0;
\r
371 segmentCount = line.fSegments.size();
\r
372 for (int i=0; i < segmentCount; i++) {
\r
373 TextLayout layout = ((BidiSegment)line.fSegments.elementAt(i)).fLayout;
\r
374 ascent = Math.max(ascent, layout.getAscent());
\r
375 float segDescent = layout.getDescent();
\r
376 descent = Math.max(descent, segDescent);
\r
377 descentPlusLeading = Math.max(descentPlusLeading, segDescent+layout.getLeading());
\r
378 line.fCharLength += layout.getCharacterCount();
\r
381 line.fAscent = (int) Math.ceil(ascent);
\r
382 line.fDescent = (int) Math.ceil(descent);
\r
383 line.fLeading = (int) Math.ceil(descentPlusLeading) - line.fDescent;
\r
386 line.fLeftToRight = fLtrDefault;
\r
387 line.fSegments.removeAllElements();
\r
389 line.fCharLength = 0;
\r
391 AttributeMap style = text.characterStyleAt(paragraphStart);
\r
392 DefaultCharacterMetric.Metric cm = fDefaultCharMetric.getMetricForStyle(style);
\r
393 line.fAscent = cm.getAscent();
\r
394 line.fDescent = cm.getDescent();
\r
395 line.fLeading = cm.getLeading();
\r
397 line.fVisibleAdvance = line.fTotalAdvance = 0;
\r
400 if (fExtraLineSpacing != 0) {
\r
401 line.fAscent += (int) Math.ceil(fExtraLineSpacing);
\r
404 if (fMinLineSpacing != 0){
\r
405 int height = line.getHeight();
\r
406 if (height < fMinLineSpacing) {
\r
407 line.fAscent += Math.ceil(fMinLineSpacing - height);
\r
411 final int lineNaturalAdvance = line.fTotalAdvance;
\r
413 line.fTotalAdvance += lineIndent;
\r
414 line.fVisibleAdvance += lineIndent;
\r
416 if (measurer != null) {
\r
417 // Now fill in fBounds field of BidiSegments. fBounds should tile
\r
419 final float lineHeight = line.getHeight();
\r
421 for (int i=1; i < segmentCount; i++) {
\r
423 BidiSegment currentSegment = (BidiSegment) line.fSegments.elementAt(i-1);
\r
424 BidiSegment nextSegment = (BidiSegment) line.fSegments.elementAt(i);
\r
429 if (line.fLeftToRight) {
\r
431 width = nextSegment.fDistanceFromLeadingMargin -
\r
432 currentSegment.fDistanceFromLeadingMargin;
\r
435 origin = currentSegment.fDistanceFromLeadingMargin;
\r
436 origin -= nextSegment.fDistanceFromLeadingMargin;
\r
437 origin += (float) Math.ceil(nextSegment.fLayout.getAdvance());
\r
438 width = (float) Math.ceil(currentSegment.fLayout.getAdvance()) - origin;
\r
440 currentSegment.fBounds = new Rectangle2D.Float(origin, -line.fAscent, width, lineHeight);
\r
443 // set last segment's bounds
\r
445 BidiSegment currentSegment = (BidiSegment) line.fSegments.elementAt(segmentCount-1);
\r
449 if (line.fLeftToRight) {
\r
451 width = lineNaturalAdvance - currentSegment.fDistanceFromLeadingMargin;
\r
454 origin = currentSegment.fDistanceFromLeadingMargin - lineNaturalAdvance;
\r
455 width = (float) Math.ceil(currentSegment.fLayout.getAdvance()) - origin;
\r
458 currentSegment.fBounds = new Rectangle2D.Float(origin, -line.fAscent, width, lineHeight);
\r
464 * Fill in fSegments, fLeftToRight. measurer must not be null
\r
466 private void computeSegments(MConstText text,
\r
467 BidiLayoutInfo line,
\r
468 LineBreakMeasurer measurer,
\r
469 final int paragraphLimit,
\r
470 final int formatWidth,
\r
471 final int lineIndent) {
\r
473 // Note on justification: only the last segment of a line is
\r
475 // Also, if a line ends in a tab it will not be justified.
\r
476 // This behavior is consistent with other word processors
\r
477 // I tried (MS Word and Lotus Word Pro).
\r
479 line.fSegments.removeAllElements();
\r
480 line.fCharLength = 0;
\r
482 TabStop currentTabStop = new TabStop((int)fLeadingMargin+lineIndent, TabStop.kLeading);
\r
484 int segmentLimit = measurer.getPosition();
\r
485 boolean firstSegment = true;
\r
487 int advanceFromLeadingMargin = lineIndent;
\r
489 boolean computeSegs = true;
\r
491 computeTabbedSegments: do {
\r
493 // compute sementLimit:
\r
494 if (segmentLimit <= measurer.getPosition()) {
\r
495 while (segmentLimit < paragraphLimit) {
\r
496 if (isTab(text.at(segmentLimit++))) {
\r
502 // NOTE: adjust available width for center tab!!!
\r
503 //System.out.println("Format width: " + (formatWidth-advanceFromLeadingMargin) +
\r
504 // "; segmentLimit: " + segmentLimit);
\r
506 int wrappingWidth = Math.max(formatWidth-advanceFromLeadingMargin, 0);
\r
507 TextLayout layout = null;
\r
508 if (firstSegment || wrappingWidth > 0 || segmentLimit > measurer.getPosition()+1) {
\r
509 layout = measurer.nextLayout(wrappingWidth, segmentLimit, !firstSegment);
\r
512 if (layout == null) {
\r
513 if (firstSegment) {
\r
514 // I doubt this would happen, but check anyway
\r
515 throw new Error("First layout is null!");
\r
517 break computeTabbedSegments;
\r
520 final int measurerPos = measurer.getPosition();
\r
521 if (measurerPos < segmentLimit) {
\r
522 computeSegs = false;
\r
523 if (fFlush == FULLY_JUSTIFIED) {
\r
524 layout = layout.getJustifiedLayout(wrappingWidth);
\r
528 computeSegs = !(measurerPos == paragraphLimit);
\r
531 if (firstSegment) {
\r
532 firstSegment = false;
\r
533 // Have to get ltr off of layout. Not available from measurer,
\r
535 line.fLeftToRight = layout.isLeftToRight();
\r
538 BidiSegment segment = new BidiSegment();
\r
539 segment.fLayout = layout;
\r
540 int layoutAdvance = (int) Math.ceil(layout.getAdvance());
\r
542 // position layout relative to leading margin, update logicalPositionOnLine
\r
544 int relativeTabPosition = currentTabStop.getPosition()-(int)fLeadingMargin;
\r
545 int logicalPositionOfLayout;
\r
546 switch (currentTabStop.getType()) {
\r
547 case TabStop.kTrailing:
\r
548 logicalPositionOfLayout = Math.max(
\r
549 relativeTabPosition-layoutAdvance,
\r
550 advanceFromLeadingMargin);
\r
552 case TabStop.kCenter:
\r
553 logicalPositionOfLayout = Math.max(
\r
554 relativeTabPosition-(layoutAdvance/2),
\r
555 advanceFromLeadingMargin);
\r
557 default: // includes decimal tab right now
\r
558 logicalPositionOfLayout = relativeTabPosition;
\r
562 // position layout in segment
\r
563 if (line.fLeftToRight) {
\r
564 segment.fDistanceFromLeadingMargin = logicalPositionOfLayout;
\r
567 segment.fDistanceFromLeadingMargin = logicalPositionOfLayout+layoutAdvance;
\r
570 // update advanceFromLeadingMargin
\r
571 advanceFromLeadingMargin = logicalPositionOfLayout + layoutAdvance;
\r
573 // add segment to segment Vector
\r
574 line.fSegments.addElement(segment);
\r
577 currentTabStop = fTabRuler.nextTab((int)fLeadingMargin+advanceFromLeadingMargin);
\r
578 if (currentTabStop.getType() == TabStop.kLeading ||
\r
579 currentTabStop.getType() == TabStop.kAuto) {
\r
580 advanceFromLeadingMargin = currentTabStop.getPosition();
\r
581 //System.out.println("Advance from leading margin:" + advanceFromLeadingMargin);
\r
585 //System.out.println("Non-leading tab, type=" + currentTabStop.getType());
\r
588 } while (computeSegs);
\r
590 // Now compute fTotalAdvance, fVisibleAdvance. These metrics may be affected
\r
591 // by a trailing tab.
\r
594 BidiSegment lastSegment = (BidiSegment) line.fSegments.lastElement();
\r
595 TextLayout lastLayout = lastSegment.fLayout;
\r
597 if (line.fLeftToRight) {
\r
598 line.fTotalAdvance = (int) Math.ceil(lastLayout.getAdvance()) +
\r
599 lastSegment.fDistanceFromLeadingMargin;
\r
600 line.fVisibleAdvance = (int) Math.ceil(lastLayout.getVisibleAdvance()) +
\r
601 lastSegment.fDistanceFromLeadingMargin;
\r
604 line.fTotalAdvance = lastSegment.fDistanceFromLeadingMargin;
\r
605 line.fVisibleAdvance = lastSegment.fDistanceFromLeadingMargin -
\r
606 (int) Math.ceil(lastLayout.getAdvance() -
\r
607 lastLayout.getVisibleAdvance());
\r
610 if (isTab(text.at(measurer.getPosition()-1))) {
\r
611 line.fTotalAdvance = Math.max(line.fTotalAdvance,
\r
612 currentTabStop.getPosition());
\r
618 * Return the highlight shape for the given character offsets.
\r
619 * The Shape returned is relative to the leftmost point on the
\r
620 * baseline of line.
\r
622 private Shape getHighlightShape(BidiLayoutInfo line,
\r
628 if (hlStart >= hlLimit) {
\r
629 throw new IllegalArgumentException("Highlight range length is not positive.");
\r
632 final int leadingMargin = (line.fLeftToRight)?
\r
633 line.fLeadingMargin : lineBound - line.fLeadingMargin;
\r
634 final int segmentCount = line.fSegments.size();
\r
637 GeneralPath highlightPath = null;
\r
639 int currentLayoutStart = line.getCharStart(lengthBasis);
\r
641 for (int i=0; i < segmentCount; i++) {
\r
643 BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);
\r
644 TextLayout layout = segment.fLayout;
\r
645 int charCount = layout.getCharacterCount();
\r
646 int currentLayoutLimit = currentLayoutStart + charCount;
\r
647 boolean rangesIntersect;
\r
648 if (hlStart <= currentLayoutStart) {
\r
649 rangesIntersect = hlLimit > currentLayoutStart;
\r
652 rangesIntersect = hlStart < currentLayoutLimit;
\r
655 if (rangesIntersect) {
\r
657 Shape currentHl = layout.getLogicalHighlightShape(
\r
658 Math.max(hlStart-currentLayoutStart, 0),
\r
659 Math.min(hlLimit-currentLayoutStart, charCount),
\r
663 if (line.fLeftToRight) {
\r
664 xTranslate = leadingMargin +
\r
665 segment.fDistanceFromLeadingMargin;
\r
668 xTranslate = leadingMargin -
\r
669 segment.fDistanceFromLeadingMargin;
\r
672 if (xTranslate != 0) {
\r
673 AffineTransform xform =
\r
674 AffineTransform.getTranslateInstance(xTranslate, 0);
\r
675 currentHl = xform.createTransformedShape(currentHl);
\r
678 if (rval == null) {
\r
682 if (highlightPath == null) {
\r
683 highlightPath = new GeneralPath();
\r
684 highlightPath.append(rval, false);
\r
685 rval = highlightPath;
\r
687 highlightPath.append(currentHl, false);
\r
690 currentLayoutStart = currentLayoutLimit;
\r
696 private void renderWithHighlight(BidiLayoutInfo line,
\r
702 TextOffset selStart,
\r
703 TextOffset selStop,
\r
704 Color highlightColor) {
\r
706 final int lineCharStart = line.getCharStart(lengthBasis);
\r
708 if (selStart != null && selStop != null && !selStart.equals(selStop) &&
\r
709 line.fCharLength != 0 &&
\r
710 selStart.fOffset < lineCharStart + line.fCharLength &&
\r
711 selStop.fOffset > lineCharStart) {
\r
713 Shape highlight = getHighlightShape(line, lengthBasis, lineBound, selStart.fOffset, selStop.fOffset);
\r
714 if (highlight != null) {
\r
715 Graphics2D hl = (Graphics2D) g.create();
\r
716 hl.setColor(highlightColor);
\r
717 hl.translate(x, y + line.fAscent);
\r
718 hl.fill(highlight);
\r
722 render(line, lengthBasis, g, lineBound, x, y);
\r
726 * Draw the line into the graphics. (x, y) is the upper-left corner
\r
727 * of the line. The leading edge of a right-aligned line is aligned
\r
728 * to (x + lineBound).
\r
730 private void render(BidiLayoutInfo line,
\r
737 final int leadingMargin = (line.fLeftToRight)?
\r
738 x + line.fLeadingMargin : x + lineBound - line.fLeadingMargin;
\r
739 final int baseline = y + line.fAscent;
\r
740 final int segmentCount = line.fSegments.size();
\r
742 for (int i=0; i < segmentCount; i++) {
\r
744 BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);
\r
747 if (line.fLeftToRight) {
\r
748 drawX = leadingMargin + segment.fDistanceFromLeadingMargin;
\r
751 drawX = leadingMargin - segment.fDistanceFromLeadingMargin;
\r
754 segment.fLayout.draw(g, drawX, baseline);
\r
758 private TextOffset hitTestSegment(TextOffset result,
\r
759 int segmentCharStart,
\r
760 BidiSegment segment,
\r
764 final TextLayout layout = segment.fLayout;
\r
765 final int charCount = layout.getCharacterCount();
\r
766 final int layoutAdvance = (int) Math.ceil(layout.getAdvance());
\r
767 Rectangle2D bounds = segment.fBounds;
\r
769 final boolean ltr = layout.isLeftToRight();
\r
771 if (ltr && (xInSegment >= layoutAdvance) || !ltr && (xInSegment <= 0)) {
\r
773 // pretend the extra space at the end of the line is a
\r
774 // tab and 'hit-test' it.
\r
777 tabCenter = (layoutAdvance+bounds.getMaxX()) / 2;
\r
780 tabCenter = bounds.getX() / 2;
\r
783 if ((xInSegment >= tabCenter) == ltr) {
\r
784 result.fOffset = charCount;
\r
785 result.fPlacement = TextOffset.BEFORE_OFFSET;
\r
788 result.fOffset = charCount-1;
\r
789 result.fPlacement = TextOffset.AFTER_OFFSET;
\r
793 TextHitInfo info = layout.hitTestChar(xInSegment, yInSegment, segment.fBounds);
\r
794 result.fOffset = info.getInsertionIndex();
\r
795 if (result.fOffset == 0) {
\r
796 result.fPlacement = TextOffset.AFTER_OFFSET;
\r
798 else if (result.fOffset == charCount) {
\r
799 result.fPlacement = TextOffset.BEFORE_OFFSET;
\r
802 result.fPlacement = info.isLeadingEdge()?
\r
803 TextOffset.AFTER_OFFSET : TextOffset.BEFORE_OFFSET;
\r
807 result.fOffset += segmentCharStart;
\r
812 * Return the offset at the point (x, y). (x, y) is relative to the top-left
\r
813 * of the line. The leading edge of a right-aligned line is aligned
\r
816 private TextOffset pixelToOffset(BidiLayoutInfo line,
\r
823 if (result == null) {
\r
824 result = new TextOffset();
\r
827 final int yInSegment = y - line.fAscent;
\r
828 final int leadingMargin = (line.fLeftToRight)?
\r
829 line.fLeadingMargin : lineBound - line.fLeadingMargin;
\r
830 final int lineCharStart = line.getCharStart(lengthBasis);
\r
832 // first see if point is before leading edge of line
\r
833 final int segmentCount = line.fSegments.size();
\r
835 int segLeadingMargin = leadingMargin;
\r
836 if (segmentCount > 0) {
\r
837 BidiSegment firstSeg = (BidiSegment) line.fSegments.elementAt(0);
\r
838 if (line.fLeftToRight) {
\r
839 segLeadingMargin += firstSeg.fDistanceFromLeadingMargin;
\r
842 segLeadingMargin -= firstSeg.fDistanceFromLeadingMargin;
\r
843 segLeadingMargin += (float) firstSeg.fBounds.getMaxX();
\r
846 if (line.fLeftToRight == (x <= segLeadingMargin)) {
\r
847 result.fOffset = lineCharStart;
\r
848 result.fPlacement = TextOffset.AFTER_OFFSET;
\r
853 int segmentCharStart = lineCharStart;
\r
855 for (int i=0; i < segmentCount; i++) {
\r
857 BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);
\r
858 int segmentOrigin = line.fLeftToRight?
\r
859 leadingMargin+segment.fDistanceFromLeadingMargin :
\r
860 leadingMargin-segment.fDistanceFromLeadingMargin;
\r
861 int xInSegment = x - segmentOrigin;
\r
862 if (line.fLeftToRight) {
\r
863 if (segment.fBounds.getMaxX() > xInSegment) {
\r
864 return hitTestSegment(result, segmentCharStart, segment, xInSegment, yInSegment);
\r
868 if (segment.fBounds.getX() < xInSegment) {
\r
869 return hitTestSegment(result, segmentCharStart, segment, xInSegment, yInSegment);
\r
872 segmentCharStart += segment.fLayout.getCharacterCount();
\r
875 result.fOffset = lineCharStart + line.fCharLength;
\r
876 result.fPlacement = TextOffset.BEFORE_OFFSET;
\r
880 private void renderCaret(BidiLayoutInfo line,
\r
887 final int charOffset,
\r
888 Color strongCaretColor,
\r
889 Color weakCaretColor)
\r
891 final int segmentCount = line.fSegments.size();
\r
892 final int lineStart = line.getCharStart(lengthBasis);
\r
894 int currentStart = lineStart;
\r
895 BidiSegment segment = null;
\r
898 for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
\r
899 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
\r
900 int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
\r
901 if (currentEndpoint > charOffset) {
\r
904 currentStart = currentEndpoint;
\r
908 There are two choices here:
\r
909 1. get carets from a TextLayout and render them, or
\r
910 2. make up a caret ourselves and render it.
\r
911 We want to do 2 when:
\r
912 * there is no text on the line, or
\r
913 * the line ends with a tab and we are drawing the last caret on the line
\r
914 Otherwise, we want 1.
\r
917 if (segmentIndex == segmentCount && segmentCount > 0) {
\r
918 // If we get here, line length is not 0, and charOffset is at end of line
\r
919 if (!isTab(text.at(charOffset-1))) {
\r
920 segmentIndex = segmentCount-1;
\r
921 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
\r
922 currentStart = lineStart + line.getCharLength() -
\r
923 segment.fLayout.getCharacterCount();
\r
927 Object savedPaint = Graphics2DConversion.getColorState(g);
\r
930 if (segmentIndex < segmentCount) {
\r
931 TextLayout layout = segment.fLayout;
\r
932 int offsetInLayout = charOffset - currentStart;
\r
933 Shape[] carets = layout.getCaretShapes(offsetInLayout, segment.fBounds);
\r
934 g.setColor(strongCaretColor);
\r
935 int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;
\r
936 int layoutX = line.fLeftToRight?
\r
937 x + layoutPos : x + lineBound - layoutPos;
\r
938 int layoutY = y + line.fAscent;
\r
940 // Translating and then clipping doesn't work. Try this:
\r
941 Rectangle2D.Float clipRect = new Rectangle2D.Float();
\r
942 clipRect.setRect(segment.fBounds);
\r
943 clipRect.x += layoutX;
\r
944 clipRect.y += layoutY;
\r
945 clipRect.width += 1;
\r
946 clipRect.height -= 1;
\r
948 Object savedClip = ClipWorkaround.saveClipState(g);
\r
950 ClipWorkaround.translateAndDrawShapeWithClip(g,
\r
955 if (carets[1] != null) {
\r
956 g.setColor(weakCaretColor);
\r
957 ClipWorkaround.translateAndDrawShapeWithClip(g,
\r
965 ClipWorkaround.restoreClipState(g, savedClip);
\r
969 int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
\r
970 int endX = line.fLeftToRight? lineEnd : lineBound-lineEnd;
\r
972 g.drawLine(endX, y, endX, y+line.getHeight()-1);
\r
976 Graphics2DConversion.restoreColorState(g, savedPaint);
\r
980 private Rectangle caretBounds(BidiLayoutInfo line,
\r
988 final int segmentCount = line.fSegments.size();
\r
989 final int lineStart = line.getCharStart(lengthBasis);
\r
990 int currentStart = lineStart;
\r
991 BidiSegment segment = null;
\r
994 for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
\r
995 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
\r
996 int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
\r
997 if (currentEndpoint > charOffset) {
\r
1000 currentStart = currentEndpoint;
\r
1003 if (segmentIndex == segmentCount && segmentCount > 0) {
\r
1004 // If we get here, line length is not 0, and charOffset is at end of line
\r
1005 if (!isTab(text.at(charOffset-1))) {
\r
1006 segmentIndex = segmentCount-1;
\r
1007 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
\r
1008 currentStart = lineStart + line.getCharLength() -
\r
1009 segment.fLayout.getCharacterCount();
\r
1015 if (segmentIndex < segmentCount) {
\r
1016 TextLayout layout = segment.fLayout;
\r
1017 int offsetInLayout = charOffset - currentStart;
\r
1018 Shape[] carets = layout.getCaretShapes(offsetInLayout, segment.fBounds);
\r
1019 r = carets[0].getBounds();
\r
1020 if (carets[1] != null) {
\r
1021 r.add(carets[1].getBounds());
\r
1025 int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;
\r
1026 if (line.fLeftToRight) {
\r
1030 r.x += lineBound - layoutPos;
\r
1032 r.y += line.fAscent;
\r
1035 r = new Rectangle();
\r
1036 r.height = line.getHeight();
\r
1038 int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
\r
1039 if (line.fLeftToRight) {
\r
1043 r.x = lineBound - lineEnd;
\r
1047 r.translate(x, y);
\r
1051 private int strongCaretBaselinePosition(BidiLayoutInfo line,
\r
1056 final int segmentCount = line.fSegments.size();
\r
1057 int currentStart = line.getCharStart(lengthBasis);
\r
1058 BidiSegment segment = null;
\r
1061 for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
\r
1062 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
\r
1063 int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
\r
1064 if (currentEndpoint > charOffset) {
\r
1067 currentStart = currentEndpoint;
\r
1070 if (segmentIndex < segmentCount) {
\r
1071 TextLayout layout = segment.fLayout;
\r
1072 int offsetInLayout = charOffset - currentStart;
\r
1073 TextHitInfo hit = TextHitInfo.afterOffset(offsetInLayout);
\r
1074 hit = TextLayout.DEFAULT_CARET_POLICY.getStrongCaret(hit, hit.getOtherHit(), layout);
\r
1075 float[] info = layout.getCaretInfo(hit);
\r
1076 int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;
\r
1077 if (line.fLeftToRight) {
\r
1078 return layoutPos + (int) info[0];
\r
1081 return lineBound - layoutPos + (int) info[0];
\r
1085 int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
\r
1086 if (line.fLeftToRight) {
\r
1090 return lineBound - lineEnd;
\r
1095 private int getNextOffset(BidiLayoutInfo line,
\r
1100 if (dir != MFormatter.eLeft && dir != MFormatter.eRight) {
\r
1101 throw new IllegalArgumentException("Invalid direction.");
\r
1104 // find segment containing offset:
\r
1105 final int segmentCount = line.fSegments.size();
\r
1106 final int lineCharStart = line.getCharStart(lengthBasis);
\r
1108 int currentStart = lineCharStart;
\r
1109 BidiSegment segment = null;
\r
1112 for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
\r
1113 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
\r
1114 int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
\r
1115 if (currentEndpoint > charOffset ||
\r
1116 (segmentIndex == segmentCount-1 && currentEndpoint==charOffset)) {
\r
1119 currentStart = currentEndpoint;
\r
1122 final boolean logAdvance = (dir==MFormatter.eRight)==(line.fLeftToRight);
\r
1126 if (segmentIndex < segmentCount) {
\r
1127 TextLayout layout = segment.fLayout;
\r
1128 int offsetInLayout = charOffset - currentStart;
\r
1129 TextHitInfo hit = (dir==MFormatter.eLeft)?
\r
1130 layout.getNextLeftHit(offsetInLayout) :
\r
1131 layout.getNextRightHit(offsetInLayout);
\r
1132 if (hit == null) {
\r
1133 result = logAdvance?
\r
1134 currentStart+layout.getCharacterCount()+1 : currentStart-1;
\r
1137 result = hit.getInsertionIndex() + currentStart;
\r
1141 result = logAdvance? lineCharStart + line.fCharLength + 1 :
\r
1142 lineCharStart - 1;
\r