0001: /*
0002: * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
0003: *
0004: * The program is provided "as is" without any warranty express or
0005: * implied, including the warranty of non-infringement and the implied
0006: * warranties of merchantibility and fitness for a particular purpose.
0007: * IBM will not be liable for any damages suffered by you as a result
0008: * of using the Program. In no event will IBM be liable for any
0009: * special, indirect or consequential damages or lost profits even if
0010: * IBM has been advised of the possibility of their occurrence. IBM
0011: * will not be liable for any third party claims against you.
0012: */
0013: // Requires Java2
0014: package com.ibm.richtext.textformat;
0015:
0016: import java.awt.Color;
0017: import java.awt.Rectangle;
0018: import java.awt.Shape;
0019:
0020: import java.util.Vector;
0021:
0022: import com.ibm.richtext.styledtext.MConstText;
0023: import com.ibm.richtext.styledtext.MTabRuler;
0024: import com.ibm.richtext.styledtext.TabStop;
0025:
0026: import com.ibm.richtext.textlayout.attributes.AttributeMap;
0027: import com.ibm.richtext.textlayout.attributes.TextAttribute;
0028:
0029: import com.ibm.richtext.textlayout.Graphics2DConversion;
0030:
0031: ///*JDK12IMPORTS
0032: import java.awt.Graphics2D;
0033:
0034: import java.awt.font.FontRenderContext;
0035: import java.awt.font.TextLayout;
0036: import java.awt.font.LineBreakMeasurer;
0037: import java.awt.font.TextHitInfo;
0038:
0039: import java.awt.geom.AffineTransform;
0040: import java.awt.geom.GeneralPath;
0041: import java.awt.geom.Rectangle2D;
0042:
0043: //JDK12IMPORTS*/
0044: /*JDK11IMPORTS
0045: import com.ibm.richtext.textlayout.Graphics2D;
0046:
0047: import com.ibm.richtext.textlayout.FontRenderContext;
0048: import com.ibm.richtext.textlayout.TextLayout;
0049: import com.ibm.richtext.textlayout.LineBreakMeasurer;
0050: import com.ibm.richtext.textlayout.TextHitInfo;
0051:
0052: import com.ibm.richtext.textlayout.AffineTransform;
0053: import com.ibm.richtext.textlayout.GeneralPath;
0054: import com.ibm.richtext.textlayout.Rectangle2D;
0055: JDK11IMPORTS*/
0056:
0057: final class BidiParagraphRenderer extends ParagraphRenderer {
0058:
0059: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
0060:
0061: private final class BidiSegment {
0062: TextLayout fLayout;
0063: Rectangle2D.Float fBounds;
0064: int fDistanceFromLeadingMargin;
0065: }
0066:
0067: private final class BidiLayoutInfo extends LayoutInfo {
0068: int fCharLength; // number of characters on line (was fLength)
0069: int fAscent;
0070: int fDescent;
0071: int fLeading;
0072: int fVisibleAdvance; // distance along line direction ie width
0073: int fTotalAdvance; // distance along line direction including trailing whitespace
0074:
0075: int fLeadingMargin; // screen distance from leading margin
0076:
0077: boolean fLeftToRight; // true iff the orientation is left-to-right
0078:
0079: final Vector fSegments = new Vector(); // segments to render, in logical order
0080:
0081: public int getCharLength() {
0082: return fCharLength;
0083: }
0084:
0085: public int getAscent() {
0086: return fAscent;
0087: }
0088:
0089: public int getDescent() {
0090: return fDescent;
0091: }
0092:
0093: public int getLeading() {
0094: return fLeading;
0095: }
0096:
0097: public int getVisibleAdvance() {
0098: return fVisibleAdvance;
0099: }
0100:
0101: public int getTotalAdvance() {
0102: return fTotalAdvance;
0103: }
0104:
0105: public int getLeadingMargin() {
0106: return fLeadingMargin;
0107: }
0108:
0109: public boolean isLeftToRight() {
0110: return fLeftToRight;
0111: }
0112:
0113: public int getHeight() {
0114: return fAscent + fDescent + fLeading;
0115: }
0116:
0117: public String toString() {
0118: return "LayoutInfo(charStart: " + getCharStart(0)
0119: + ", fCharLength: " + fCharLength + ", fAscent: "
0120: + fAscent + ", fDescent: " + fDescent
0121: + ", fVisibleAdvance: " + fVisibleAdvance
0122: + ", fTotalAdvance: " + fTotalAdvance
0123: + ", fLeadingMargin: " + fLeadingMargin + ")";
0124: }
0125:
0126: BidiParagraphRenderer fRenderer;
0127:
0128: // just delegate to renderer for now
0129:
0130: public void renderWithHighlight(int lengthBasis, Graphics2D g,
0131: int lineBound, int x, int y, TextOffset selStart,
0132: TextOffset selStop, Color highlightColor) {
0133:
0134: fRenderer.renderWithHighlight(this , lengthBasis, g,
0135: lineBound, x, y, selStart, selStop, highlightColor);
0136: }
0137:
0138: public void render(int lengthBasis, Graphics2D g,
0139: int lineBound, int x, int y) {
0140: fRenderer.render(this , lengthBasis, g, lineBound, x, y);
0141: }
0142:
0143: public void renderCaret(MConstText text, int lengthBasis,
0144: Graphics2D g, int lineBound, int x, int y,
0145: int charOffset, Color strongCaretColor,
0146: Color weakCaretColor) {
0147: fRenderer.renderCaret(this , text, lengthBasis, g,
0148: lineBound, x, y, charOffset, strongCaretColor,
0149: weakCaretColor);
0150: }
0151:
0152: public TextOffset pixelToOffset(int lengthBasis,
0153: TextOffset result, int lineBound, int x, int y) {
0154: return fRenderer.pixelToOffset(this , lengthBasis, result,
0155: lineBound, x, y);
0156: }
0157:
0158: public Rectangle caretBounds(MConstText text, int lengthBasis,
0159: int lineBound, int charOffset, int x, int y) {
0160: return fRenderer.caretBounds(this , text, lengthBasis,
0161: lineBound, charOffset, x, y);
0162: }
0163:
0164: public int strongCaretBaselinePosition(int lengthBasis,
0165: int lineBound, int charOffset) {
0166:
0167: return fRenderer.strongCaretBaselinePosition(this ,
0168: lengthBasis, lineBound, charOffset);
0169: }
0170:
0171: public int getNextOffset(int lengthBasis, int charOffset,
0172: short dir) {
0173:
0174: return fRenderer.getNextOffset(this , lengthBasis,
0175: charOffset, dir);
0176: }
0177: }
0178:
0179: private static final int FLUSH_LEADING = TextAttribute.FLUSH_LEADING
0180: .intValue();
0181: private static final int FLUSH_CENTER = TextAttribute.FLUSH_CENTER
0182: .intValue();
0183: private static final int FLUSH_TRAILING = TextAttribute.FLUSH_TRAILING
0184: .intValue();
0185: private static final int FULLY_JUSTIFIED = TextAttribute.FULLY_JUSTIFIED
0186: .intValue();
0187:
0188: private AttributeMap cacheStyle = null;
0189:
0190: private float fLeadingMargin;
0191: private float fTrailingMargin;
0192: private float fFirstLineIndent;
0193: private float fMinLineSpacing;
0194: private float fExtraLineSpacing;
0195:
0196: private int fFlush = -1;
0197: private MTabRuler fTabRuler;
0198:
0199: private boolean fLtrDefault;
0200: private DefaultCharacterMetric fDefaultCharMetric;
0201:
0202: BidiParagraphRenderer(AttributeMap pStyle,
0203: DefaultCharacterMetric defaultCharMetric) {
0204:
0205: fDefaultCharMetric = defaultCharMetric;
0206: initRenderer(pStyle);
0207: }
0208:
0209: private float getFloatValue(Object key, AttributeMap style) {
0210: return ((Float) style.get(key)).floatValue();
0211: }
0212:
0213: private int getIntValue(Object key, AttributeMap style) {
0214: return ((Integer) style.get(key)).intValue();
0215: }
0216:
0217: /**
0218: * NOTE: it is illegal to initialize a StandardParagraphRenderer for any style
0219: * other than the one it was created with.
0220: */
0221: public void initRenderer(AttributeMap pStyle) {
0222:
0223: if (cacheStyle == null) {
0224:
0225: fLeadingMargin = getFloatValue(
0226: TextAttribute.LEADING_MARGIN, pStyle);
0227: fTrailingMargin = getFloatValue(
0228: TextAttribute.TRAILING_MARGIN, pStyle);
0229: fFirstLineIndent = getFloatValue(
0230: TextAttribute.FIRST_LINE_INDENT, pStyle);
0231: fMinLineSpacing = getFloatValue(
0232: TextAttribute.MIN_LINE_SPACING, pStyle);
0233: fExtraLineSpacing = getFloatValue(
0234: TextAttribute.EXTRA_LINE_SPACING, pStyle);
0235:
0236: fFlush = getIntValue(TextAttribute.LINE_FLUSH, pStyle);
0237:
0238: fTabRuler = (MTabRuler) pStyle.get(TextAttribute.TAB_RULER);
0239:
0240: Object runDir = pStyle.get(TextAttribute.RUN_DIRECTION);
0241: fLtrDefault = !TextAttribute.RUN_DIRECTION_RTL
0242: .equals(runDir);
0243:
0244: cacheStyle = pStyle;
0245: } else if (pStyle != cacheStyle) {
0246: if (!pStyle.equals(cacheStyle)) {
0247: throw new Error(
0248: "Attempt to share BidiParagraphRenderer between styles!");
0249: } else {
0250: cacheStyle = pStyle;
0251: }
0252: }
0253: }
0254:
0255: private static boolean isTab(char ch) {
0256: return ch == '\t';
0257: }
0258:
0259: /**
0260: * Fill in info with the next line.
0261: * @param measurer the LineBreakMeasurer for this paragraph.
0262: * Current position should be the first character on the line.
0263: * If null, a 0-length line is generated. If measurer is null
0264: * then paragraphStart and paragraphLimit should be equal.
0265: */
0266: // Usually totalFormatWidth and lineBound will be the same.
0267: // totalFormatWidth is used for wrapping, but lineBound is
0268: // for flushing. These may be different for unwrapped text,
0269: // for example.
0270: public LayoutInfo layout(MConstText text, LayoutInfo layoutToReuse,
0271: LineBreakMeasurer measurer, FontRenderContext frc,
0272: int paragraphStart, int paragraphLimit,
0273: int totalFormatWidth, int lineBound) {
0274:
0275: if ((measurer == null) != (paragraphStart == paragraphLimit)) {
0276: throw new IllegalArgumentException(
0277: "measurer, paragraphStart, paragraphLimit are wrong.");
0278: }
0279: BidiLayoutInfo line = null;
0280:
0281: try {
0282: line = (BidiLayoutInfo) layoutToReuse;
0283: } catch (ClassCastException e) {
0284: }
0285:
0286: if (line == null) {
0287: line = new BidiLayoutInfo();
0288: }
0289:
0290: line.fRenderer = this ;
0291:
0292: final int lineCharStart = measurer == null ? paragraphStart
0293: : measurer.getPosition();
0294: line.setCharStart(lineCharStart);
0295:
0296: final int lineIndent = (lineCharStart == paragraphStart) ? (int) fFirstLineIndent
0297: : 0;
0298:
0299: int formatWidth = totalFormatWidth
0300: - (int) (fLeadingMargin + fTrailingMargin);
0301: computeLineMetrics(text, line, measurer, frc, paragraphStart,
0302: paragraphLimit, formatWidth, lineIndent);
0303:
0304: // position the line according to the line flush
0305: if (fFlush == FLUSH_TRAILING || fFlush == FLUSH_CENTER) {
0306: int lineArea = lineBound
0307: - (int) (fLeadingMargin + fTrailingMargin);
0308: int advanceDifference = lineArea - line.fVisibleAdvance;
0309:
0310: if (fFlush == FLUSH_TRAILING) {
0311: line.fLeadingMargin = ((int) (fLeadingMargin))
0312: + advanceDifference;
0313: } else if (fFlush == FLUSH_CENTER) {
0314: line.fLeadingMargin = (int) (fLeadingMargin + advanceDifference / 2);
0315: }
0316: } else {
0317: line.fLeadingMargin = (int) fLeadingMargin;
0318: }
0319:
0320: return line;
0321: }
0322:
0323: /**
0324: * Fill in the following fields in line:
0325: * fCharLength, fAscent, fDescent, fLeading, fVisibleAdvance,
0326: * fTotalAdvance.
0327: * Uses: line.fLeadingMargin
0328: * @param formatWidth the width to fit the line into.
0329: */
0330: private void computeLineMetrics(MConstText text,
0331: BidiLayoutInfo line, LineBreakMeasurer measurer,
0332: FontRenderContext frc, final int paragraphStart,
0333: final int paragraphLimit, final int formatWidth,
0334: final int lineIndent) {
0335:
0336: int segmentCount = 0;
0337: /* variable not used boolean firstLine = measurer==null ||
0338: measurer.getPosition() == paragraphStart; */
0339:
0340: if (measurer != null) {
0341: computeSegments(text, line, measurer, paragraphLimit,
0342: formatWidth, lineIndent);
0343:
0344: // iterate through segments and accumulate ascent, descent,
0345: // leading, char length
0346: float ascent = 0;
0347: float descent = 0;
0348: float descentPlusLeading = 0;
0349:
0350: segmentCount = line.fSegments.size();
0351: for (int i = 0; i < segmentCount; i++) {
0352: TextLayout layout = ((BidiSegment) line.fSegments
0353: .elementAt(i)).fLayout;
0354: ascent = Math.max(ascent, layout.getAscent());
0355: float segDescent = layout.getDescent();
0356: descent = Math.max(descent, segDescent);
0357: descentPlusLeading = Math.max(descentPlusLeading,
0358: segDescent + layout.getLeading());
0359: line.fCharLength += layout.getCharacterCount();
0360: }
0361:
0362: line.fAscent = (int) Math.ceil(ascent);
0363: line.fDescent = (int) Math.ceil(descent);
0364: line.fLeading = (int) Math.ceil(descentPlusLeading)
0365: - line.fDescent;
0366: } else {
0367: line.fLeftToRight = fLtrDefault;
0368: line.fSegments.removeAllElements();
0369:
0370: line.fCharLength = 0;
0371:
0372: AttributeMap style = text.characterStyleAt(paragraphStart);
0373: DefaultCharacterMetric.Metric cm = fDefaultCharMetric
0374: .getMetricForStyle(style);
0375: line.fAscent = cm.getAscent();
0376: line.fDescent = cm.getDescent();
0377: line.fLeading = cm.getLeading();
0378:
0379: line.fVisibleAdvance = line.fTotalAdvance = 0;
0380: }
0381:
0382: if (fExtraLineSpacing != 0) {
0383: line.fAscent += (int) Math.ceil(fExtraLineSpacing);
0384: }
0385:
0386: if (fMinLineSpacing != 0) {
0387: int height = line.getHeight();
0388: if (height < fMinLineSpacing) {
0389: line.fAscent += Math.ceil(fMinLineSpacing - height);
0390: }
0391: }
0392:
0393: final int lineNaturalAdvance = line.fTotalAdvance;
0394:
0395: line.fTotalAdvance += lineIndent;
0396: line.fVisibleAdvance += lineIndent;
0397:
0398: if (measurer != null) {
0399: // Now fill in fBounds field of BidiSegments. fBounds should tile
0400: // the line.
0401: final float lineHeight = line.getHeight();
0402:
0403: for (int i = 1; i < segmentCount; i++) {
0404:
0405: BidiSegment currentSegment = (BidiSegment) line.fSegments
0406: .elementAt(i - 1);
0407: BidiSegment nextSegment = (BidiSegment) line.fSegments
0408: .elementAt(i);
0409:
0410: float origin;
0411: float width;
0412:
0413: if (line.fLeftToRight) {
0414: origin = 0;
0415: width = nextSegment.fDistanceFromLeadingMargin
0416: - currentSegment.fDistanceFromLeadingMargin;
0417: } else {
0418: origin = currentSegment.fDistanceFromLeadingMargin;
0419: origin -= nextSegment.fDistanceFromLeadingMargin;
0420: origin += (float) Math.ceil(nextSegment.fLayout
0421: .getAdvance());
0422: width = (float) Math.ceil(currentSegment.fLayout
0423: .getAdvance())
0424: - origin;
0425: }
0426: currentSegment.fBounds = new Rectangle2D.Float(origin,
0427: -line.fAscent, width, lineHeight);
0428: }
0429:
0430: // set last segment's bounds
0431: {
0432: BidiSegment currentSegment = (BidiSegment) line.fSegments
0433: .elementAt(segmentCount - 1);
0434: float origin;
0435: float width;
0436:
0437: if (line.fLeftToRight) {
0438: origin = 0;
0439: width = lineNaturalAdvance
0440: - currentSegment.fDistanceFromLeadingMargin;
0441: } else {
0442: origin = currentSegment.fDistanceFromLeadingMargin
0443: - lineNaturalAdvance;
0444: width = (float) Math.ceil(currentSegment.fLayout
0445: .getAdvance())
0446: - origin;
0447: }
0448:
0449: currentSegment.fBounds = new Rectangle2D.Float(origin,
0450: -line.fAscent, width, lineHeight);
0451: }
0452: }
0453: }
0454:
0455: /**
0456: * Fill in fSegments, fLeftToRight. measurer must not be null
0457: */
0458: private void computeSegments(MConstText text, BidiLayoutInfo line,
0459: LineBreakMeasurer measurer, final int paragraphLimit,
0460: final int formatWidth, final int lineIndent) {
0461:
0462: // Note on justification: only the last segment of a line is
0463: // justified.
0464: // Also, if a line ends in a tab it will not be justified.
0465: // This behavior is consistent with other word processors
0466: // I tried (MS Word and Lotus Word Pro).
0467:
0468: line.fSegments.removeAllElements();
0469: line.fCharLength = 0;
0470:
0471: TabStop currentTabStop = new TabStop((int) fLeadingMargin
0472: + lineIndent, TabStop.kLeading);
0473:
0474: int segmentLimit = measurer.getPosition();
0475: boolean firstSegment = true;
0476:
0477: int advanceFromLeadingMargin = lineIndent;
0478:
0479: boolean computeSegs = true;
0480:
0481: computeTabbedSegments: do {
0482:
0483: // compute sementLimit:
0484: if (segmentLimit <= measurer.getPosition()) {
0485: while (segmentLimit < paragraphLimit) {
0486: if (isTab(text.at(segmentLimit++))) {
0487: break;
0488: }
0489: }
0490: }
0491:
0492: // NOTE: adjust available width for center tab!!!
0493: //System.out.println("Format width: " + (formatWidth-advanceFromLeadingMargin) +
0494: // "; segmentLimit: " + segmentLimit);
0495:
0496: int wrappingWidth = Math.max(formatWidth
0497: - advanceFromLeadingMargin, 0);
0498: TextLayout layout = null;
0499: if (firstSegment || wrappingWidth > 0
0500: || segmentLimit > measurer.getPosition() + 1) {
0501: layout = measurer.nextLayout(wrappingWidth,
0502: segmentLimit, !firstSegment);
0503: }
0504:
0505: if (layout == null) {
0506: if (firstSegment) {
0507: // I doubt this would happen, but check anyway
0508: throw new Error("First layout is null!");
0509: }
0510: break computeTabbedSegments;
0511: }
0512:
0513: final int measurerPos = measurer.getPosition();
0514: if (measurerPos < segmentLimit) {
0515: computeSegs = false;
0516: if (fFlush == FULLY_JUSTIFIED) {
0517: layout = layout.getJustifiedLayout(wrappingWidth);
0518: }
0519: } else {
0520: computeSegs = !(measurerPos == paragraphLimit);
0521: }
0522:
0523: if (firstSegment) {
0524: firstSegment = false;
0525: // Have to get ltr off of layout. Not available from measurer,
0526: // unfortunately.
0527: line.fLeftToRight = layout.isLeftToRight();
0528: }
0529:
0530: BidiSegment segment = new BidiSegment();
0531: segment.fLayout = layout;
0532: int layoutAdvance = (int) Math.ceil(layout.getAdvance());
0533:
0534: // position layout relative to leading margin, update logicalPositionOnLine
0535:
0536: int relativeTabPosition = currentTabStop.getPosition()
0537: - (int) fLeadingMargin;
0538: int logicalPositionOfLayout;
0539: switch (currentTabStop.getType()) {
0540: case TabStop.kTrailing:
0541: logicalPositionOfLayout = Math.max(relativeTabPosition
0542: - layoutAdvance, advanceFromLeadingMargin);
0543: break;
0544: case TabStop.kCenter:
0545: logicalPositionOfLayout = Math
0546: .max(relativeTabPosition - (layoutAdvance / 2),
0547: advanceFromLeadingMargin);
0548: break;
0549: default: // includes decimal tab right now
0550: logicalPositionOfLayout = relativeTabPosition;
0551: break;
0552: }
0553:
0554: // position layout in segment
0555: if (line.fLeftToRight) {
0556: segment.fDistanceFromLeadingMargin = logicalPositionOfLayout;
0557: } else {
0558: segment.fDistanceFromLeadingMargin = logicalPositionOfLayout
0559: + layoutAdvance;
0560: }
0561:
0562: // update advanceFromLeadingMargin
0563: advanceFromLeadingMargin = logicalPositionOfLayout
0564: + layoutAdvance;
0565:
0566: // add segment to segment Vector
0567: line.fSegments.addElement(segment);
0568:
0569: // get next tab
0570: currentTabStop = fTabRuler.nextTab((int) fLeadingMargin
0571: + advanceFromLeadingMargin);
0572: if (currentTabStop.getType() == TabStop.kLeading
0573: || currentTabStop.getType() == TabStop.kAuto) {
0574: advanceFromLeadingMargin = currentTabStop.getPosition();
0575: //System.out.println("Advance from leading margin:" + advanceFromLeadingMargin);
0576:
0577: } else {
0578: //System.out.println("Non-leading tab, type=" + currentTabStop.getType());
0579: }
0580:
0581: } while (computeSegs);
0582:
0583: // Now compute fTotalAdvance, fVisibleAdvance. These metrics may be affected
0584: // by a trailing tab.
0585:
0586: {
0587: BidiSegment lastSegment = (BidiSegment) line.fSegments
0588: .lastElement();
0589: TextLayout lastLayout = lastSegment.fLayout;
0590:
0591: if (line.fLeftToRight) {
0592: line.fTotalAdvance = (int) Math.ceil(lastLayout
0593: .getAdvance())
0594: + lastSegment.fDistanceFromLeadingMargin;
0595: line.fVisibleAdvance = (int) Math.ceil(lastLayout
0596: .getVisibleAdvance())
0597: + lastSegment.fDistanceFromLeadingMargin;
0598: } else {
0599: line.fTotalAdvance = lastSegment.fDistanceFromLeadingMargin;
0600: line.fVisibleAdvance = lastSegment.fDistanceFromLeadingMargin
0601: - (int) Math.ceil(lastLayout.getAdvance()
0602: - lastLayout.getVisibleAdvance());
0603: }
0604:
0605: if (isTab(text.at(measurer.getPosition() - 1))) {
0606: line.fTotalAdvance = Math.max(line.fTotalAdvance,
0607: currentTabStop.getPosition());
0608: }
0609: }
0610: }
0611:
0612: /**
0613: * Return the highlight shape for the given character offsets.
0614: * The Shape returned is relative to the leftmost point on the
0615: * baseline of line.
0616: */
0617: private Shape getHighlightShape(BidiLayoutInfo line,
0618: int lengthBasis, int lineBound, int hlStart, int hlLimit) {
0619:
0620: if (hlStart >= hlLimit) {
0621: throw new IllegalArgumentException(
0622: "Highlight range length is not positive.");
0623: }
0624:
0625: final int leadingMargin = (line.fLeftToRight) ? line.fLeadingMargin
0626: : lineBound - line.fLeadingMargin;
0627: final int segmentCount = line.fSegments.size();
0628:
0629: Shape rval = null;
0630: GeneralPath highlightPath = null;
0631:
0632: int currentLayoutStart = line.getCharStart(lengthBasis);
0633:
0634: for (int i = 0; i < segmentCount; i++) {
0635:
0636: BidiSegment segment = (BidiSegment) line.fSegments
0637: .elementAt(i);
0638: TextLayout layout = segment.fLayout;
0639: int charCount = layout.getCharacterCount();
0640: int currentLayoutLimit = currentLayoutStart + charCount;
0641: boolean rangesIntersect;
0642: if (hlStart <= currentLayoutStart) {
0643: rangesIntersect = hlLimit > currentLayoutStart;
0644: } else {
0645: rangesIntersect = hlStart < currentLayoutLimit;
0646: }
0647:
0648: if (rangesIntersect) {
0649:
0650: Shape currentHl = layout.getLogicalHighlightShape(Math
0651: .max(hlStart - currentLayoutStart, 0), Math
0652: .min(hlLimit - currentLayoutStart, charCount),
0653: segment.fBounds);
0654:
0655: float xTranslate;
0656: if (line.fLeftToRight) {
0657: xTranslate = leadingMargin
0658: + segment.fDistanceFromLeadingMargin;
0659: } else {
0660: xTranslate = leadingMargin
0661: - segment.fDistanceFromLeadingMargin;
0662: }
0663:
0664: if (xTranslate != 0) {
0665: AffineTransform xform = AffineTransform
0666: .getTranslateInstance(xTranslate, 0);
0667: currentHl = xform.createTransformedShape(currentHl);
0668: }
0669:
0670: if (rval == null) {
0671: rval = currentHl;
0672: } else {
0673: if (highlightPath == null) {
0674: highlightPath = new GeneralPath();
0675: highlightPath.append(rval, false);
0676: rval = highlightPath;
0677: }
0678: highlightPath.append(currentHl, false);
0679: }
0680: }
0681: currentLayoutStart = currentLayoutLimit;
0682: }
0683:
0684: return rval;
0685: }
0686:
0687: private void renderWithHighlight(BidiLayoutInfo line,
0688: int lengthBasis, Graphics2D g, int lineBound, int x, int y,
0689: TextOffset selStart, TextOffset selStop,
0690: Color highlightColor) {
0691:
0692: final int lineCharStart = line.getCharStart(lengthBasis);
0693:
0694: if (selStart != null && selStop != null
0695: && !selStart.equals(selStop) && line.fCharLength != 0
0696: && selStart.fOffset < lineCharStart + line.fCharLength
0697: && selStop.fOffset > lineCharStart) {
0698:
0699: Shape highlight = getHighlightShape(line, lengthBasis,
0700: lineBound, selStart.fOffset, selStop.fOffset);
0701: if (highlight != null) {
0702: Graphics2D hl = (Graphics2D) g.create();
0703: hl.setColor(highlightColor);
0704: hl.translate(x, y + line.fAscent);
0705: hl.fill(highlight);
0706: }
0707: }
0708:
0709: render(line, lengthBasis, g, lineBound, x, y);
0710: }
0711:
0712: /**
0713: * Draw the line into the graphics. (x, y) is the upper-left corner
0714: * of the line. The leading edge of a right-aligned line is aligned
0715: * to (x + lineBound).
0716: */
0717: private void render(BidiLayoutInfo line, int lengthBasis,
0718: Graphics2D g, int lineBound, int x, int y) {
0719:
0720: final int leadingMargin = (line.fLeftToRight) ? x
0721: + line.fLeadingMargin : x + lineBound
0722: - line.fLeadingMargin;
0723: final int baseline = y + line.fAscent;
0724: final int segmentCount = line.fSegments.size();
0725:
0726: for (int i = 0; i < segmentCount; i++) {
0727:
0728: BidiSegment segment = (BidiSegment) line.fSegments
0729: .elementAt(i);
0730:
0731: float drawX;
0732: if (line.fLeftToRight) {
0733: drawX = leadingMargin
0734: + segment.fDistanceFromLeadingMargin;
0735: } else {
0736: drawX = leadingMargin
0737: - segment.fDistanceFromLeadingMargin;
0738: }
0739:
0740: segment.fLayout.draw(g, drawX, baseline);
0741: }
0742: }
0743:
0744: private TextOffset hitTestSegment(TextOffset result,
0745: int segmentCharStart, BidiSegment segment, int xInSegment,
0746: int yInSegment) {
0747:
0748: final TextLayout layout = segment.fLayout;
0749: final int charCount = layout.getCharacterCount();
0750: final int layoutAdvance = (int) Math.ceil(layout.getAdvance());
0751: Rectangle2D bounds = segment.fBounds;
0752:
0753: final boolean ltr = layout.isLeftToRight();
0754:
0755: if (ltr && (xInSegment >= layoutAdvance) || !ltr
0756: && (xInSegment <= 0)) {
0757:
0758: // pretend the extra space at the end of the line is a
0759: // tab and 'hit-test' it.
0760: double tabCenter;
0761: if (ltr) {
0762: tabCenter = (layoutAdvance + bounds.getMaxX()) / 2;
0763: } else {
0764: tabCenter = bounds.getX() / 2;
0765: }
0766:
0767: if ((xInSegment >= tabCenter) == ltr) {
0768: result.fOffset = charCount;
0769: result.fPlacement = TextOffset.BEFORE_OFFSET;
0770: } else {
0771: result.fOffset = charCount - 1;
0772: result.fPlacement = TextOffset.AFTER_OFFSET;
0773: }
0774: } else {
0775: TextHitInfo info = layout.hitTestChar(xInSegment,
0776: yInSegment, segment.fBounds);
0777: result.fOffset = info.getInsertionIndex();
0778: if (result.fOffset == 0) {
0779: result.fPlacement = TextOffset.AFTER_OFFSET;
0780: } else if (result.fOffset == charCount) {
0781: result.fPlacement = TextOffset.BEFORE_OFFSET;
0782: } else {
0783: result.fPlacement = info.isLeadingEdge() ? TextOffset.AFTER_OFFSET
0784: : TextOffset.BEFORE_OFFSET;
0785: }
0786: }
0787:
0788: result.fOffset += segmentCharStart;
0789: return result;
0790: }
0791:
0792: /**
0793: * Return the offset at the point (x, y). (x, y) is relative to the top-left
0794: * of the line. The leading edge of a right-aligned line is aligned
0795: * to lineBound.
0796: */
0797: private TextOffset pixelToOffset(BidiLayoutInfo line,
0798: int lengthBasis, TextOffset result, int lineBound, int x,
0799: int y) {
0800:
0801: if (result == null) {
0802: result = new TextOffset();
0803: }
0804:
0805: final int yInSegment = y - line.fAscent;
0806: final int leadingMargin = (line.fLeftToRight) ? line.fLeadingMargin
0807: : lineBound - line.fLeadingMargin;
0808: final int lineCharStart = line.getCharStart(lengthBasis);
0809:
0810: // first see if point is before leading edge of line
0811: final int segmentCount = line.fSegments.size();
0812: {
0813: int segLeadingMargin = leadingMargin;
0814: if (segmentCount > 0) {
0815: BidiSegment firstSeg = (BidiSegment) line.fSegments
0816: .elementAt(0);
0817: if (line.fLeftToRight) {
0818: segLeadingMargin += firstSeg.fDistanceFromLeadingMargin;
0819: } else {
0820: segLeadingMargin -= firstSeg.fDistanceFromLeadingMargin;
0821: segLeadingMargin += (float) firstSeg.fBounds
0822: .getMaxX();
0823: }
0824: }
0825: if (line.fLeftToRight == (x <= segLeadingMargin)) {
0826: result.fOffset = lineCharStart;
0827: result.fPlacement = TextOffset.AFTER_OFFSET;
0828: return result;
0829: }
0830: }
0831:
0832: int segmentCharStart = lineCharStart;
0833:
0834: for (int i = 0; i < segmentCount; i++) {
0835:
0836: BidiSegment segment = (BidiSegment) line.fSegments
0837: .elementAt(i);
0838: int segmentOrigin = line.fLeftToRight ? leadingMargin
0839: + segment.fDistanceFromLeadingMargin
0840: : leadingMargin
0841: - segment.fDistanceFromLeadingMargin;
0842: int xInSegment = x - segmentOrigin;
0843: if (line.fLeftToRight) {
0844: if (segment.fBounds.getMaxX() > xInSegment) {
0845: return hitTestSegment(result, segmentCharStart,
0846: segment, xInSegment, yInSegment);
0847: }
0848: } else {
0849: if (segment.fBounds.getX() < xInSegment) {
0850: return hitTestSegment(result, segmentCharStart,
0851: segment, xInSegment, yInSegment);
0852: }
0853: }
0854: segmentCharStart += segment.fLayout.getCharacterCount();
0855: }
0856:
0857: result.fOffset = lineCharStart + line.fCharLength;
0858: result.fPlacement = TextOffset.BEFORE_OFFSET;
0859: return result;
0860: }
0861:
0862: private void renderCaret(BidiLayoutInfo line, MConstText text,
0863: int lengthBasis, Graphics2D g, int lineBound, int x, int y,
0864: final int charOffset, Color strongCaretColor,
0865: Color weakCaretColor) {
0866: final int segmentCount = line.fSegments.size();
0867: final int lineStart = line.getCharStart(lengthBasis);
0868:
0869: int currentStart = lineStart;
0870: BidiSegment segment = null;
0871: int segmentIndex;
0872:
0873: for (segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
0874: segment = (BidiSegment) line.fSegments
0875: .elementAt(segmentIndex);
0876: int currentEndpoint = currentStart
0877: + segment.fLayout.getCharacterCount();
0878: if (currentEndpoint > charOffset) {
0879: break;
0880: }
0881: currentStart = currentEndpoint;
0882: }
0883:
0884: /*
0885: There are two choices here:
0886: 1. get carets from a TextLayout and render them, or
0887: 2. make up a caret ourselves and render it.
0888: We want to do 2 when:
0889: * there is no text on the line, or
0890: * the line ends with a tab and we are drawing the last caret on the line
0891: Otherwise, we want 1.
0892: */
0893:
0894: if (segmentIndex == segmentCount && segmentCount > 0) {
0895: // If we get here, line length is not 0, and charOffset is at end of line
0896: if (!isTab(text.at(charOffset - 1))) {
0897: segmentIndex = segmentCount - 1;
0898: segment = (BidiSegment) line.fSegments
0899: .elementAt(segmentIndex);
0900: currentStart = lineStart + line.getCharLength()
0901: - segment.fLayout.getCharacterCount();
0902: }
0903: }
0904:
0905: Object savedPaint = Graphics2DConversion.getColorState(g);
0906:
0907: try {
0908: if (segmentIndex < segmentCount) {
0909: TextLayout layout = segment.fLayout;
0910: int offsetInLayout = charOffset - currentStart;
0911: Shape[] carets = layout.getCaretShapes(offsetInLayout,
0912: segment.fBounds);
0913: g.setColor(strongCaretColor);
0914: int layoutPos = line.fLeadingMargin
0915: + segment.fDistanceFromLeadingMargin;
0916: int layoutX = line.fLeftToRight ? x + layoutPos : x
0917: + lineBound - layoutPos;
0918: int layoutY = y + line.fAscent;
0919:
0920: // Translating and then clipping doesn't work. Try this:
0921: Rectangle2D.Float clipRect = new Rectangle2D.Float();
0922: clipRect.setRect(segment.fBounds);
0923: clipRect.x += layoutX;
0924: clipRect.y += layoutY;
0925: clipRect.width += 1;
0926: clipRect.height -= 1;
0927:
0928: Object savedClip = ClipWorkaround.saveClipState(g);
0929: try {
0930: ClipWorkaround.translateAndDrawShapeWithClip(g,
0931: layoutX, layoutY, clipRect, carets[0]);
0932: if (carets[1] != null) {
0933: g.setColor(weakCaretColor);
0934: ClipWorkaround.translateAndDrawShapeWithClip(g,
0935: layoutX, layoutY, clipRect, carets[1]);
0936: }
0937: } finally {
0938: ClipWorkaround.restoreClipState(g, savedClip);
0939: }
0940: } else {
0941: int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
0942: int endX = line.fLeftToRight ? lineEnd : lineBound
0943: - lineEnd;
0944: endX += x;
0945: g.drawLine(endX, y, endX, y + line.getHeight() - 1);
0946: }
0947: } finally {
0948: Graphics2DConversion.restoreColorState(g, savedPaint);
0949: }
0950: }
0951:
0952: private Rectangle caretBounds(BidiLayoutInfo line, MConstText text,
0953: int lengthBasis, int lineBound, int charOffset, int x, int y) {
0954:
0955: final int segmentCount = line.fSegments.size();
0956: final int lineStart = line.getCharStart(lengthBasis);
0957: int currentStart = lineStart;
0958: BidiSegment segment = null;
0959: int segmentIndex;
0960:
0961: for (segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
0962: segment = (BidiSegment) line.fSegments
0963: .elementAt(segmentIndex);
0964: int currentEndpoint = currentStart
0965: + segment.fLayout.getCharacterCount();
0966: if (currentEndpoint > charOffset) {
0967: break;
0968: }
0969: currentStart = currentEndpoint;
0970: }
0971:
0972: if (segmentIndex == segmentCount && segmentCount > 0) {
0973: // If we get here, line length is not 0, and charOffset is at end of line
0974: if (!isTab(text.at(charOffset - 1))) {
0975: segmentIndex = segmentCount - 1;
0976: segment = (BidiSegment) line.fSegments
0977: .elementAt(segmentIndex);
0978: currentStart = lineStart + line.getCharLength()
0979: - segment.fLayout.getCharacterCount();
0980: }
0981: }
0982:
0983: Rectangle r;
0984:
0985: if (segmentIndex < segmentCount) {
0986: TextLayout layout = segment.fLayout;
0987: int offsetInLayout = charOffset - currentStart;
0988: Shape[] carets = layout.getCaretShapes(offsetInLayout,
0989: segment.fBounds);
0990: r = carets[0].getBounds();
0991: if (carets[1] != null) {
0992: r.add(carets[1].getBounds());
0993: }
0994: r.width += 1;
0995:
0996: int layoutPos = line.fLeadingMargin
0997: + segment.fDistanceFromLeadingMargin;
0998: if (line.fLeftToRight) {
0999: r.x += layoutPos;
1000: } else {
1001: r.x += lineBound - layoutPos;
1002: }
1003: r.y += line.fAscent;
1004: } else {
1005: r = new Rectangle();
1006: r.height = line.getHeight();
1007: r.width = 1;
1008: int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
1009: if (line.fLeftToRight) {
1010: r.x = lineEnd;
1011: } else {
1012: r.x = lineBound - lineEnd;
1013: }
1014: }
1015:
1016: r.translate(x, y);
1017: return r;
1018: }
1019:
1020: private int strongCaretBaselinePosition(BidiLayoutInfo line,
1021: int lengthBasis, int lineBound, int charOffset) {
1022:
1023: final int segmentCount = line.fSegments.size();
1024: int currentStart = line.getCharStart(lengthBasis);
1025: BidiSegment segment = null;
1026: int segmentIndex;
1027:
1028: for (segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
1029: segment = (BidiSegment) line.fSegments
1030: .elementAt(segmentIndex);
1031: int currentEndpoint = currentStart
1032: + segment.fLayout.getCharacterCount();
1033: if (currentEndpoint > charOffset) {
1034: break;
1035: }
1036: currentStart = currentEndpoint;
1037: }
1038:
1039: if (segmentIndex < segmentCount) {
1040: TextLayout layout = segment.fLayout;
1041: int offsetInLayout = charOffset - currentStart;
1042: TextHitInfo hit = TextHitInfo.afterOffset(offsetInLayout);
1043: hit = TextLayout.DEFAULT_CARET_POLICY.getStrongCaret(hit,
1044: hit.getOtherHit(), layout);
1045: float[] info = layout.getCaretInfo(hit);
1046: int layoutPos = line.fLeadingMargin
1047: + segment.fDistanceFromLeadingMargin;
1048: if (line.fLeftToRight) {
1049: return layoutPos + (int) info[0];
1050: } else {
1051: return lineBound - layoutPos + (int) info[0];
1052: }
1053: } else {
1054: int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
1055: if (line.fLeftToRight) {
1056: return lineEnd;
1057: } else {
1058: return lineBound - lineEnd;
1059: }
1060: }
1061: }
1062:
1063: private int getNextOffset(BidiLayoutInfo line, int lengthBasis,
1064: int charOffset, short dir) {
1065:
1066: if (dir != MFormatter.eLeft && dir != MFormatter.eRight) {
1067: throw new IllegalArgumentException("Invalid direction.");
1068: }
1069:
1070: // find segment containing offset:
1071: final int segmentCount = line.fSegments.size();
1072: final int lineCharStart = line.getCharStart(lengthBasis);
1073:
1074: int currentStart = lineCharStart;
1075: BidiSegment segment = null;
1076: int segmentIndex;
1077:
1078: for (segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
1079: segment = (BidiSegment) line.fSegments
1080: .elementAt(segmentIndex);
1081: int currentEndpoint = currentStart
1082: + segment.fLayout.getCharacterCount();
1083: if (currentEndpoint > charOffset
1084: || (segmentIndex == segmentCount - 1 && currentEndpoint == charOffset)) {
1085: break;
1086: }
1087: currentStart = currentEndpoint;
1088: }
1089:
1090: final boolean logAdvance = (dir == MFormatter.eRight) == (line.fLeftToRight);
1091:
1092: int result;
1093:
1094: if (segmentIndex < segmentCount) {
1095: TextLayout layout = segment.fLayout;
1096: int offsetInLayout = charOffset - currentStart;
1097: TextHitInfo hit = (dir == MFormatter.eLeft) ? layout
1098: .getNextLeftHit(offsetInLayout) : layout
1099: .getNextRightHit(offsetInLayout);
1100: if (hit == null) {
1101: result = logAdvance ? currentStart
1102: + layout.getCharacterCount() + 1
1103: : currentStart - 1;
1104: } else {
1105: result = hit.getInsertionIndex() + currentStart;
1106: }
1107: } else {
1108: result = logAdvance ? lineCharStart + line.fCharLength + 1
1109: : lineCharStart - 1;
1110: }
1111:
1112: return result;
1113: }
1114: }
|