summaryrefslogtreecommitdiffstats
path: root/awt/org/apache/harmony/awt/gl/font/CaretManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'awt/org/apache/harmony/awt/gl/font/CaretManager.java')
-rw-r--r--awt/org/apache/harmony/awt/gl/font/CaretManager.java530
1 files changed, 530 insertions, 0 deletions
diff --git a/awt/org/apache/harmony/awt/gl/font/CaretManager.java b/awt/org/apache/harmony/awt/gl/font/CaretManager.java
new file mode 100644
index 0000000..b18bdd5
--- /dev/null
+++ b/awt/org/apache/harmony/awt/gl/font/CaretManager.java
@@ -0,0 +1,530 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * @author Oleg V. Khaschansky
+ * @version $Revision$
+ *
+ * @date: Jun 14, 2005
+ */
+
+package org.apache.harmony.awt.gl.font;
+
+import java.awt.font.TextHitInfo;
+import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.*;
+
+import org.apache.harmony.awt.internal.nls.Messages;
+
+/**
+ * This class provides functionality for creating caret and highlight shapes
+ * (bidirectional text is also supported, but, unfortunately, not tested yet).
+ */
+public class CaretManager {
+ private TextRunBreaker breaker;
+
+ public CaretManager(TextRunBreaker breaker) {
+ this.breaker = breaker;
+ }
+
+ /**
+ * Checks if TextHitInfo is not out of the text range and throws the
+ * IllegalArgumentException if it is.
+ * @param info - text hit info
+ */
+ private void checkHit(TextHitInfo info) {
+ int idx = info.getInsertionIndex();
+
+ if (idx < 0 || idx > breaker.getCharCount()) {
+ // awt.42=TextHitInfo out of range
+ throw new IllegalArgumentException(Messages.getString("awt.42")); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Calculates and returns visual position from the text hit info.
+ * @param hitInfo - text hit info
+ * @return visual index
+ */
+ private int getVisualFromHitInfo(TextHitInfo hitInfo) {
+ final int idx = hitInfo.getCharIndex();
+
+ if (idx >= 0 && idx < breaker.getCharCount()) {
+ int visual = breaker.getVisualFromLogical(idx);
+ // We take next character for (LTR char + TRAILING info) and (RTL + LEADING)
+ if (hitInfo.isLeadingEdge() ^ ((breaker.getLevel(idx) & 0x1) == 0x0)) {
+ visual++;
+ }
+ return visual;
+ } else if (idx < 0) {
+ return breaker.isLTR() ? 0: breaker.getCharCount();
+ } else {
+ return breaker.isLTR() ? breaker.getCharCount() : 0;
+ }
+ }
+
+ /**
+ * Calculates text hit info from the visual position
+ * @param visual - visual position
+ * @return text hit info
+ */
+ private TextHitInfo getHitInfoFromVisual(int visual) {
+ final boolean first = visual == 0;
+
+ if (!(first || visual == breaker.getCharCount())) {
+ int logical = breaker.getLogicalFromVisual(visual);
+ return (breaker.getLevel(logical) & 0x1) == 0x0 ?
+ TextHitInfo.leading(logical) : // LTR
+ TextHitInfo.trailing(logical); // RTL
+ } else if (first) {
+ return breaker.isLTR() ?
+ TextHitInfo.trailing(-1) :
+ TextHitInfo.leading(breaker.getCharCount());
+ } else { // Last
+ return breaker.isLTR() ?
+ TextHitInfo.leading(breaker.getCharCount()) :
+ TextHitInfo.trailing(-1);
+ }
+ }
+
+ /**
+ * Creates caret info. Required for the getCaretInfo
+ * methods of the TextLayout
+ * @param hitInfo - specifies caret position
+ * @return caret info, see TextLayout.getCaretInfo documentation
+ */
+ public float[] getCaretInfo(TextHitInfo hitInfo) {
+ checkHit(hitInfo);
+ float res[] = new float[2];
+
+ int visual = getVisualFromHitInfo(hitInfo);
+ float advance, angle;
+ TextRunSegment seg;
+
+ if (visual < breaker.getCharCount()) {
+ int logIdx = breaker.getLogicalFromVisual(visual);
+ int segmentIdx = breaker.logical2segment[logIdx];
+ seg = breaker.runSegments.get(segmentIdx);
+ advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx);
+ angle = seg.metrics.italicAngle;
+
+ } else { // Last character
+ int logIdx = breaker.getLogicalFromVisual(visual-1);
+ int segmentIdx = breaker.logical2segment[logIdx];
+ seg = breaker.runSegments.get(segmentIdx);
+ advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx+1);
+ }
+
+ angle = seg.metrics.italicAngle;
+
+ res[0] = advance;
+ res[1] = angle;
+
+ return res;
+ }
+
+ /**
+ * Returns the next position to the right from the current caret position
+ * @param hitInfo - current position
+ * @return next position to the right
+ */
+ public TextHitInfo getNextRightHit(TextHitInfo hitInfo) {
+ checkHit(hitInfo);
+ int visual = getVisualFromHitInfo(hitInfo);
+
+ if (visual == breaker.getCharCount()) {
+ return null;
+ }
+
+ TextHitInfo newInfo;
+
+ while(visual <= breaker.getCharCount()) {
+ visual++;
+ newInfo = getHitInfoFromVisual(visual);
+
+ if (newInfo.getCharIndex() >= breaker.logical2segment.length) {
+ return newInfo;
+ }
+
+ if (hitInfo.getCharIndex() >= 0) { // Don't check for leftmost info
+ if (
+ breaker.logical2segment[newInfo.getCharIndex()] !=
+ breaker.logical2segment[hitInfo.getCharIndex()]
+ ) {
+ return newInfo; // We crossed segment boundary
+ }
+ }
+
+ TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo
+ .getCharIndex()]);
+ if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) {
+ return newInfo;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the next position to the left from the current caret position
+ * @param hitInfo - current position
+ * @return next position to the left
+ */
+ public TextHitInfo getNextLeftHit(TextHitInfo hitInfo) {
+ checkHit(hitInfo);
+ int visual = getVisualFromHitInfo(hitInfo);
+
+ if (visual == 0) {
+ return null;
+ }
+
+ TextHitInfo newInfo;
+
+ while(visual >= 0) {
+ visual--;
+ newInfo = getHitInfoFromVisual(visual);
+
+ if (newInfo.getCharIndex() < 0) {
+ return newInfo;
+ }
+
+ // Don't check for rightmost info
+ if (hitInfo.getCharIndex() < breaker.logical2segment.length) {
+ if (
+ breaker.logical2segment[newInfo.getCharIndex()] !=
+ breaker.logical2segment[hitInfo.getCharIndex()]
+ ) {
+ return newInfo; // We crossed segment boundary
+ }
+ }
+
+ TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo
+ .getCharIndex()]);
+ if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) {
+ return newInfo;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * For each visual caret position there are two hits. For the simple LTR text one is
+ * a trailing of the previous char and another is the leading of the next char. This
+ * method returns the opposite hit for the given hit.
+ * @param hitInfo - given hit
+ * @return opposite hit
+ */
+ public TextHitInfo getVisualOtherHit(TextHitInfo hitInfo) {
+ checkHit(hitInfo);
+
+ int idx = hitInfo.getCharIndex();
+
+ int resIdx;
+ boolean resIsLeading;
+
+ if (idx >= 0 && idx < breaker.getCharCount()) { // Hit info in the middle
+ int visual = breaker.getVisualFromLogical(idx);
+
+ // Char is LTR + LEADING info
+ if (((breaker.getLevel(idx) & 0x1) == 0x0) ^ hitInfo.isLeadingEdge()) {
+ visual++;
+ if (visual == breaker.getCharCount()) {
+ if (breaker.isLTR()) {
+ resIdx = breaker.getCharCount();
+ resIsLeading = true;
+ } else {
+ resIdx = -1;
+ resIsLeading = false;
+ }
+ } else {
+ resIdx = breaker.getLogicalFromVisual(visual);
+ if ((breaker.getLevel(resIdx) & 0x1) == 0x0) {
+ resIsLeading = true;
+ } else {
+ resIsLeading = false;
+ }
+ }
+ } else {
+ visual--;
+ if (visual == -1) {
+ if (breaker.isLTR()) {
+ resIdx = -1;
+ resIsLeading = false;
+ } else {
+ resIdx = breaker.getCharCount();
+ resIsLeading = true;
+ }
+ } else {
+ resIdx = breaker.getLogicalFromVisual(visual);
+ if ((breaker.getLevel(resIdx) & 0x1) == 0x0) {
+ resIsLeading = false;
+ } else {
+ resIsLeading = true;
+ }
+ }
+ }
+ } else if (idx < 0) { // before "start"
+ if (breaker.isLTR()) {
+ resIdx = breaker.getLogicalFromVisual(0);
+ resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // LTR char?
+ } else {
+ resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1);
+ resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // RTL char?
+ }
+ } else { // idx == breaker.getCharCount()
+ if (breaker.isLTR()) {
+ resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1);
+ resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // LTR char?
+ } else {
+ resIdx = breaker.getLogicalFromVisual(0);
+ resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // RTL char?
+ }
+ }
+
+ return resIsLeading ? TextHitInfo.leading(resIdx) : TextHitInfo.trailing(resIdx);
+ }
+
+ public Line2D getCaretShape(TextHitInfo hitInfo, TextLayout layout) {
+ return getCaretShape(hitInfo, layout, true, false, null);
+ }
+
+ /**
+ * Creates a caret shape.
+ * @param hitInfo - hit where to place a caret
+ * @param layout - text layout
+ * @param useItalic - unused for now, was used to create
+ * slanted carets for italic text
+ * @param useBounds - true if the cared should fit into the provided bounds
+ * @param bounds - bounds for the caret
+ * @return caret shape
+ */
+ public Line2D getCaretShape(
+ TextHitInfo hitInfo, TextLayout layout,
+ boolean useItalic, boolean useBounds, Rectangle2D bounds
+ ) {
+ checkHit(hitInfo);
+
+ float x1, x2, y1, y2;
+
+ int charIdx = hitInfo.getCharIndex();
+
+ if (charIdx >= 0 && charIdx < breaker.getCharCount()) {
+ TextRunSegment segment = breaker.runSegments.get(breaker.logical2segment[charIdx]);
+ y1 = segment.metrics.descent;
+ y2 = - segment.metrics.ascent - segment.metrics.leading;
+
+ x1 = x2 = segment.getCharPosition(charIdx) + (hitInfo.isLeadingEdge() ?
+ 0 : segment.getCharAdvance(charIdx));
+ // Decided that straight cursor looks better even for italic fonts,
+ // especially combined with highlighting
+ /*
+ // Not graphics, need to check italic angle and baseline
+ if (layout.getBaseline() >= 0) {
+ if (segment.metrics.italicAngle != 0 && useItalic) {
+ x1 -= segment.metrics.italicAngle * segment.metrics.descent;
+ x2 += segment.metrics.italicAngle *
+ (segment.metrics.ascent + segment.metrics.leading);
+
+ float baselineOffset =
+ layout.getBaselineOffsets()[layout.getBaseline()];
+ y1 += baselineOffset;
+ y2 += baselineOffset;
+ }
+ }
+ */
+ } else {
+ y1 = layout.getDescent();
+ y2 = - layout.getAscent() - layout.getLeading();
+ x1 = x2 = ((breaker.getBaseLevel() & 0x1) == 0 ^ charIdx < 0) ?
+ layout.getAdvance() : 0;
+ }
+
+ if (useBounds) {
+ y1 = (float) bounds.getMaxY();
+ y2 = (float) bounds.getMinY();
+
+ if (x2 > bounds.getMaxX()) {
+ x1 = x2 = (float) bounds.getMaxX();
+ }
+ if (x1 < bounds.getMinX()) {
+ x1 = x2 = (float) bounds.getMinX();
+ }
+ }
+
+ return new Line2D.Float(x1, y1, x2, y2);
+ }
+
+ /**
+ * Creates caret shapes for the specified offset. On the boundaries where
+ * the text is changing its direction this method may return two shapes
+ * for the strong and the weak carets, in other cases it would return one.
+ * @param offset - offset in the text.
+ * @param bounds - bounds to fit the carets into
+ * @param policy - caret policy
+ * @param layout - text layout
+ * @return one or two caret shapes
+ */
+ public Shape[] getCaretShapes(
+ int offset, Rectangle2D bounds,
+ TextLayout.CaretPolicy policy, TextLayout layout
+ ) {
+ TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
+ TextHitInfo hit2 = getVisualOtherHit(hit1);
+
+ Shape caret1 = getCaretShape(hit1, layout);
+
+ if (getVisualFromHitInfo(hit1) == getVisualFromHitInfo(hit2)) {
+ return new Shape[] {caret1, null};
+ }
+ Shape caret2 = getCaretShape(hit2, layout);
+
+ TextHitInfo strongHit = policy.getStrongCaret(hit1, hit2, layout);
+ return strongHit.equals(hit1) ?
+ new Shape[] {caret1, caret2} :
+ new Shape[] {caret2, caret1};
+ }
+
+ /**
+ * Connects two carets to produce a highlight shape.
+ * @param caret1 - 1st caret
+ * @param caret2 - 2nd caret
+ * @return highlight shape
+ */
+ GeneralPath connectCarets(Line2D caret1, Line2D caret2) {
+ GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
+ path.moveTo((float) caret1.getX1(), (float) caret1.getY1());
+ path.lineTo((float) caret2.getX1(), (float) caret2.getY1());
+ path.lineTo((float) caret2.getX2(), (float) caret2.getY2());
+ path.lineTo((float) caret1.getX2(), (float) caret1.getY2());
+
+ path.closePath();
+
+ return path;
+ }
+
+ /**
+ * Creates a highlight shape from given two hits. This shape
+ * will always be visually contiguous
+ * @param hit1 - 1st hit
+ * @param hit2 - 2nd hit
+ * @param bounds - bounds to fit the shape into
+ * @param layout - text layout
+ * @return highlight shape
+ */
+ public Shape getVisualHighlightShape(
+ TextHitInfo hit1, TextHitInfo hit2,
+ Rectangle2D bounds, TextLayout layout
+ ) {
+ checkHit(hit1);
+ checkHit(hit2);
+
+ Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds);
+ Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds);
+
+ return connectCarets(caret1, caret2);
+ }
+
+ /**
+ * Suppose that the user visually selected a block of text which has
+ * several different levels (mixed RTL and LTR), so, in the logical
+ * representation of the text this selection may be not contigous.
+ * This methods returns a set of logical ranges for the arbitrary
+ * visual selection represented by two hits.
+ * @param hit1 - 1st hit
+ * @param hit2 - 2nd hit
+ * @return logical ranges for the selection
+ */
+ public int[] getLogicalRangesForVisualSelection(TextHitInfo hit1, TextHitInfo hit2) {
+ checkHit(hit1);
+ checkHit(hit2);
+
+ int visual1 = getVisualFromHitInfo(hit1);
+ int visual2 = getVisualFromHitInfo(hit2);
+
+ if (visual1 > visual2) {
+ int tmp = visual2;
+ visual2 = visual1;
+ visual1 = tmp;
+ }
+
+ // Max level is 255, so we don't need more than 512 entries
+ int results[] = new int[512];
+
+ int prevLogical, logical, runStart, numRuns = 0;
+
+ logical = runStart = prevLogical = breaker.getLogicalFromVisual(visual1);
+
+ // Get all the runs. We use the fact that direction is constant in all runs.
+ for (int i=visual1+1; i<=visual2; i++) {
+ logical = breaker.getLogicalFromVisual(i);
+ int diff = logical-prevLogical;
+
+ // Start of the next run encountered
+ if (diff > 1 || diff < -1) {
+ results[(numRuns)*2] = Math.min(runStart, prevLogical);
+ results[(numRuns)*2 + 1] = Math.max(runStart, prevLogical);
+ numRuns++;
+ runStart = logical;
+ }
+
+ prevLogical = logical;
+ }
+
+ // The last unsaved run
+ results[(numRuns)*2] = Math.min(runStart, logical);
+ results[(numRuns)*2 + 1] = Math.max(runStart, logical);
+ numRuns++;
+
+ int retval[] = new int[numRuns*2];
+ System.arraycopy(results, 0, retval, 0, numRuns*2);
+ return retval;
+ }
+
+ /**
+ * Creates a highlight shape from given two endpoints in the logical
+ * representation. This shape is not always visually contiguous
+ * @param firstEndpoint - 1st logical endpoint
+ * @param secondEndpoint - 2nd logical endpoint
+ * @param bounds - bounds to fit the shape into
+ * @param layout - text layout
+ * @return highlight shape
+ */
+ public Shape getLogicalHighlightShape(
+ int firstEndpoint, int secondEndpoint,
+ Rectangle2D bounds, TextLayout layout
+ ) {
+ GeneralPath res = new GeneralPath();
+
+ for (int i=firstEndpoint; i<=secondEndpoint; i++) {
+ int endRun = breaker.getLevelRunLimit(i, secondEndpoint);
+ TextHitInfo hit1 = TextHitInfo.leading(i);
+ TextHitInfo hit2 = TextHitInfo.trailing(endRun-1);
+
+ Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds);
+ Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds);
+
+ res.append(connectCarets(caret1, caret2), false);
+
+ i = endRun;
+ }
+
+ return res;
+ }
+}