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: // Revision: 70 1.38 richtext/AsyncFormatter.java, richtext, richtext
0015: package com.ibm.richtext.textformat;
0016:
0017: import java.awt.Graphics;
0018: import java.awt.Rectangle;
0019: import java.awt.Point;
0020: import java.awt.Color;
0021:
0022: import java.text.BreakIterator;
0023: import java.text.CharacterIterator;
0024:
0025: import java.util.Hashtable;
0026:
0027: import com.ibm.richtext.textlayout.attributes.AttributeMap;
0028: import com.ibm.richtext.textlayout.attributes.TextAttribute;
0029:
0030: import com.ibm.richtext.styledtext.MConstText;
0031:
0032: import com.ibm.richtext.textlayout.Graphics2DConversion;
0033:
0034: ///*JDK12IMPORTS
0035: import java.awt.Graphics2D;
0036: import java.awt.font.LineBreakMeasurer;
0037: import java.awt.font.FontRenderContext;
0038:
0039: //JDK12IMPORTS*/
0040:
0041: /*JDK11IMPORTS
0042: import com.ibm.richtext.textlayout.Graphics2D;
0043: import com.ibm.richtext.textlayout.LineBreakMeasurer;
0044: import com.ibm.richtext.textlayout.FontRenderContext;
0045: JDK11IMPORTS*/
0046:
0047: /*
0048: Change history:
0049:
0050: 7/25/96 -
0051:
0052: 8/15/96
0053: Fixed bug in textOffsetToPoint (fPixHeight wasn't added to negative heights). {jbr}
0054:
0055: 8/19/96
0056: Removed references to JustificationStyle constants, and moved them into MFormatter {sfb}
0057:
0058: 8/23/96
0059: Modified findLineAt and getLineContaining - added optimization in search loop. Also,
0060: they were failing when fLTPosEnd+1 == fLTNegStart. Fixed.
0061:
0062: 8/26/96
0063: Moved FormatDaemon stuff into this class.
0064:
0065: 9/11/96
0066: Shortened line returned from textOffsetToPoint by 1 pixel
0067:
0068: 9/23/96
0069: textOffsetToPoint line length restored (see above). drawText() now draws only lines
0070: which fall in rectangle param.
0071:
0072: 9/26/96
0073: whitespace at end of line is used for caret positioning
0074:
0075: 10/4/96
0076: Added static TextBox method.
0077:
0078: 10/8/96
0079: Line 1300 - less than changed to less than or equal in textOffsetToPoint. Watch for
0080: hangs in formatText.
0081:
0082: 10/9/96
0083: Changed sync. model. fFormatInBackground is used to start/stop bg formatting
0084:
0085: 10/17/96
0086: Added new flags: fLineInc, fFillInc for bidi support. updateFormat, pointToTextOffset, getBoundingRect,
0087: and findNewInsertionOffset should now function correctly for non-Roman documents. Nothing else has been
0088: modified to support intl text.
0089:
0090: 10/21/96
0091: Pushed paragraph formatting and caret positioning into LineLayout. In process of pushing
0092: highlighting into LineLayout.
0093:
0094: 10/24/96
0095: Now getting paragraph styles from paragraph buffer.
0096:
0097: 7/7/97
0098: Up-arrow doesn't move to beginning of text if you're on the first line.
0099:
0100: 7/1/98
0101: No longer intersecting damaged rect with view rect in updateFormat.
0102: */
0103:
0104: /**
0105: * This class implements MFormatter. It maintains a table of
0106: * <tt>LayoutInfo</tt> instances which contain layout information
0107: * for each line in the text. This class formats lines on demand,
0108: * and creates a low-priority thread to format text in the background.
0109: * Note that, at times, some text may not have been formatted, especially
0110: * if the text is large.
0111: * <p>
0112: * The line table is an array of <tt>LayoutInfo</tt> objects, which expands as needed to hold
0113: * all computed lines in the text. The line table consists of three
0114: * regions: a "positive" region, a "gap," and a "negative" region.
0115: * In the positive region, character and graphics offsets are positive
0116: * integers, computed from the beginning of the text / display. In the
0117: * gap, line table entries are null. New lines may be inserted into the gap.
0118: * In the negative region, character and graphics offsets are negative;
0119: * their absolute values indicate distances from the end of the text / display.
0120: * The <tt>fLTPosEnd</tt> member gives the index in the line table of the
0121: * last positive entry. The <tt>fLTNegStart</tt> gives the index of first
0122: * negative entry. If there are no negative entries, <tt>fLTNegStart</tt> is
0123: * equal to <tt>fLTSize</tt>, the size of the line table.
0124: * <p>
0125: * Changes to the line table occur only in the <tt>formatText()</tt> method.
0126: * This method calls <tt>LineLayout.layout()</tt> for each line to format.
0127: *
0128: * @author John Raley
0129: *
0130: * @see MFormatter
0131: * @see LineLayout
0132: * @see LayoutContext
0133: * @see LayoutInfo
0134: */
0135:
0136: final class AsyncFormatter extends MFormatter implements Runnable {
0137: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
0138: /**
0139: * Text to format.
0140: */
0141: private MConstText fText;
0142:
0143: /**
0144: * Default values.
0145: */
0146: private AttributeMap fDefaultValues;
0147:
0148: /**
0149: * Font resolver.
0150: */
0151: private FontResolver fFontResolver;
0152:
0153: /**
0154: * Default character metric - used to set height of empty paragraphs
0155: */
0156: private DefaultCharacterMetric fDefaultCharMetric;
0157:
0158: /**
0159: * Length to which lines are formatted.
0160: */
0161: private int fLineDim;
0162:
0163: /**
0164: * Table of formatted lines.
0165: */
0166: private LayoutInfo fLineTable[];
0167:
0168: /**
0169: * Size of line table.
0170: */
0171: private int fLTSize = 10; // initial size must be > 0
0172:
0173: /**
0174: * Index of last positive entry in line table.
0175: */
0176: private int fLTPosEnd;
0177:
0178: /**
0179: * Index of first negative entry in line table.
0180: */
0181: private int fLTNegStart;
0182:
0183: /**
0184: * Length of text on which negative line offsets are based.
0185: * @see #formatText
0186: */
0187: private int fLTCurTextLen;
0188:
0189: /**
0190: * Length of formatted text in fill direction, in pixels.
0191: */
0192: private int fPixHeight;
0193:
0194: /**
0195: * Length of formatted text including pseudoline.
0196: */
0197: private int fFullPixHeight;
0198:
0199: private int fMinX;
0200: private int fMaxX;
0201:
0202: /**
0203: * <tt>true</tt> if lines should be formatted to fit line dimension.
0204: */
0205: private boolean fWrap;
0206:
0207: /**
0208: * <tt>true</tt> if characters run horizontally.
0209: */
0210: private boolean fHLine = true;
0211:
0212: /**
0213: * <tt>true</tt> if characters run from from low to high coordinates on line.
0214: */
0215: private boolean fLineInc = true;
0216:
0217: /**
0218: * <tt>true</tt> if lines run from low to high coordinates within page.
0219: */
0220: private boolean fFillInc = true;
0221:
0222: /**
0223: * Value returned from <tt>findLineAt()</tt>
0224: * if pixel height precedes topmost line.
0225: */
0226: private static final int kBeforeFirstLine = -2;
0227:
0228: /**
0229: * Value returned from <tt>findLineAt()</tt> and <tt>getLineContaining()</tt>
0230: * if offset / pixel height is after all existing lines.
0231: */
0232: private static final int kAfterLastLine = -1;
0233:
0234: /**
0235: * Thread which invokes formatter in the background.
0236: */
0237: private Thread fDaemon;
0238:
0239: /**
0240: * FontRenderContext to measure with. Currently not settable after
0241: * construction.
0242: */
0243: private FontRenderContext fFontRenderContext;
0244:
0245: /**
0246: * Controls whether background formatting can run.
0247: */
0248: private boolean fBgFormatAllowed = false;
0249:
0250: /**
0251: * Cached line break object.
0252: */
0253: private BreakIterator fLineBreak = null;
0254:
0255: /**
0256: * Cached LineBreakMeasurer.
0257: */
0258: private LineBreakMeasurer fCachedMeasurer = null;
0259: private int fCachedMeasurerStart;
0260: private int fCachedMeasurerLimit;
0261:
0262: // Some JDK's (Sun's 1.2.2) throw exceptions from
0263: // LineBreakMeasurer.insertChar and deleteChar. This class
0264: // detects this condition and doesn't use these two methods
0265: // if they throw exceptions.
0266: private static boolean fgCacheMeasurers = true;
0267:
0268: /**
0269: * Current text time stamp. Used to maintain modification invariant.
0270: */
0271: private int fCurTimeStamp;
0272:
0273: /**
0274: * Cache of ParagraphRenderers.
0275: */
0276: private Hashtable fRendererCache = new Hashtable();
0277:
0278: /**
0279: * Draw text inside a rectangle. Does not cache any formatting information.
0280: * This is convenient for small amounts of text; comparable to TETextBox on the Mac.
0281: * <p>
0282: * @param text the text to draw
0283: * @param g Graphics on which to draw
0284: * @param drawRect rectangle in which text will be drawn
0285: * @param fillInc if true, lines run from low to high coordinates in page.
0286: * @param hLine if true, characters run horizontally within a line.
0287: */
0288:
0289: private static boolean isParagraphSeparator(char ch) {
0290:
0291: return ch == '\n' || ch == '\u2029';
0292: }
0293:
0294: /**
0295: * Create an <tt>AsyncFormatter</tt>.
0296: * @param text the text to format
0297: * @param lineBound length to which lines are foramtted
0298: * @param wrap <tt>true</tt> if text should be "line wrapped" (formatted to fit destination area)
0299: */
0300: AsyncFormatter(MConstText text, AttributeMap defaultValues,
0301: int lineBound, boolean wrap, Graphics g) {
0302: fText = text;
0303: fDefaultValues = defaultValues;
0304: fFontResolver = new FontResolver(fDefaultValues);
0305:
0306: fLineDim = lineBound;
0307: fWrap = wrap;
0308: Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);
0309: fFontRenderContext = g2d.getFontRenderContext();
0310:
0311: fDefaultCharMetric = new DefaultCharacterMetric(fFontResolver,
0312: fFontRenderContext);
0313: fLTCurTextLen = text.length();
0314: removeAllLines();
0315:
0316: fDaemon = new Thread(this );
0317: fDaemon.start();
0318: }
0319:
0320: public AttributeMap getDefaultValues() {
0321:
0322: return fDefaultValues;
0323: }
0324:
0325: public void checkTimeStamp() {
0326: String admonition = "Probably, you modified "
0327: + "the text before calling stopBackgroundFormatting().";
0328:
0329: if (fText.getTimeStamp() != fCurTimeStamp) {
0330: throw new Error("Time stamp is out of sync. " + admonition);
0331: }
0332: if (fText.length() != fLTCurTextLen) {
0333: throw new Error("Length changed unexpectedly. "
0334: + "fText.length()=" + fText.length() + "; "
0335: + "fLTCurTextLen=" + fLTCurTextLen + "; "
0336: + "formatter=" + this + "; " + "text=" + fText);
0337: }
0338: }
0339:
0340: /**
0341: * Specify whether to wrap lines using the line dimension.
0342: * @param wrap if <tt>true</tt> lines will be wrapped; otherwise new lines will only be
0343: * started when a newline is encountered.
0344: */
0345: public synchronized void setWrap(boolean wrap) {
0346: if (wrap != fWrap) {
0347: fWrap = wrap;
0348: removeAllLines();
0349: }
0350: }
0351:
0352: /**
0353: * Return true if lines are wrapped using the line dimension.
0354: * @see #setWrap
0355: */
0356: public synchronized boolean wrap() {
0357: return fWrap;
0358: }
0359:
0360: /**
0361: * Specify the lineBound in pixels. If line wrapping is on, lines
0362: * will be wrapped to this value.
0363: * <p>
0364: *
0365: * @param lineBound the distance, in pixels, used to wrap lines.
0366: */
0367: public synchronized void setLineBound(int lineBound) {
0368: if (fLineDim != lineBound) {
0369: fLineDim = lineBound;
0370: if (fWrap) {
0371: removeAllLines();
0372: }
0373: }
0374: }
0375:
0376: /**
0377: * Return the number of pixels along the line dimension.
0378: */
0379: public synchronized int lineBound() {
0380: return fLineDim;
0381: }
0382:
0383: /**
0384: * Return <tt>true</tt> if characters run from low to high coordinates on line.
0385: */
0386: private boolean lineInc() {
0387: return fLineInc;
0388: }
0389:
0390: /**
0391: * Return <tt>true</tt> if lines run from low to high coordinates on page.
0392: */
0393: private boolean fillInc() {
0394: return fFillInc;
0395: }
0396:
0397: /**
0398: * Remove all lines in the line table. Used after an operation that
0399: * invalidates all existing lines, such as changing line wrapping or the
0400: * line dim.
0401: */
0402: private synchronized void removeAllLines() {
0403: fCurTimeStamp = fText.getTimeStamp();
0404: stopBackgroundFormatting();
0405:
0406: fMinX = 0;
0407: fMaxX = fLineDim;
0408:
0409: fLineTable = new LayoutInfo[fLTSize]; // fLTSize must be > 0
0410: fLTNegStart = fLTSize;
0411: fLTPosEnd = 0;
0412:
0413: fLineTable[0] = pseudoLineInfo(null, 0);
0414:
0415: fPixHeight = fLineTable[0].getHeight(); // ??? or should it be zero?
0416: fFullPixHeight = fPixHeight;
0417:
0418: // format at least one line:
0419: formatToHeight(fPixHeight + 1);
0420:
0421: enableBGFormat();
0422: }
0423:
0424: /**
0425: * Fill the layout info with information appropriate to the pseudoline.
0426: */
0427: private synchronized LayoutInfo pseudoLineInfo(LayoutInfo info,
0428: int offset) {
0429: AttributeMap st = fText.paragraphStyleAt(fLTCurTextLen); // ??? if text is empty or this is the end of the text, what happens?
0430: ParagraphRenderer renderer = getRendererFor(st);
0431: info = renderer.layout(fText, info, (LineBreakMeasurer) null,
0432: fFontRenderContext, offset, offset, fLineDim, fLineDim);
0433:
0434: return info;
0435: }
0436:
0437: /**
0438: * Return the index of the last valid line in the line table.
0439: */
0440: private int lastLine() {
0441: return (fLTNegStart == fLTSize) ? fLTPosEnd : fLTSize - 1;
0442: }
0443:
0444: /**
0445: * Shift line table such that <tt>lastPos</tt> is the last positive
0446: * entry in the table. <b>NOTE: <tt>lastPos</tt> must be a valid line!</b>
0447: * <p>
0448: * @param lastPos the index of the line which will become the last positive
0449: * entry in the line table
0450: */
0451: private void shiftTableTo(int lastPos) {
0452: LayoutInfo li;
0453:
0454: while (lastPos < fLTPosEnd) { // shift +'s to -'s
0455: li = fLineTable[fLTPosEnd];
0456: fLineTable[fLTPosEnd--] = null;
0457:
0458: li.makeRelativeToEnd(fLTCurTextLen, fPixHeight);
0459:
0460: fLineTable[--fLTNegStart] = li;
0461: }
0462:
0463: while (lastPos >= fLTNegStart) { // shift -'s to +'s
0464: li = fLineTable[fLTNegStart];
0465: fLineTable[fLTNegStart++] = null;
0466:
0467: li.makeRelativeToBeginning(fLTCurTextLen, fPixHeight);
0468:
0469: fLineTable[++fLTPosEnd] = li;
0470: }
0471: }
0472:
0473: /**
0474: * Increase the size of the line table.
0475: */
0476: private void expandLineTable() {
0477: // This just doubles the size of the line table.
0478:
0479: LayoutInfo newLineTable[] = new LayoutInfo[fLineTable.length * 2];
0480: int newNegStart = newLineTable.length
0481: - (fLineTable.length - fLTNegStart);
0482:
0483: System.arraycopy(fLineTable, 0, newLineTable, 0, fLTPosEnd + 1);
0484: System.arraycopy(fLineTable, fLTNegStart, newLineTable,
0485: newNegStart, fLTSize - fLTNegStart);
0486:
0487: fLTNegStart = newNegStart;
0488: fLTSize = newLineTable.length;
0489: fLineTable = newLineTable;
0490: }
0491:
0492: /**
0493: * Return the index of the line containing the pixel position <tt>fillCoord</tt>.
0494: * If fillCoord exceeds the bottom of the text, return kAfterLastLine.
0495: * If fillCoord is less than the top of the text, return kBeforeFirstLine.
0496: * <p>
0497: * @param fillCoord "height" of line to locate.
0498: */
0499: private int findLineAt(int fillCoord) {
0500: int low, high, mid;
0501: int lowStart, highStart, midStart;
0502:
0503: if (fillCoord >= fPixHeight)
0504: return kAfterLastLine;
0505: else if (fillCoord < 0)
0506: return kBeforeFirstLine;
0507:
0508: if ((fLTNegStart < fLTSize)
0509: && (fillCoord >= fLineTable[fLTNegStart]
0510: .getGraphicStart(fPixHeight))) {
0511: fillCoord -= fPixHeight;
0512:
0513: low = fLTNegStart;
0514: high = fLTSize;
0515: highStart = 0;
0516: } else {
0517: low = 0;
0518: high = fLTPosEnd + 1;
0519: highStart = fLineTable[fLTPosEnd].getGraphicStart(0)
0520: + fLineTable[fLTPosEnd].getHeight();
0521: }
0522: lowStart = fLineTable[low].getGraphicStart(0);
0523:
0524: do {
0525: if (lowStart == highStart)
0526: return low;
0527:
0528: mid = low + (fillCoord - lowStart) / (highStart - lowStart)
0529: * (high - low);
0530: midStart = fLineTable[mid].getGraphicStart(0);
0531:
0532: if (midStart > fillCoord) {
0533: high = mid;
0534: highStart = fLineTable[high].getGraphicStart(0);
0535: } else if (midStart + fLineTable[mid].getHeight() <= fillCoord) {
0536: low = mid + 1;
0537: lowStart = fLineTable[low].getGraphicStart(0);
0538: } else
0539: return mid;
0540:
0541: } while (low < high);
0542:
0543: return 0;
0544: }
0545:
0546: /**
0547: * Return the index of the first character in the line.
0548: * @param line the internal index of the line (direct index into linetable).
0549: */
0550: private int lineCharStartInternal(int line) {
0551: return fLineTable[line].getCharStart(fLTCurTextLen);
0552: }
0553:
0554: /**
0555: * Return the index of the character following the last character in the line.
0556: * @param line the internal index of the line (direct index into linetable).
0557: */
0558: private int lineCharLimitInternal(int line) {
0559: return lineCharStartInternal(line)
0560: + fLineTable[line].getCharLength();
0561: }
0562:
0563: /**
0564: * Return the graphic start of the line, unadjusted for fill direction.
0565: * @param line the internal index of the line (direct index into linetable).
0566: */
0567: private int lineGraphicStartInternal(int line) {
0568: return fLineTable[line].getGraphicStart(fPixHeight);
0569: }
0570:
0571: /**
0572: * Return the graphic limit of the line, unadjusted for fill direction.
0573: * @param line the internal index of the line (direct index into linetable).
0574: */
0575: private int lineGraphicLimitInternal(int line) {
0576: return lineGraphicStartInternal(line)
0577: + fLineTable[line].getHeight();
0578: }
0579:
0580: /**
0581: * Return the offset of the first character which has not been formatted.
0582: * If all text has been formatted, return the current text length.
0583: */
0584: private int lastLineCharStop() {
0585: return lineCharLimitInternal(lastLine());
0586: }
0587:
0588: /**
0589: * Return a 'valid' line containing offset. This differs from getLineContaining in
0590: * this maps kAfterLastLine to lastLine(), so that the result is always a valid
0591: * linetable index.
0592: */
0593: private int getValidLineContaining(TextOffset offset) {
0594:
0595: return getValidLineContaining(offset.fOffset, offset.fPlacement);
0596: }
0597:
0598: /**
0599: * Return a 'valid' line containing offset. This differs from getLineContaining in
0600: * this maps kAfterLastLine to lastLine(), so that the result is always a valid
0601: * linetable index.
0602: */
0603: private int getValidLineContaining(int insOffset, boolean placement) {
0604: int line = getLineContaining(insOffset, placement);
0605: if (line == kAfterLastLine)
0606: line = lastLine();
0607: else if (line == kBeforeFirstLine)
0608: throw new IllegalArgumentException(
0609: "Debug: getLineContaining returned kBeforeFirstLine");
0610:
0611: return line;
0612: }
0613:
0614: /**
0615: * Return index of line containing <tt>offset</tt>.
0616: * ??? If offset is after last formatted line, returns kAfterLastLine. Is that good?
0617: * <p>
0618: * @param offset the offset whose line should be located
0619: * @returns line containing <tt>offset</tt>
0620: */
0621: private int getLineContaining(TextOffset offset) {
0622:
0623: return getLineContaining(offset.fOffset, offset.fPlacement);
0624: }
0625:
0626: private int getLineContaining(int insOffset, boolean placement) {
0627: int pos = insOffset;
0628: if (placement == TextOffset.BEFORE_OFFSET && pos > 0)
0629: --pos;
0630:
0631: if (pos < 0) {
0632: throw new IllegalArgumentException(
0633: "Debug: getLineContaining offset < 0: " + pos);
0634: }
0635:
0636: if (pos >= lastLineCharStop()) {
0637: return emptyParagraphAtEndOfText() ? kAfterLastLine
0638: : lastLine();
0639: }
0640:
0641: int low, high, mid;
0642: int lowStart, highStart, midStart;
0643:
0644: if ((fLTNegStart < fLTSize)
0645: && (pos >= fLineTable[fLTNegStart]
0646: .getCharStart(fLTCurTextLen))) {
0647: pos -= fLTCurTextLen;
0648:
0649: low = fLTNegStart;
0650: high = fLTSize;
0651: highStart = 0;
0652: } else {
0653: low = 0;
0654: high = fLTPosEnd + 1;
0655: highStart = fLineTable[fLTPosEnd].getCharStart(0)
0656: + fLineTable[fLTPosEnd].getCharLength();
0657: }
0658: lowStart = fLineTable[low].getCharStart(0);
0659:
0660: do {
0661: if (highStart == lowStart) {
0662: return low;
0663: }
0664:
0665: mid = low + (pos - lowStart) / (highStart - lowStart)
0666: * (high - low);
0667: midStart = fLineTable[mid].getCharStart(0);
0668:
0669: if (midStart > pos) {
0670: high = mid;
0671: highStart = fLineTable[high].getCharStart(0);
0672: } else if (midStart + fLineTable[mid].getCharLength() <= pos) {
0673: low = mid + 1;
0674: lowStart = fLineTable[low].getCharStart(0);
0675: } else {
0676: return mid;
0677: }
0678:
0679: } while (low < high);
0680:
0681: return 0;
0682: }
0683:
0684: /**
0685: * Display text in drawArea. Does not reformat text.
0686: * <p>
0687: * @param g the Graphics object in which to draw
0688: * @param drawArea the rectangle, in g's coordinate system, in which to draw
0689: * @param origin the top-left corner of the text, in g's coordinate system
0690: */
0691: public synchronized void draw(Graphics g, Rectangle drawArea,
0692: Point origin) {
0693: draw(g, drawArea, origin, null, null, null);
0694: }
0695:
0696: public synchronized void draw(Graphics g, Rectangle drawArea,
0697: Point origin, TextOffset selStart, TextOffset selStop,
0698: Color highlight) {
0699:
0700: checkTimeStamp();
0701: Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);
0702:
0703: // Get starting and ending fill 'heights.'
0704:
0705: int startFill;
0706: int endFill;
0707:
0708: if (fFillInc)
0709: startFill = drawArea.y - origin.y;
0710: else
0711: startFill = origin.y - (drawArea.y + drawArea.height);
0712:
0713: endFill = startFill + drawArea.height;
0714:
0715: // We're drawing one more line than necessary when we update because of a
0716: // selection change. But we're drawing the right amount of lines when we
0717: // refresh the whole display. This affects rendering speed significantly,
0718: // and creating a new paragraph renderer for each line doesn't help either.
0719: // For now, I'm going to subtract one from the fill height, on the theory
0720: // that we're picking up the extra line because of a one-pixel slop.
0721: // This seems to work, although perhaps if one pixel of a line at the
0722: // bottom should draw, it won't.
0723:
0724: --endFill;
0725:
0726: // Format to ending fill height, so line table is valid for all lines we need to draw.
0727:
0728: formatToHeight(endFill);
0729:
0730: // Get starting and ending lines for fill height. If the start of the fill is after the last line,
0731: // or the end of the fill is before the first line, return.
0732:
0733: int curLine = findLineAt(startFill);
0734: if (curLine == kAfterLastLine)
0735: return;
0736: else if (curLine == kBeforeFirstLine)
0737: curLine = 0;
0738:
0739: int lastLine = findLineAt(endFill);
0740: if (lastLine == kBeforeFirstLine)
0741: return;
0742: else if (lastLine == kAfterLastLine)
0743: lastLine = lastLine();
0744:
0745: // Get the base coordinates (lineX, lineY) for the starting line.
0746:
0747: int lineX, lineY;
0748:
0749: int gStart = lineGraphicStartInternal(curLine);
0750:
0751: if (fHLine) {
0752: if (fLineInc)
0753: lineX = origin.x;
0754: else
0755: lineX = origin.x - fLineDim;
0756: if (fFillInc)
0757: lineY = origin.y + gStart;
0758: else
0759: lineY = origin.y
0760: - (gStart + fLineTable[curLine].getHeight());
0761: } else {
0762: if (fLineInc)
0763: lineY = origin.y;
0764: else
0765: lineY = origin.y - fLineDim;
0766: if (fFillInc)
0767: lineX = origin.x + gStart;
0768: else
0769: lineX = origin.x
0770: - (gStart + fLineTable[curLine].getHeight());
0771: }
0772:
0773: // Iterate through lines, drawing each one and incrementing the base coordinate by the line height.
0774:
0775: for (; curLine <= lastLine; curLine++) {
0776: // Adjust curLine around gap in line table.
0777: if ((curLine > fLTPosEnd) && (curLine < fLTNegStart))
0778: curLine = fLTNegStart;
0779:
0780: fLineTable[curLine].renderWithHighlight(fLTCurTextLen, g2d,
0781: fLineDim, lineX, lineY, selStart, selStop,
0782: highlight);
0783:
0784: // Increment line base for next iteration.
0785: int lineInc = fLineTable[curLine].getHeight();
0786: if (fFillInc) {
0787: if (fHLine)
0788: lineY += lineInc;
0789: else
0790: lineX += lineInc;
0791: } else {
0792: if (fHLine)
0793: lineY -= lineInc;
0794: else
0795: lineX -= lineInc;
0796: }
0797: }
0798: }
0799:
0800: /**
0801: * Format text to given height.
0802: * @param height the height to which text will be formatted.
0803: */
0804: public synchronized void formatToHeight(int reqHeight) {
0805: checkTimeStamp();
0806: if (reqHeight <= fPixHeight) // already formatted to this height
0807: return;
0808:
0809: if (fText.length() == lastLineCharStop()) // already formatted all the text
0810: return;
0811:
0812: // +++ should disable update thread here
0813:
0814: if (fLTNegStart < fLTSize)
0815: shiftTableTo(fLTSize - 1);
0816:
0817: formatText(0, 0, reqHeight, false);
0818: }
0819:
0820: /**
0821: * Format text to given offset.
0822: * @param offset the offset to which text will be formatted.
0823: */
0824: private void formatToOffset(TextOffset offset) {
0825: formatToOffset(offset.fOffset, offset.fPlacement);
0826: }
0827:
0828: private synchronized void formatToOffset(int offset,
0829: boolean placement) {
0830:
0831: checkTimeStamp();
0832: int llcs = lastLineCharStop();
0833: if (llcs < fLTCurTextLen) {
0834: int limit = offset;
0835: if (placement == TextOffset.AFTER_OFFSET) // format to past character offset is associated with
0836: limit++;
0837: if (limit >= llcs) { // ??? would '>' be ok instead or '>='?
0838: if (limit > fLTCurTextLen)
0839: limit = fLTCurTextLen;
0840:
0841: shiftTableTo(lastLine());
0842: formatText(llcs, limit - llcs, Integer.MAX_VALUE, true);
0843: }
0844: }
0845: }
0846:
0847: /**
0848: * Reformat text after a change.
0849: * After the formatter's text changes, call this method to reformat. Does
0850: * not redraw.
0851: * @param afStart the offset into the text where modification began; ie, the
0852: * first character in the text which is "different" in some way. Does not
0853: * have to be nonnegative.
0854: * @param afLength the number of new or changed characters in the text. Should never
0855: * be less than 0.
0856: * @param viewRect the Rectangle in which the text will be displayed. This is needed for
0857: * returning the "damaged" area - the area of the screen in which the text must be redrawn.
0858: * @param origin the top-left corner of the text, in the display's coordinate system
0859: * @returns a <tt>Rectangle</tt> which specifies the area in which text must be
0860: * redrawn to reflect the change to the text.
0861: */
0862: public Rectangle updateFormat(final int afStart,
0863: final int afLength, Rectangle viewRect, Point origin) {
0864: if (afStart < 0) {
0865: throw new IllegalArgumentException(
0866: "Debug: updateFormat afStart < 0: " + afStart);
0867: }
0868: if (fBgFormatAllowed) {
0869: throw new IllegalArgumentException(
0870: "Background formatting should have been disabled");
0871: }
0872: fCurTimeStamp = fText.getTimeStamp();
0873:
0874: int curLine = getValidLineContaining(afStart,
0875: TextOffset.AFTER_OFFSET);
0876: int lineStartPos = lineCharStartInternal(curLine);
0877:
0878: // optimize by finding out whether change occurred
0879: // after first word break on curline
0880:
0881: int firstPossibleBreak;
0882:
0883: if (lineStartPos < fText.length()) {
0884:
0885: if (fLineBreak == null) {
0886: fLineBreak = BreakIterator.getLineInstance();
0887: }
0888: CharacterIterator charIter = fText
0889: .createCharacterIterator();
0890: charIter.setIndex(lineStartPos);
0891: fLineBreak.setText(charIter);
0892:
0893: firstPossibleBreak = fLineBreak.following(lineStartPos);
0894: } else
0895: firstPossibleBreak = afStart;
0896:
0897: if ((curLine > 0)
0898: && (firstPossibleBreak == BreakIterator.DONE || afStart <= firstPossibleBreak)) {
0899: curLine--;
0900: if (curLine < fLTNegStart && curLine > fLTPosEnd)
0901: curLine = fLTPosEnd;
0902: }
0903:
0904: shiftTableTo(curLine);
0905:
0906: int pixHeight; // after the formatText call, at least pixHeight text must be formatted
0907:
0908: if (fHLine) {
0909: if (fFillInc)
0910: pixHeight = viewRect.y + viewRect.height - origin.y;
0911: else
0912: pixHeight = origin.y - viewRect.y;
0913: } else {
0914: if (fFillInc)
0915: pixHeight = viewRect.x + viewRect.width - origin.x;
0916: else
0917: pixHeight = origin.x - viewRect.x;
0918: }
0919:
0920: Rectangle r = formatText(afStart, afLength, pixHeight, false);
0921:
0922: //dumpLineTable();
0923:
0924: if ((fPixHeight < pixHeight) && (fLTNegStart < fLTSize)
0925: && (fLTCurTextLen > lastLineCharStop())) {
0926: shiftTableTo(lastLine());
0927: Rectangle s = formatText(0, 0, pixHeight, false);
0928: r = r.union(s);
0929: }
0930:
0931: intlRect(origin, r);
0932: //System.out.println("Damaged rect: "+r+"; origin: "+origin);
0933:
0934: // don't need to synchronized here, b/c the daemon shouldn't be running when
0935: // this is executing
0936:
0937: if (fText.length() < lastLineCharStop())
0938: enableBGFormat();
0939: else
0940: stopBackgroundFormatting();
0941:
0942: //dumpLineTable();
0943:
0944: return r;
0945: }
0946:
0947: private LineBreakMeasurer makeMeasurer(int paragraphStart,
0948: int paragraphLimit) {
0949:
0950: MTextIterator iter = new MTextIterator(fText, fFontResolver,
0951: paragraphStart, paragraphLimit);
0952: LineBreakMeasurer measurer = new LineBreakMeasurer(iter,
0953: fFontRenderContext);
0954: if (fgCacheMeasurers) {
0955: fCachedMeasurerStart = paragraphStart;
0956: fCachedMeasurerLimit = paragraphLimit;
0957: fCachedMeasurer = measurer;
0958: }
0959: return measurer;
0960: }
0961:
0962: /**
0963: * Compute text format. This method calculates text format; it can be
0964: * called for various purposes: to reformat text after an edit, to
0965: * format text to a particular height, or to format text up to a
0966: * particular offset.
0967: * <p>
0968: * The calling method must ensure that <tt>fLineTable</tt> has been shifted
0969: * such that the last positive line is where the formatting operation will
0970: * begin.
0971: * <p>
0972: * Called by: <tt>formatToHeight()</tt>, <tt>updateFormat()</tt>,
0973: * <tt>textOffsetToPoint()</tt>, <tt>getBoundingRect()</tt>
0974: * @param afStart the offset of the first character in the text which has changed
0975: * @param afLength the number of new or changed characters in the text
0976: * @param reqHeight the pixel height to which text must be formatted. Ignored
0977: * if <tt>formatAllNewText</tt> is <tt>true</tt>, or if old lines remain in the
0978: * line table after all changed text has been formatted.
0979: * @param seekOffsetAtEnd if <tt>true</tt>, formatting continues until the line
0980: * containing afStart+afLength has been formatted. If false, formatting may stop
0981: * when reqHeight has been reached. This parameter should be <tt>true</tt> <b>only</b>
0982: * if the object of the formatting operation is to extend formatting to a particular
0983: * offset within the text; it should be <tt>false</tt> everywhere else.
0984: * @returns a rectangle, relative to the top-left of the text, which encloses the
0985: * screen area whose appearance has changed due to the reformatting.
0986: */
0987:
0988: private Rectangle formatText(int afStart, final int afLength,
0989: int reqHeight, boolean seekOffsetAtEnd) {
0990: /* assumes line table shifted such that first line to format is
0991: last positive line */
0992:
0993: if (afLength < 0) {
0994: throw new IllegalArgumentException(
0995: "afLength < 0. afLength=" + afLength);
0996: }
0997:
0998: int newTextEnd = afStart + afLength;
0999:
1000: final int newCurTextLen = fText.length();
1001:
1002: // variable not used int oldPixHeight = fPixHeight;
1003: int oldFullPixHeight = fFullPixHeight;
1004: fPixHeight -= fLineTable[fLTPosEnd].getHeight();
1005:
1006: int curGraphicStart = fLineTable[fLTPosEnd]
1007: .getGraphicStart(fPixHeight);
1008: int curLineStart = fLineTable[fLTPosEnd]
1009: .getCharStart(newCurTextLen);
1010:
1011: int curParagraphStart = fText.paragraphStart(curLineStart);
1012: int curParagraphLimit = Integer.MIN_VALUE; // dummy value
1013:
1014: int damageStart = curGraphicStart;
1015:
1016: ParagraphRenderer renderer = null;
1017: LineBreakMeasurer measurer = null;
1018:
1019: // try to use cached LineBreakMeasurer if possible
1020: if (fCachedMeasurer != null
1021: && curParagraphStart == fCachedMeasurerStart) {
1022:
1023: curParagraphLimit = fText.paragraphLimit(curParagraphStart);
1024:
1025: try {
1026: if (newCurTextLen - fLTCurTextLen == 1 && afLength == 1) {
1027: if (curParagraphLimit == fCachedMeasurerLimit + 1) {
1028: MTextIterator iter = new MTextIterator(fText,
1029: fFontResolver, curParagraphStart,
1030: curParagraphLimit);
1031: fCachedMeasurer.insertChar(iter, afStart);
1032: fCachedMeasurerLimit += 1;
1033: measurer = fCachedMeasurer;
1034: }
1035: } else if (fLTCurTextLen - newCurTextLen == 1
1036: && afLength == 0) {
1037: if (fCachedMeasurerLimit > fCachedMeasurerStart + 1
1038: && curParagraphLimit == fCachedMeasurerLimit - 1) {
1039: MTextIterator iter = new MTextIterator(fText,
1040: fFontResolver, curParagraphStart,
1041: curParagraphLimit);
1042: fCachedMeasurer.deleteChar(iter, afStart);
1043: fCachedMeasurerLimit -= 1;
1044: measurer = fCachedMeasurer;
1045: }
1046: }
1047: } catch (ArrayIndexOutOfBoundsException e) {
1048: fCachedMeasurer = null;
1049: fgCacheMeasurers = false;
1050: }
1051:
1052: if (measurer != null) {
1053: // need to set up renderer since the paragraph update in the
1054: // formatting loop will not happen
1055: AttributeMap style = fText
1056: .paragraphStyleAt(curParagraphStart);
1057: renderer = getRendererFor(style);
1058: measurer.setPosition(curLineStart);
1059: }
1060: }
1061:
1062: if (measurer == null) {
1063: // trigger paragraph update at start of formatting loop
1064: curParagraphLimit = curParagraphStart;
1065: curParagraphStart = 0;
1066: }
1067:
1068: fLTCurTextLen = newCurTextLen;
1069:
1070: while (true) {
1071: // System.out.println("line: " + fLTPosEnd + ", cls: " + curLineStart);
1072:
1073: if (curLineStart >= curParagraphLimit) {
1074: curParagraphStart = curParagraphLimit;
1075: curParagraphLimit = fText
1076: .paragraphLimit(curParagraphStart);
1077:
1078: AttributeMap style = fText
1079: .paragraphStyleAt(curParagraphStart);
1080: renderer = getRendererFor(style);
1081:
1082: if (curParagraphStart < curParagraphLimit) {
1083: measurer = makeMeasurer(curParagraphStart,
1084: curParagraphLimit);
1085: measurer.setPosition(curLineStart);
1086: } else {
1087: measurer = null;
1088: }
1089: }
1090:
1091: {
1092: boolean haveOldDirection = fLineTable[fLTPosEnd] != null;
1093: boolean oldDirection = false; // dummy value for compiler
1094: if (haveOldDirection) {
1095: oldDirection = fLineTable[fLTPosEnd]
1096: .isLeftToRight();
1097: }
1098:
1099: fLineTable[fLTPosEnd] = renderer.layout(fText,
1100: fLineTable[fLTPosEnd], measurer,
1101: fFontRenderContext, curParagraphStart,
1102: curParagraphLimit, fWrap ? fLineDim
1103: : Integer.MAX_VALUE, fLineDim);
1104: if (haveOldDirection) {
1105: if (fLineTable[fLTPosEnd].isLeftToRight() != oldDirection) {
1106: newTextEnd = Math.max(newTextEnd,
1107: curParagraphLimit);
1108: }
1109: }
1110: }
1111:
1112: {
1113: LayoutInfo theLine = fLineTable[fLTPosEnd];
1114:
1115: theLine.setGraphicStart(curGraphicStart);
1116: curGraphicStart += theLine.getHeight();
1117:
1118: fPixHeight += theLine.getHeight();
1119: curLineStart += theLine.getCharLength();
1120:
1121: if (!fWrap) {
1122: int lineWidth = theLine.getTotalAdvance()
1123: + theLine.getLeadingMargin();
1124: if (theLine.isLeftToRight()) {
1125: if (fMaxX < lineWidth) {
1126: fMaxX = lineWidth;
1127: }
1128: } else {
1129: if (fLineDim - lineWidth < fMinX) {
1130: fMinX = fLineDim - lineWidth;
1131: }
1132: }
1133: }
1134: }
1135: /*
1136: Next, discard obsolete lines. A line is obsolete if it
1137: contains new text or text which has been formatted.
1138: */
1139:
1140: while (fLTNegStart < fLTSize) {
1141: int linePos = fLineTable[fLTNegStart]
1142: .getCharStart(newCurTextLen);
1143: if (linePos >= curLineStart && linePos >= newTextEnd)
1144: break;
1145:
1146: // System.out.println("delete neg line: " + fLTNegStart);
1147: fPixHeight -= fLineTable[fLTNegStart].getHeight();
1148: fLineTable[fLTNegStart++] = null;
1149: }
1150:
1151: int stopAt;
1152: if (fLTNegStart < fLTSize)
1153: stopAt = fLineTable[fLTNegStart]
1154: .getCharStart(newCurTextLen);
1155: else
1156: stopAt = newCurTextLen;
1157:
1158: /*
1159: Now, if exit conditions aren't met, create a new line.
1160: */
1161:
1162: if (seekOffsetAtEnd) {
1163: if ((curLineStart >= newTextEnd)
1164: && (fLTNegStart == fLTSize)) {
1165: // System.out.println("break 1");
1166: break;
1167: }
1168: } else {
1169: if (curLineStart >= stopAt) {
1170: // System.out.println("curLineStart: " + curLineStart + " >= stopAt: " + stopAt);
1171: break;
1172: } else if (fLTNegStart == fLTSize
1173: && fPixHeight >= reqHeight) {
1174: // System.out.println("break 3");
1175: break;
1176: }
1177: }
1178:
1179: if (fLTPosEnd + 1 == fLTNegStart)
1180: expandLineTable();
1181:
1182: fLineTable[++fLTPosEnd] = null; // will be created by Renderer
1183: }
1184: //System.out.print("\n");
1185:
1186: if (newCurTextLen == 0) {
1187: fLineTable[0] = pseudoLineInfo(fLineTable[0], 0);
1188: fPixHeight = fLineTable[0].getHeight();
1189: }
1190: fFullPixHeight = fPixHeight;
1191:
1192: if (isParaBreakBefore(newCurTextLen)) {
1193: fFullPixHeight += lastCharHeight();
1194: }
1195: /*
1196: System.out.println("curLineStart: " + curLineStart +
1197: ", fLTPosEnd: " + fLTPosEnd +
1198: ", fLTNegStart: " + fLTNegStart +
1199: ", fLTSize: " + fLTSize);
1200:
1201: System.out.println("oldFullPixHeight: " + oldFullPixHeight + ", newFullPixHeight: " + fFullPixHeight);
1202: */
1203: int damageLength;
1204: if (fFullPixHeight == oldFullPixHeight) {
1205: damageLength = fLineTable[fLTPosEnd]
1206: .getGraphicStart(fPixHeight)
1207: + fLineTable[fLTPosEnd].getHeight() - damageStart;
1208: } else {
1209: damageLength = Math.max(fFullPixHeight, oldFullPixHeight);
1210: }
1211:
1212: return new Rectangle(fMinX, damageStart, fMaxX - fMinX,
1213: damageLength);
1214: }
1215:
1216: private void dumpLineTable() {
1217: int i;
1218:
1219: System.out.println("fLTCurTextLen=" + fLTCurTextLen + " ");
1220: for (i = 0; i <= fLTPosEnd; i++)
1221: System.out.println("Line " + i + " starts at "
1222: + fLineTable[i].getCharStart(fLTCurTextLen)
1223: + " and extends " + fLineTable[i].getCharLength());
1224:
1225: for (i = fLTNegStart; i < fLTSize; i++)
1226: System.out.println("Line "
1227: + (i - fLTNegStart + fLTPosEnd + 1) + " starts at "
1228: + fLineTable[i].getCharStart(fLTCurTextLen)
1229: + " and extends " + fLineTable[i].getCharLength());
1230: }
1231:
1232: public synchronized int minX() {
1233:
1234: return fMinX;
1235: }
1236:
1237: /**
1238: * Return the horizontal extent of the text, in pixels.
1239: * <p>
1240: * This returns an approximation based on the currently formatted text.
1241: */
1242: public synchronized int maxX() {
1243: checkTimeStamp();
1244:
1245: return fMaxX;
1246: }
1247:
1248: /**
1249: * Return the height of the last character in the text.
1250: *
1251: * This is used for the 'extra height' needed to display a caret at the end of the text when the
1252: * text is empty or ends with a newline.
1253: */
1254: private int lastCharHeight() {
1255: int charIndex = lastLineCharStop() - 1;
1256: AttributeMap st = fText.characterStyleAt(charIndex);
1257: DefaultCharacterMetric.Metric metric = fDefaultCharMetric
1258: .getMetricForStyle(st);
1259:
1260: int height = metric.getAscent();
1261: height += metric.getDescent();
1262: height += metric.getLeading();
1263:
1264: return height;
1265: }
1266:
1267: /**
1268: * Return true if the character at pos is a paragraph separator.
1269: */
1270: private boolean isParaBreakBefore(int pos) {
1271: return pos > 0
1272: && (fText.at(pos - 1) == '\u2029' || fText.at(pos - 1) == '\n');
1273: // we really need to take look at this and determine what this function
1274: // should be doing. What I've got here right now is a temporary implementation.
1275: }
1276:
1277: public synchronized int minY() {
1278:
1279: return 0;
1280: }
1281:
1282: /**
1283: * Return the vertical extent of the text, in pixels.
1284: * <p>
1285: * This returns an approximation based on the currently formatted text.
1286: */
1287: public synchronized int maxY() {
1288: checkTimeStamp();
1289:
1290: int numChars = lastLineCharStop();
1291:
1292: int pixHeight = fPixHeight;
1293: if (numChars == fLTCurTextLen
1294: && isParaBreakBefore(fLTCurTextLen)) {
1295: pixHeight += lastCharHeight();
1296: }
1297:
1298: if (numChars != 0)
1299: return pixHeight * fText.length() / numChars;
1300: else
1301: return 0;
1302: }
1303:
1304: /**
1305: * Return the actual pixel length of the text which has been formatted.
1306: */
1307: public synchronized int formattedHeight() {
1308: checkTimeStamp();
1309: return fPixHeight;
1310: }
1311:
1312: /**
1313: * There are two modes for dealing with carriage returns at the end of a line. In the 'infinite width'
1314: * mode, the last character is considered to have infinite width. Thus if the point is past the 'real'
1315: * end of the line, the offset is the position before that last character, and the offset is associated
1316: * with that character (placement after). In the 'actual width' mode, the offset is positioned after
1317: * that character, but still associated with it (placement before).
1318: */
1319:
1320: private TextOffset lineDimToOffset(TextOffset result, int line,
1321: int lineX, int lineY, TextOffset anchor,
1322: boolean infiniteMode) {
1323: // temporarily adjust line info to remove the negative char starts used in the line table.
1324: // then call through to the paragraph renderer to get the offset. Don't put line end
1325: // optimization here, let the renderer do it (perhaps it does fancy stuff with the margins).
1326:
1327: LayoutInfo lineInfo = fLineTable[line];
1328:
1329: result = lineInfo.pixelToOffset(fLTCurTextLen, result,
1330: fLineDim, lineX, lineY);
1331:
1332: if (infiniteMode
1333: && (result.fOffset > lineInfo
1334: .getCharStart(fLTCurTextLen))
1335: && isParaBreakBefore(result.fOffset)
1336: && (anchor == null || anchor.fOffset == result.fOffset - 1)) {
1337:
1338: result.setOffset(result.fOffset - 1,
1339: TextOffset.AFTER_OFFSET);
1340: }
1341:
1342: return result;
1343: }
1344:
1345: /**
1346: * Given a screen location p, return the offset of the character in the text nearest to p.
1347: */
1348: public synchronized TextOffset pointToTextOffset(TextOffset result,
1349: int px, int py, Point origin, TextOffset anchor,
1350: boolean infiniteMode) {
1351: checkTimeStamp();
1352: if (result == null)
1353: result = new TextOffset();
1354:
1355: int fillD;
1356:
1357: if (fHLine)
1358: fillD = py - origin.y;
1359: else
1360: fillD = px - origin.x;
1361:
1362: if (!fFillInc)
1363: fillD = -fillD;
1364:
1365: if (fillD < 0) {
1366: result.setOffset(0, TextOffset.AFTER_OFFSET);
1367: return result;
1368: }
1369:
1370: formatToHeight(fillD);
1371:
1372: if (fillD >= fPixHeight) {
1373: boolean bias = fLTCurTextLen == 0 ? TextOffset.AFTER_OFFSET
1374: : TextOffset.BEFORE_OFFSET;
1375: result.setOffset(fLTCurTextLen, bias);
1376: return result;
1377: }
1378:
1379: int line = findLineAt(fillD); // always a valid line
1380: int gStart = lineGraphicStartInternal(line);
1381:
1382: int lineX, lineY; // upper-left corner of line
1383: if (fHLine) {
1384: lineX = origin.x;
1385: lineY = fFillInc ? origin.y + gStart : origin.y
1386: - (gStart + fLineTable[line].getHeight());
1387: } else {
1388: lineY = origin.y;
1389: lineX = fFillInc ? origin.x + gStart : origin.x
1390: - (gStart + fLineTable[line].getHeight());
1391: }
1392:
1393: return lineDimToOffset(result, line, px - lineX, py - lineY,
1394: anchor, infiniteMode);
1395: }
1396:
1397: private boolean emptyParagraphAtEndOfText() {
1398:
1399: return fLTCurTextLen > 0
1400: && isParagraphSeparator(fText.at(fLTCurTextLen - 1));
1401: }
1402:
1403: /**
1404: * Return true if the offset designates a point on the pseudoline following a paragraph
1405: * separator at the end of text. This is true if the offset is the end of text
1406: * and the last character in the text is a paragraph separator.
1407: */
1408: private boolean afterLastParagraph(TextOffset offset) {
1409: return offset.fOffset == fLTCurTextLen
1410: && emptyParagraphAtEndOfText();
1411: }
1412:
1413: /**
1414: * Given an offset, return the Rectangle bounding the caret at the offset.
1415: * @param offset an offset into the text
1416: * @param origin the top-left corner of the text, in the display's coordinate system
1417: * @return a Rectangle bounding the caret.
1418: */
1419: public synchronized Rectangle getCaretRect(TextOffset offset,
1420: Point origin) {
1421:
1422: Rectangle r = new Rectangle();
1423: getCaretRect(r, offset, origin);
1424: return r;
1425: }
1426:
1427: private void getCaretRect(Rectangle r, TextOffset offset,
1428: Point origin) {
1429:
1430: checkTimeStamp();
1431: formatToOffset(offset);
1432:
1433: if (afterLastParagraph(offset)) {
1434: int pseudoLineHeight = lastCharHeight();
1435: if (fHLine) {
1436: int lineY = fFillInc ? origin.y + fPixHeight : origin.y
1437: - fPixHeight - pseudoLineHeight;
1438: r.setBounds(origin.x, lineY, 0, pseudoLineHeight);
1439: } else {
1440: int lineX = fFillInc ? origin.x + fPixHeight : origin.x
1441: - fPixHeight - pseudoLineHeight;
1442: r.setBounds(lineX, origin.y, pseudoLineHeight, 0);
1443: }
1444: return;
1445: }
1446:
1447: int line = getValidLineContaining(offset);
1448:
1449: int gStart = lineGraphicStartInternal(line);
1450:
1451: int lineX, lineY;
1452:
1453: if (fHLine) {
1454: lineX = origin.x;
1455: if (fFillInc)
1456: lineY = origin.y + gStart;
1457: else
1458: lineY = origin.y
1459: - (gStart + fLineTable[line].getHeight());
1460: } else {
1461: lineY = origin.y;
1462: if (fFillInc)
1463: lineX = origin.x + gStart;
1464: else
1465: lineX = origin.x
1466: - (gStart + fLineTable[line].getHeight());
1467: }
1468:
1469: Rectangle bounds = fLineTable[line].caretBounds(fText,
1470: fLTCurTextLen, fLineDim, offset.fOffset, lineX, lineY);
1471:
1472: r.setBounds(bounds);
1473: }
1474:
1475: /**
1476: * Draw the caret(s) associated with the given offset into the given Graphics.
1477: * @param g the Graphics to draw into
1478: * @param offset the offset in the text for which the caret is drawn
1479: * @param origin the top-left corner of the text, in the display's coordinate system
1480: * @param strongCaretColor the color of the strong caret
1481: * @param weakCaretColor the color of the weak caret (if any)
1482: */
1483: public synchronized void drawCaret(Graphics g, TextOffset offset,
1484: Point origin, Color strongCaretColor, Color weakCaretColor) {
1485:
1486: checkTimeStamp();
1487: Graphics2D g2d = Graphics2DConversion.getGraphics2D(g);
1488: formatToOffset(offset);
1489:
1490: LayoutInfo line;
1491: int gStart;
1492:
1493: if (afterLastParagraph(offset)) {
1494: gStart = fPixHeight;
1495: line = pseudoLineInfo(null, offset.fOffset);
1496: } else {
1497: int lineIndex = getValidLineContaining(offset);
1498: gStart = lineGraphicStartInternal(lineIndex);
1499: line = fLineTable[lineIndex];
1500: }
1501:
1502: int lineX, lineY;
1503:
1504: if (fHLine) {
1505: lineX = origin.x;
1506: if (fFillInc)
1507: lineY = origin.y + gStart;
1508: else
1509: lineY = origin.y - (gStart + line.getHeight());
1510: } else {
1511: lineY = origin.y;
1512: if (fFillInc)
1513: lineX = origin.x + gStart;
1514: else
1515: lineX = origin.x - (gStart + line.getHeight());
1516: }
1517:
1518: line
1519: .renderCaret(fText, fLTCurTextLen, g2d, fLineDim,
1520: lineX, lineY, offset.fOffset, strongCaretColor,
1521: weakCaretColor);
1522: }
1523:
1524: /**
1525: * Given two offsets in the text, return a rectangle which encloses the lines containing the offsets.
1526: * Offsets do not need to be ordered or nonnegative.
1527: * @param offset1,offset2 offsets into the text
1528: * @param origin the top-left corner of the text, in the display's coordinate system
1529: * @returns a <tt>Rectangle</tt>, relative to <tt>origin</tt>, which encloses the lines containing the offsets
1530: */
1531: public synchronized Rectangle getBoundingRect(TextOffset offset1,
1532: TextOffset offset2, Point origin, boolean tight) {
1533:
1534: Rectangle r = new Rectangle();
1535: getBoundingRect(r, offset1, offset2, origin, tight);
1536: return r;
1537: }
1538:
1539: /*
1540: Transform r from "text" coordinates to "screen" coordinates.
1541: */
1542:
1543: private void intlRect(Point origin, Rectangle r) {
1544:
1545: int lineOrig, fillOrig;
1546:
1547: if (fHLine) {
1548: lineOrig = origin.x;
1549: fillOrig = origin.y;
1550: } else {
1551: lineOrig = origin.y;
1552: fillOrig = origin.x;
1553: }
1554:
1555: if (fLineInc)
1556: r.x += lineOrig;
1557: else
1558: r.x = lineOrig - (r.x + r.width);
1559:
1560: if (fFillInc)
1561: r.y += fillOrig;
1562: else
1563: r.y = fillOrig - (r.y + r.height);
1564:
1565: if (!fHLine) {
1566: int t = r.x;
1567: r.x = r.y;
1568: r.y = t;
1569: t = r.width;
1570: r.width = r.height;
1571: r.height = t;
1572: }
1573: }
1574:
1575: public synchronized void getBoundingRect(Rectangle r,
1576: TextOffset offset1, TextOffset offset2, Point origin,
1577: boolean tight) {
1578: checkTimeStamp();
1579: if (offset1.equals(offset2)) {
1580: getCaretRect(r, offset1, origin);
1581: return;
1582: }
1583: if (offset1.greaterThan(offset2)) {
1584: TextOffset t;
1585: t = offset1;
1586: offset1 = offset2;
1587: offset2 = t;
1588: }
1589:
1590: formatToOffset(offset2);
1591:
1592: int line = getValidLineContaining(offset1);
1593: r.y = lineGraphicStartInternal(line);
1594:
1595: int gLimit;
1596: boolean sameLine = false;
1597:
1598: if (afterLastParagraph(offset2))
1599: gLimit = fPixHeight + lastCharHeight();
1600: else {
1601: int line2 = getValidLineContaining(offset2);
1602: gLimit = lineGraphicLimitInternal(line2);
1603: sameLine = (line == line2);
1604: }
1605:
1606: r.height = gLimit - r.y;
1607:
1608: if (sameLine && tight == TIGHT) {
1609: Rectangle rt = new Rectangle();
1610: getCaretRect(rt, offset1, origin);
1611: r.setBounds(rt);
1612: if (!offset1.equals(offset2)) {
1613: getCaretRect(rt, offset2, origin);
1614: r.add(rt);
1615: }
1616: } else {
1617: r.x = fMinX;
1618: r.width = fMaxX - fMinX;
1619: intlRect(origin, r);
1620: }
1621:
1622: // System.out.print("gbr: " + r.x + ", " + r.y + ", " + r.width + ", " + r.height);
1623:
1624: // System.out.println(" --> " + r.x + ", " + r.y + ", " + r.width + ", " + r.height);
1625: }
1626:
1627: /**
1628: * Compute the offset resulting from moving from a previous offset in direction dir.
1629: * For arrow keys.
1630: * @param result the offset to modify and return. may be null, if so a new offset is allocated, modified, and returned.
1631: * @param previousOffset the insertion offset prior to the arrow key press.
1632: * @param direction the direction of the arrow key (eUp, eDown, eLeft, or eRight)
1633: * @returns new offset based on direction and previous offset.
1634: */
1635: public synchronized TextOffset findInsertionOffset(
1636: TextOffset result, TextOffset prevOffset, short dir) {
1637: return findNewInsertionOffset(result, prevOffset, prevOffset,
1638: dir);
1639: }
1640:
1641: /**
1642: * Transform key direction: after this step, "left" means previous glyph, "right" means next glyph,
1643: *"up" means previous line, "down" means next line
1644: */
1645: private short remapArrowKey(short dir) {
1646:
1647: if (!fLineInc) {
1648: if (dir == eLeft)
1649: dir = eRight;
1650: else if (dir == eRight)
1651: dir = eLeft;
1652: }
1653:
1654: if (!fFillInc) {
1655: if (dir == eUp)
1656: dir = eDown;
1657: else if (dir == eDown)
1658: dir = eUp;
1659: }
1660:
1661: if (!fHLine) {
1662: if (dir == eLeft)
1663: dir = eUp;
1664: else if (dir == eRight)
1665: dir = eDown;
1666: else if (dir == eUp)
1667: dir = eLeft;
1668: else if (dir == eDown)
1669: dir = eRight;
1670: }
1671:
1672: return dir;
1673: }
1674:
1675: /**
1676: * Compute the offset resulting from moving from a previous offset, starting at an original offset, in direction dir.
1677: * For arrow keys. Use this for "smart" up/down keys.
1678: * @param result the offset to modify and return. May be null, if so a new offset is allocated, modified, and returned.
1679: * @param origOffset the offset at which an up-down arrow key sequence began.
1680: * @param prevOffset the insertion offset prior to the arrow key press
1681: * @param dir the direction of the arrow key (eUp, eDown, eLeft, or eRight)
1682: * @returns new offset based on direction, original offset, and previous offset.
1683: */
1684: public synchronized TextOffset findNewInsertionOffset(
1685: TextOffset result, TextOffset origOffset,
1686: TextOffset prevOffset, short dir) {
1687: checkTimeStamp();
1688: if (result == null)
1689: result = new TextOffset();
1690:
1691: dir = remapArrowKey(dir);
1692:
1693: // assume that text at origOffset and prevOffset has already been formatted
1694:
1695: if (dir == eLeft || dir == eRight) {
1696: formatToOffset(prevOffset);
1697: int line = getValidLineContaining(prevOffset);
1698:
1699: result.fPlacement = TextOffset.AFTER_OFFSET;
1700: result.fOffset = fLineTable[line].getNextOffset(
1701: fLTCurTextLen, prevOffset.fOffset, dir);
1702: if (result.fOffset < 0) {
1703: result.fOffset = 0;
1704: } else if (result.fOffset >= fLTCurTextLen) {
1705: result.setOffset(fLTCurTextLen,
1706: TextOffset.BEFORE_OFFSET);
1707: }
1708: } else {
1709: int distOnLine;
1710:
1711: if (afterLastParagraph(origOffset))
1712: distOnLine = 0;
1713: else {
1714: int line = getValidLineContaining(origOffset);
1715:
1716: distOnLine = fLineTable[line]
1717: .strongCaretBaselinePosition(fLTCurTextLen,
1718: fLineDim, origOffset.fOffset);
1719: }
1720:
1721: // get prevOffset's line
1722: int line;
1723: if (afterLastParagraph(prevOffset))
1724: line = lastLine() + 1;
1725: else {
1726: line = getLineContaining(prevOffset);
1727:
1728: if (dir == eDown
1729: && (line == kAfterLastLine || line == lastLine())
1730: && (lastLineCharStop() < fText.length())) {
1731: shiftTableTo(lastLine());
1732: formatText(lastLineCharStop(), 1,
1733: Integer.MAX_VALUE, true);
1734: line = getLineContaining(prevOffset);
1735: }
1736:
1737: if (line == kBeforeFirstLine)
1738: line = 0;
1739: else if (line == kAfterLastLine)
1740: line = lastLine();
1741: }
1742:
1743: if (dir == eUp)
1744: line--;
1745: else if (dir == eDown)
1746: line++;
1747: else
1748: throw new IllegalArgumentException(
1749: "Debug: Illegal direction parameter in findNewInsertionOffset");
1750:
1751: if (line < 0) {
1752: //result.setOffset(0, TextOffset.AFTER_OFFSET);
1753: result.assign(prevOffset);
1754: } else if (line > lastLine()) {
1755: result.setOffset(fLTCurTextLen,
1756: TextOffset.BEFORE_OFFSET);
1757: } else {
1758: if (fLineTable[line] == null)
1759: line = (dir == eUp) ? fLTPosEnd : fLTNegStart;
1760:
1761: // anchor is null since we never want a position after newline. If we used the real anchor,
1762: // we might not ignore the newline even though infiniteMode is true.
1763: lineDimToOffset(result, line, distOnLine, 0, null, true);
1764: }
1765: }
1766:
1767: // System.out.println("fnio prev: " + prevOffset + ", new: " + result);
1768:
1769: return result;
1770: }
1771:
1772: public synchronized void stopBackgroundFormatting() {
1773: checkTimeStamp();
1774: fBgFormatAllowed = false;
1775: }
1776:
1777: private synchronized void enableBGFormat() {
1778: try {
1779: fBgFormatAllowed = true;
1780: notify();
1781: } catch (IllegalMonitorStateException e) {
1782: }
1783: }
1784:
1785: private int lineIndexToNumber(int lineIndex) {
1786:
1787: if (lineIndex <= fLTPosEnd) {
1788: return lineIndex;
1789: } else {
1790: return lineIndex - (fLTNegStart - fLTPosEnd - 1);
1791: }
1792: }
1793:
1794: private int lineNumberToIndex(int lineNumber) {
1795:
1796: if (lineNumber <= fLTPosEnd) {
1797: return lineNumber;
1798: } else {
1799: return lineNumber + (fLTNegStart - fLTPosEnd - 1);
1800: }
1801: }
1802:
1803: private void formatToLineNumber(int lineNumber) {
1804:
1805: while (lastLineCharStop() < fLTCurTextLen
1806: && lineNumber >= lineIndexToNumber(fLTSize)) {
1807: // could be smarter and choose larger amounts for
1808: // larger lines, but probably not worth the effort
1809: formatToHeight(fPixHeight + kPixIncrement);
1810: }
1811: }
1812:
1813: private static final boolean STRICT = true;
1814: private static final boolean LENIENT = false;
1815:
1816: /**
1817: * Insure that at least lineNumber lines exist, doing
1818: * extra formatting if necessary.
1819: * Throws exception if lineNumber is not valid.
1820: * @param strict if STRICT, only lines [0...maxLineNumber()]
1821: * are permitted. If LENIENT, maxLineNumber()+1 is
1822: * the greatest valid value.
1823: */
1824: private void validateLineNumber(int lineNumber, boolean strict) {
1825:
1826: formatToLineNumber(lineNumber);
1827:
1828: int maxNumber = lineIndexToNumber(fLTSize);
1829: if (strict == STRICT) {
1830: maxNumber -= 1;
1831: }
1832:
1833: if (lineNumber > maxNumber + 1
1834: || (lineNumber == maxNumber + 1 && !emptyParagraphAtEndOfText())) {
1835: throw new IllegalArgumentException("Invalid line number: "
1836: + lineNumber);
1837: }
1838: }
1839:
1840: public synchronized int getLineCount() {
1841:
1842: // format all text:
1843: formatToHeight(Integer.MAX_VALUE);
1844:
1845: int lineCount = lineIndexToNumber(fLTSize);
1846:
1847: if (emptyParagraphAtEndOfText()) {
1848: lineCount += 1;
1849: }
1850:
1851: return lineCount;
1852: }
1853:
1854: public synchronized int lineContaining(int charIndex) {
1855:
1856: formatToOffset(charIndex, TextOffset.AFTER_OFFSET);
1857:
1858: boolean placement = TextOffset.AFTER_OFFSET;
1859: if (charIndex == fLTCurTextLen && charIndex > 0) {
1860: placement = emptyParagraphAtEndOfText() ? TextOffset.AFTER_OFFSET
1861: : TextOffset.BEFORE_OFFSET;
1862: }
1863:
1864: return lineContaining(charIndex, placement);
1865: }
1866:
1867: public synchronized int lineContaining(TextOffset offset) {
1868:
1869: formatToOffset(offset);
1870:
1871: if (afterLastParagraph(offset)) {
1872: return lineIndexToNumber(fLTSize);
1873: }
1874:
1875: return lineContaining(offset.fOffset, offset.fPlacement);
1876: }
1877:
1878: private int lineContaining(int off, boolean placement) {
1879:
1880: int line = off == 0 ? 0 : getLineContaining(off, placement);
1881:
1882: if (line == kAfterLastLine) {
1883: line = fLTSize;
1884: } else if (line == kBeforeFirstLine) {
1885: throw new Error(
1886: "lineContaining got invalid result from getLineContaining().");
1887: }
1888:
1889: return lineIndexToNumber(line);
1890: }
1891:
1892: public synchronized int lineRangeLow(int lineNumber) {
1893:
1894: validateLineNumber(lineNumber, STRICT);
1895: int index = lineNumberToIndex(lineNumber);
1896:
1897: if (index == fLTSize) {
1898: if (emptyParagraphAtEndOfText()) {
1899: return lastLineCharStop();
1900: }
1901: }
1902:
1903: if (index >= fLTSize) {
1904: throw new IllegalArgumentException("lineNumber is invalid.");
1905: } else {
1906: return lineCharStartInternal(index);
1907: }
1908: }
1909:
1910: public synchronized int lineRangeLimit(int lineNumber) {
1911:
1912: validateLineNumber(lineNumber, STRICT);
1913: int index = lineNumberToIndex(lineNumber);
1914:
1915: if (index == fLTSize) {
1916: if (emptyParagraphAtEndOfText()) {
1917: return lastLineCharStop();
1918: }
1919: }
1920:
1921: if (index >= fLTSize) {
1922: throw new IllegalArgumentException("lineNumber is invalid.");
1923: } else {
1924: return lineCharLimitInternal(index);
1925: }
1926: }
1927:
1928: /**
1929: * Return the number of the line at the given graphic height.
1930: * If height is greater than full height, return line count.
1931: */
1932: public synchronized int lineAtHeight(int height) {
1933:
1934: if (height >= fPixHeight) {
1935:
1936: int line = getLineCount();
1937: if (height < fFullPixHeight) {
1938: line -= 1;
1939: }
1940: return line;
1941: } else if (height < 0) {
1942: return -1;
1943: } else {
1944: return lineIndexToNumber(findLineAt(height));
1945: }
1946: }
1947:
1948: public synchronized int lineGraphicStart(int lineNumber) {
1949:
1950: checkTimeStamp();
1951: validateLineNumber(lineNumber, LENIENT);
1952:
1953: int index = lineNumberToIndex(lineNumber);
1954:
1955: if (index < fLTSize) {
1956: return lineGraphicStartInternal(index);
1957: } else {
1958: if (index == fLTSize + 1) {
1959: return fFullPixHeight;
1960: } else {
1961: return fPixHeight;
1962: }
1963: }
1964: }
1965:
1966: public synchronized boolean lineIsLeftToRight(int lineNumber) {
1967:
1968: validateLineNumber(lineNumber, STRICT);
1969:
1970: int index = lineNumberToIndex(lineNumber);
1971:
1972: if (index < fLTSize) {
1973: return fLineTable[index].isLeftToRight();
1974: } else {
1975: AttributeMap st = fText.paragraphStyleAt(fLTCurTextLen);
1976: return !TextAttribute.RUN_DIRECTION_RTL.equals(st
1977: .get(TextAttribute.RUN_DIRECTION));
1978: }
1979: }
1980:
1981: /**
1982: * Number of pixels by which to advance formatting in the background.
1983: */
1984: private static final int kPixIncrement = 100;
1985:
1986: /**
1987: * Time to sleep between background formatting operations.
1988: */
1989: private static final int kInterval = 100;
1990:
1991: /**
1992: * Perform periodic background formatting.
1993: */
1994: public void run() {
1995: while (true) {
1996: synchronized (this ) {
1997: while (!fBgFormatAllowed) {
1998: try {
1999: wait();
2000: } catch (InterruptedException e) {
2001: }
2002: }
2003:
2004: checkTimeStamp();
2005: formatToHeight(fPixHeight + kPixIncrement);
2006:
2007: if (lastLineCharStop() == fLTCurTextLen) {
2008: stopBackgroundFormatting();
2009: }
2010: }
2011:
2012: try {
2013: Thread.sleep(kInterval);
2014: } catch (InterruptedException e) {
2015: }
2016: }
2017: }
2018:
2019: private ParagraphRenderer getRendererFor(AttributeMap s) {
2020:
2021: // Note: eventually we could let clients put their own renderers
2022: // on the text.
2023: BidiParagraphRenderer renderer = (BidiParagraphRenderer) fRendererCache
2024: .get(s);
2025: if (renderer == null) {
2026: renderer = new BidiParagraphRenderer(fDefaultValues
2027: .addAttributes(s), fDefaultCharMetric);
2028: fRendererCache.put(s, renderer);
2029: }
2030: return renderer;
2031: }
2032:
2033: }
|