0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: /**
0018: * @author Oleg V. Khaschansky
0019: * @version $Revision$
0020: *
0021: */package org.apache.harmony.awt.gl.font;
0022:
0023: import java.awt.*;
0024: import java.awt.font.*;
0025: import java.awt.geom.Rectangle2D;
0026: import java.awt.geom.GeneralPath;
0027: import java.awt.geom.AffineTransform;
0028: import java.awt.geom.Point2D; // XXX - TODO - bidi not implemented yet
0029: //import java.text.Bidi;
0030: import java.util.Arrays;
0031:
0032: import org.apache.harmony.awt.internal.nls.Messages;
0033:
0034: /**
0035: * Date: Apr 25, 2005
0036: * Time: 4:33:18 PM
0037: *
0038: * This class contains the implementation of the behavior of the
0039: * text run segment with constant text attributes and direction.
0040: */
0041: public class TextRunSegmentImpl {
0042:
0043: /**
0044: * This class contains basic information required for creation
0045: * of the glyph-based text run segment.
0046: */
0047: public static class TextSegmentInfo {
0048: // XXX - TODO - bidi not implemented yet
0049: //Bidi bidi;
0050:
0051: Font font;
0052: FontRenderContext frc;
0053:
0054: char text[];
0055:
0056: int start;
0057: int end;
0058: int length;
0059:
0060: int flags = 0;
0061:
0062: byte level = 0;
0063:
0064: TextSegmentInfo(byte level, Font font, FontRenderContext frc,
0065: char text[], int start, int end) {
0066: this .font = font;
0067: this .frc = frc;
0068: this .text = text;
0069: this .start = start;
0070: this .end = end;
0071: this .level = level;
0072: length = end - start;
0073: }
0074: }
0075:
0076: /**
0077: * This class represents a simple text segment backed by the glyph vector
0078: */
0079: public static class TextRunSegmentCommon extends TextRunSegment {
0080: TextSegmentInfo info;
0081: private GlyphVector gv;
0082: private float advanceIncrements[];
0083: private int char2glyph[];
0084: private GlyphJustificationInfo gjis[]; // Glyph justification info
0085:
0086: TextRunSegmentCommon(TextSegmentInfo i,
0087: TextDecorator.Decoration d) {
0088: // XXX - todo - check support bidi
0089: i.flags &= ~0x09; // Clear bidi flags
0090:
0091: if ((i.level & 0x1) != 0) {
0092: i.flags |= Font.LAYOUT_RIGHT_TO_LEFT;
0093: }
0094:
0095: info = i;
0096: this .decoration = d;
0097:
0098: LineMetrics lm = i.font.getLineMetrics(i.text, i.start,
0099: i.end, i.frc);
0100: this .metrics = new BasicMetrics(lm, i.font);
0101:
0102: if (lm.getNumChars() != i.length) { // XXX todo - This should be handled
0103: // awt.41=Font returned unsupported type of line metrics. This case is known, but not supported yet.
0104: throw new UnsupportedOperationException(Messages
0105: .getString("awt.41")); //$NON-NLS-1$
0106: }
0107: }
0108:
0109: @Override
0110: public Object clone() {
0111: return new TextRunSegmentCommon(info, decoration);
0112: }
0113:
0114: /**
0115: * Creates glyph vector from the managed text if needed
0116: * @return glyph vector
0117: */
0118: private GlyphVector getGlyphVector() {
0119: if (gv == null) {
0120: gv = info.font.layoutGlyphVector(info.frc, info.text,
0121: info.start, info.end - info.start, // NOTE: This parameter violates
0122: // spec, it is count,
0123: // not limit as spec states
0124: info.flags);
0125: }
0126:
0127: return gv;
0128: }
0129:
0130: /**
0131: * Renders this text run segment
0132: * @param g2d - graphics to render to
0133: * @param xOffset - X offset from the graphics origin to the
0134: * origin of the text layout
0135: * @param yOffset - Y offset from the graphics origin to the
0136: * origin of the text layout
0137: */
0138: @Override
0139: void draw(Graphics2D g2d, float xOffset, float yOffset) {
0140: if (decoration == null) {
0141: g2d.drawGlyphVector(getGlyphVector(), xOffset + x,
0142: yOffset + y);
0143: } else {
0144: TextDecorator.prepareGraphics(this , g2d, xOffset,
0145: yOffset);
0146: g2d.drawGlyphVector(getGlyphVector(), xOffset + x,
0147: yOffset + y);
0148: TextDecorator.drawTextDecorations(this , g2d, xOffset,
0149: yOffset);
0150: TextDecorator.restoreGraphics(decoration, g2d);
0151: }
0152: }
0153:
0154: /**
0155: * Returns visual bounds of this segment
0156: * @return visual bounds
0157: */
0158: @Override
0159: Rectangle2D getVisualBounds() {
0160: if (visualBounds == null) {
0161: visualBounds = TextDecorator.extendVisualBounds(this ,
0162: getGlyphVector().getVisualBounds(), decoration);
0163:
0164: visualBounds.setRect(x + visualBounds.getX(), y
0165: + visualBounds.getY(), visualBounds.getWidth(),
0166: visualBounds.getHeight());
0167: }
0168:
0169: return (Rectangle2D) visualBounds.clone();
0170: }
0171:
0172: /**
0173: * Returns logical bounds of this segment
0174: * @return logical bounds
0175: */
0176: @Override
0177: Rectangle2D getLogicalBounds() {
0178: if (logicalBounds == null) {
0179: logicalBounds = getGlyphVector().getLogicalBounds();
0180:
0181: logicalBounds.setRect(x + logicalBounds.getX(), y
0182: + logicalBounds.getY(), logicalBounds
0183: .getWidth(), logicalBounds.getHeight());
0184: }
0185:
0186: return (Rectangle2D) logicalBounds.clone();
0187: }
0188:
0189: @Override
0190: float getAdvance() {
0191: return (float) getLogicalBounds().getWidth();
0192: }
0193:
0194: /**
0195: * Attempts to map each character to the corresponding advance increment
0196: */
0197: void initAdvanceMapping() {
0198: GlyphVector gv = getGlyphVector();
0199: int charIndicies[] = gv.getGlyphCharIndices(0, gv
0200: .getNumGlyphs(), null);
0201: advanceIncrements = new float[info.length];
0202:
0203: for (int i = 0; i < charIndicies.length; i++) {
0204: advanceIncrements[charIndicies[i]] = gv
0205: .getGlyphMetrics(i).getAdvance();
0206: }
0207: }
0208:
0209: /**
0210: * Calculates advance delta between two characters
0211: * @param start - 1st position
0212: * @param end - 2nd position
0213: * @return advance increment between specified positions
0214: */
0215: @Override
0216: float getAdvanceDelta(int start, int end) {
0217: // Get coordinates in the segment context
0218: start -= info.start;
0219: end -= info.start;
0220:
0221: if (advanceIncrements == null) {
0222: initAdvanceMapping();
0223: }
0224:
0225: if (start < 0) {
0226: start = 0;
0227: }
0228: if (end > info.length) {
0229: end = info.length;
0230: }
0231:
0232: float sum = 0;
0233: for (int i = start; i < end; i++) {
0234: sum += advanceIncrements[i];
0235: }
0236:
0237: return sum;
0238: }
0239:
0240: /**
0241: * Calculates index of the character which advance is equal to
0242: * the given. If the given advance is greater then the segment
0243: * advance it returns the position after the last character.
0244: * @param advance - given advance
0245: * @param start - character, from which to start measuring advance
0246: * @return character index
0247: */
0248: @Override
0249: int getCharIndexFromAdvance(float advance, int start) {
0250: // XXX - todo - probably, possible to optimize
0251: // Add check if the given advance is greater then
0252: // the segment advance in the beginning. In this case
0253: // we don't need to run through all increments
0254: if (advanceIncrements == null) {
0255: initAdvanceMapping();
0256: }
0257:
0258: start -= info.start;
0259:
0260: if (start < 0) {
0261: start = 0;
0262: }
0263:
0264: int i = start;
0265: for (; i < info.length; i++) {
0266: advance -= advanceIncrements[i];
0267: if (advance < 0) {
0268: break;
0269: }
0270: }
0271:
0272: return i + info.start;
0273: }
0274:
0275: @Override
0276: int getStart() {
0277: return info.start;
0278: }
0279:
0280: @Override
0281: int getEnd() {
0282: return info.end;
0283: }
0284:
0285: @Override
0286: int getLength() {
0287: return info.length;
0288: }
0289:
0290: /**
0291: * Attempts to create mapping of the characters to glyphs in the glyph vector.
0292: * @return array where for each character index stored corresponding glyph index
0293: */
0294: private int[] getChar2Glyph() {
0295: if (char2glyph == null) {
0296: GlyphVector gv = getGlyphVector();
0297: char2glyph = new int[info.length];
0298: Arrays.fill(char2glyph, -1);
0299:
0300: // Fill glyph indicies for first characters corresponding to each glyph
0301: int charIndicies[] = gv.getGlyphCharIndices(0, gv
0302: .getNumGlyphs(), null);
0303: for (int i = 0; i < charIndicies.length; i++) {
0304: char2glyph[charIndicies[i]] = i;
0305: }
0306:
0307: // If several characters corresponds to one glyph, create mapping for them
0308: // Suppose that these characters are going all together
0309: int currIndex = 0;
0310: for (int i = 0; i < char2glyph.length; i++) {
0311: if (char2glyph[i] < 0) {
0312: char2glyph[i] = currIndex;
0313: } else {
0314: currIndex = char2glyph[i];
0315: }
0316: }
0317: }
0318:
0319: return char2glyph;
0320: }
0321:
0322: /**
0323: * Creates black box bounds shape for the specified range
0324: * @param start - range sart
0325: * @param limit - range end
0326: * @return black box bounds shape
0327: */
0328: @Override
0329: Shape getCharsBlackBoxBounds(int start, int limit) {
0330: start -= info.start;
0331: limit -= info.start;
0332:
0333: if (limit > info.length) {
0334: limit = info.length;
0335: }
0336:
0337: GeneralPath result = new GeneralPath();
0338:
0339: int glyphIndex = 0;
0340:
0341: for (int i = start; i < limit; i++) {
0342: glyphIndex = getChar2Glyph()[i];
0343: result.append(getGlyphVector().getGlyphVisualBounds(
0344: glyphIndex), false);
0345: }
0346:
0347: // Shift to the segment's coordinates
0348: result
0349: .transform(AffineTransform.getTranslateInstance(x,
0350: y));
0351:
0352: return result;
0353: }
0354:
0355: /**
0356: * Calculates position of the character on the screen
0357: * @param index - character index
0358: * @return X coordinate of the character position
0359: */
0360: @Override
0361: float getCharPosition(int index) {
0362: index -= info.start;
0363:
0364: if (index > info.length) {
0365: index = info.length;
0366: }
0367:
0368: float result = 0;
0369:
0370: int glyphIndex = getChar2Glyph()[index];
0371: result = (float) getGlyphVector().getGlyphPosition(
0372: glyphIndex).getX();
0373:
0374: // Shift to the segment's coordinates
0375: result += x;
0376:
0377: return result;
0378: }
0379:
0380: /**
0381: * Returns the advance of the individual character
0382: * @param index - character index
0383: * @return character advance
0384: */
0385: @Override
0386: float getCharAdvance(int index) {
0387: if (advanceIncrements == null) {
0388: initAdvanceMapping();
0389: }
0390:
0391: return advanceIncrements[index - this .getStart()];
0392: }
0393:
0394: /**
0395: * Returns the outline shape
0396: * @return outline
0397: */
0398: @Override
0399: Shape getOutline() {
0400: AffineTransform t = AffineTransform.getTranslateInstance(x,
0401: y);
0402: return t.createTransformedShape(TextDecorator
0403: .extendOutline(this , getGlyphVector().getOutline(),
0404: decoration));
0405: }
0406:
0407: /**
0408: * Checks if the character doesn't contribute to the text advance
0409: * @param index - character index
0410: * @return true if the character has zero advance
0411: */
0412: @Override
0413: boolean charHasZeroAdvance(int index) {
0414: if (advanceIncrements == null) {
0415: initAdvanceMapping();
0416: }
0417:
0418: return advanceIncrements[index - this .getStart()] == 0;
0419: }
0420:
0421: /**
0422: * Creates text hit info from the hit position
0423: * @param hitX - X coordinate relative to the origin of the layout
0424: * @param hitY - Y coordinate relative to the origin of the layout
0425: * @return hit info
0426: */
0427: @Override
0428: TextHitInfo hitTest(float hitX, float hitY) {
0429: hitX -= x;
0430:
0431: float glyphPositions[] = getGlyphVector()
0432: .getGlyphPositions(0, info.length + 1, null);
0433:
0434: int glyphIdx;
0435: boolean leading = false;
0436: for (glyphIdx = 1; glyphIdx <= info.length; glyphIdx++) {
0437: if (glyphPositions[(glyphIdx) * 2] >= hitX) {
0438: float advance = glyphPositions[(glyphIdx) * 2]
0439: - glyphPositions[(glyphIdx - 1) * 2];
0440: leading = glyphPositions[(glyphIdx - 1) * 2]
0441: + advance / 2 > hitX ? true : false;
0442: glyphIdx--;
0443: break;
0444: }
0445: }
0446:
0447: if (glyphIdx == info.length) {
0448: glyphIdx--;
0449: }
0450:
0451: int charIdx = getGlyphVector().getGlyphCharIndex(glyphIdx);
0452:
0453: return (leading) ^ ((info.level & 0x1) == 0x1) ? TextHitInfo
0454: .leading(charIdx + info.start)
0455: : TextHitInfo.trailing(charIdx + info.start);
0456: }
0457:
0458: /**
0459: * Collects GlyphJustificationInfo objects from the glyph vector
0460: * @return array of all GlyphJustificationInfo objects
0461: */
0462: private GlyphJustificationInfo[] getGlyphJustificationInfos() {
0463: if (gjis == null) {
0464: GlyphVector gv = getGlyphVector();
0465: int nGlyphs = gv.getNumGlyphs();
0466: int charIndicies[] = gv.getGlyphCharIndices(0, nGlyphs,
0467: null);
0468: gjis = new GlyphJustificationInfo[nGlyphs];
0469:
0470: // Patch: temporary patch, getGlyphJustificationInfo is not implemented
0471: float fontSize = info.font.getSize2D();
0472: GlyphJustificationInfo defaultInfo = new GlyphJustificationInfo(
0473: 0, // weight
0474: false, GlyphJustificationInfo.PRIORITY_NONE,
0475: 0,
0476: 0, // grow
0477: false, GlyphJustificationInfo.PRIORITY_NONE, 0,
0478: 0); // shrink
0479: GlyphJustificationInfo spaceInfo = new GlyphJustificationInfo(
0480: fontSize, // weight
0481: true,
0482: GlyphJustificationInfo.PRIORITY_WHITESPACE,
0483: 0,
0484: fontSize, // grow
0485: true,
0486: GlyphJustificationInfo.PRIORITY_WHITESPACE, 0,
0487: fontSize); // shrink
0488:
0489: ////////
0490: // Temporary patch, getGlyphJustificationInfo is not implemented
0491: for (int i = 0; i < nGlyphs; i++) {
0492: //gjis[i] = getGlyphVector().getGlyphJustificationInfo(i);
0493:
0494: char c = info.text[charIndicies[i] + info.start];
0495: if (Character.isWhitespace(c)) {
0496: gjis[i] = spaceInfo;
0497: } else {
0498: gjis[i] = defaultInfo;
0499: }
0500: // End patch
0501: }
0502: }
0503:
0504: return gjis;
0505: }
0506:
0507: /**
0508: * Collects justification information into JustificationInfo object
0509: * @param jInfo - JustificationInfo object
0510: */
0511: @Override
0512: void updateJustificationInfo(
0513: TextRunBreaker.JustificationInfo jInfo) {
0514: int lastChar = Math.min(jInfo.lastIdx, info.end)
0515: - info.start;
0516: boolean haveFirst = info.start <= jInfo.firstIdx;
0517: boolean haveLast = info.end >= (jInfo.lastIdx + 1);
0518:
0519: int prevGlyphIdx = -1;
0520: int currGlyphIdx;
0521:
0522: if (jInfo.grow) { // Check how much we can grow/shrink on current priority level
0523: for (int i = 0; i < lastChar; i++) {
0524: currGlyphIdx = getChar2Glyph()[i];
0525:
0526: if (currGlyphIdx == prevGlyphIdx) {
0527: // Several chars could be represented by one glyph,
0528: // suppose they are contiguous
0529: continue;
0530: }
0531: prevGlyphIdx = currGlyphIdx;
0532:
0533: GlyphJustificationInfo gji = getGlyphJustificationInfos()[currGlyphIdx];
0534: if (gji.growPriority == jInfo.priority) {
0535: jInfo.weight += gji.weight * 2;
0536: jInfo.growLimit += gji.growLeftLimit;
0537: jInfo.growLimit += gji.growRightLimit;
0538: if (gji.growAbsorb) {
0539: jInfo.absorbedWeight += gji.weight * 2;
0540: }
0541: }
0542: }
0543: } else {
0544: for (int i = 0; i < lastChar; i++) {
0545: currGlyphIdx = getChar2Glyph()[i];
0546: if (currGlyphIdx == prevGlyphIdx) {
0547: continue;
0548: }
0549: prevGlyphIdx = currGlyphIdx;
0550:
0551: GlyphJustificationInfo gji = getGlyphJustificationInfos()[currGlyphIdx];
0552: if (gji.shrinkPriority == jInfo.priority) {
0553: jInfo.weight += gji.weight * 2;
0554: jInfo.growLimit -= gji.shrinkLeftLimit;
0555: jInfo.growLimit -= gji.shrinkRightLimit;
0556: if (gji.shrinkAbsorb) {
0557: jInfo.absorbedWeight += gji.weight * 2;
0558: }
0559: }
0560: }
0561: }
0562:
0563: if (haveFirst) { // Don't add padding before first char
0564: GlyphJustificationInfo gji = getGlyphJustificationInfos()[getChar2Glyph()[0]];
0565: jInfo.weight -= gji.weight;
0566: if (jInfo.grow) {
0567: jInfo.growLimit -= gji.growLeftLimit;
0568: if (gji.growAbsorb) {
0569: jInfo.absorbedWeight -= gji.weight;
0570: }
0571: } else {
0572: jInfo.growLimit += gji.shrinkLeftLimit;
0573: if (gji.shrinkAbsorb) {
0574: jInfo.absorbedWeight -= gji.weight;
0575: }
0576: }
0577: }
0578:
0579: if (haveLast) { // Don't add padding after last char
0580: GlyphJustificationInfo gji = getGlyphJustificationInfos()[getChar2Glyph()[lastChar]];
0581: jInfo.weight -= gji.weight;
0582: if (jInfo.grow) {
0583: jInfo.growLimit -= gji.growRightLimit;
0584: if (gji.growAbsorb) {
0585: jInfo.absorbedWeight -= gji.weight;
0586: }
0587: } else {
0588: jInfo.growLimit += gji.shrinkRightLimit;
0589: if (gji.shrinkAbsorb) {
0590: jInfo.absorbedWeight -= gji.weight;
0591: }
0592: }
0593: }
0594: }
0595:
0596: /**
0597: * Performs justification of the segment.
0598: * Updates positions of individual characters.
0599: * @param jInfos - justification information, gathered by the previous passes
0600: * @return amount of growth or shrink of the segment
0601: */
0602: @Override
0603: float doJustification(TextRunBreaker.JustificationInfo jInfos[]) {
0604: int lastPriority = jInfos[jInfos.length - 1] == null ? -1
0605: : jInfos[jInfos.length - 1].priority;
0606:
0607: // Get the highest priority
0608: int highestPriority = 0;
0609: for (; highestPriority < jInfos.length; highestPriority++) {
0610: if (jInfos[highestPriority] != null) {
0611: break;
0612: }
0613: }
0614:
0615: if (highestPriority == jInfos.length) {
0616: return 0;
0617: }
0618:
0619: TextRunBreaker.JustificationInfo firstInfo = jInfos[highestPriority];
0620: TextRunBreaker.JustificationInfo lastInfo = lastPriority > 0 ? jInfos[lastPriority]
0621: : null;
0622:
0623: boolean haveFirst = info.start <= firstInfo.firstIdx;
0624: boolean haveLast = info.end >= (firstInfo.lastIdx + 1);
0625:
0626: // Here we suppose that GLYPHS are ordered LEFT TO RIGHT
0627: int firstGlyph = haveFirst ? getChar2Glyph()[firstInfo.firstIdx
0628: - info.start]
0629: : getChar2Glyph()[0];
0630:
0631: int lastGlyph = haveLast ? getChar2Glyph()[firstInfo.lastIdx
0632: - info.start]
0633: : getChar2Glyph()[info.length - 1];
0634: if (haveLast) {
0635: lastGlyph--;
0636: }
0637:
0638: TextRunBreaker.JustificationInfo currInfo;
0639: float glyphOffset = 0;
0640: float positionIncrement = 0;
0641: float sideIncrement = 0;
0642:
0643: if (haveFirst) { // Don't add padding before first char
0644: GlyphJustificationInfo gji = getGlyphJustificationInfos()[firstGlyph];
0645: currInfo = jInfos[gji.growPriority];
0646: if (currInfo != null) {
0647: if (currInfo.useLimits) {
0648: if (currInfo.absorb) {
0649: glyphOffset += gji.weight
0650: * currInfo.absorbedGapPerUnit;
0651: } else if (lastInfo != null
0652: && lastInfo.priority == currInfo.priority) {
0653: glyphOffset += gji.weight
0654: * lastInfo.absorbedGapPerUnit;
0655: }
0656: glyphOffset += firstInfo.grow ? gji.growRightLimit
0657: : -gji.shrinkRightLimit;
0658: } else {
0659: glyphOffset += gji.weight * currInfo.gapPerUnit;
0660: }
0661: }
0662:
0663: firstGlyph++;
0664: }
0665:
0666: if (firstInfo.grow) {
0667: for (int i = firstGlyph; i <= lastGlyph; i++) {
0668: GlyphJustificationInfo gji = getGlyphJustificationInfos()[i];
0669: currInfo = jInfos[gji.growPriority];
0670: if (currInfo == null) {
0671: // We still have to increment glyph position
0672: Point2D glyphPos = getGlyphVector()
0673: .getGlyphPosition(i);
0674: glyphPos.setLocation(glyphPos.getX()
0675: + glyphOffset, glyphPos.getY());
0676: getGlyphVector().setGlyphPosition(i, glyphPos);
0677:
0678: continue;
0679: }
0680:
0681: if (currInfo.useLimits) {
0682: glyphOffset += gji.growLeftLimit;
0683: if (currInfo.absorb) {
0684: sideIncrement = gji.weight
0685: * currInfo.absorbedGapPerUnit;
0686: glyphOffset += sideIncrement;
0687: positionIncrement = glyphOffset;
0688: glyphOffset += sideIncrement;
0689: } else if (lastInfo != null
0690: && lastInfo.priority == currInfo.priority) {
0691: sideIncrement = gji.weight
0692: * lastInfo.absorbedGapPerUnit;
0693: glyphOffset += sideIncrement;
0694: positionIncrement = glyphOffset;
0695: glyphOffset += sideIncrement;
0696: } else {
0697: positionIncrement = glyphOffset;
0698: }
0699: glyphOffset += gji.growRightLimit;
0700: } else {
0701: sideIncrement = gji.weight
0702: * currInfo.gapPerUnit;
0703: glyphOffset += sideIncrement;
0704: positionIncrement = glyphOffset;
0705: glyphOffset += sideIncrement;
0706: }
0707:
0708: Point2D glyphPos = getGlyphVector()
0709: .getGlyphPosition(i);
0710: glyphPos.setLocation(glyphPos.getX()
0711: + positionIncrement, glyphPos.getY());
0712: getGlyphVector().setGlyphPosition(i, glyphPos);
0713: }
0714: } else {
0715: for (int i = firstGlyph; i <= lastGlyph; i++) {
0716: GlyphJustificationInfo gji = getGlyphJustificationInfos()[i];
0717: currInfo = jInfos[gji.shrinkPriority];
0718: if (currInfo == null) {
0719: // We still have to increment glyph position
0720: Point2D glyphPos = getGlyphVector()
0721: .getGlyphPosition(i);
0722: glyphPos.setLocation(glyphPos.getX()
0723: + glyphOffset, glyphPos.getY());
0724: getGlyphVector().setGlyphPosition(i, glyphPos);
0725:
0726: continue;
0727: }
0728:
0729: if (currInfo.useLimits) {
0730: glyphOffset -= gji.shrinkLeftLimit;
0731: if (currInfo.absorb) {
0732: sideIncrement = gji.weight
0733: * currInfo.absorbedGapPerUnit;
0734: glyphOffset += sideIncrement;
0735: positionIncrement = glyphOffset;
0736: glyphOffset += sideIncrement;
0737: } else if (lastInfo != null
0738: && lastInfo.priority == currInfo.priority) {
0739: sideIncrement = gji.weight
0740: * lastInfo.absorbedGapPerUnit;
0741: glyphOffset += sideIncrement;
0742: positionIncrement = glyphOffset;
0743: glyphOffset += sideIncrement;
0744: } else {
0745: positionIncrement = glyphOffset;
0746: }
0747: glyphOffset -= gji.shrinkRightLimit;
0748: } else {
0749: sideIncrement = gji.weight
0750: * currInfo.gapPerUnit;
0751: glyphOffset += sideIncrement;
0752: positionIncrement = glyphOffset;
0753: glyphOffset += sideIncrement;
0754: }
0755:
0756: Point2D glyphPos = getGlyphVector()
0757: .getGlyphPosition(i);
0758: glyphPos.setLocation(glyphPos.getX()
0759: + positionIncrement, glyphPos.getY());
0760: getGlyphVector().setGlyphPosition(i, glyphPos);
0761: }
0762: }
0763:
0764: if (haveLast) { // Don't add padding after last char
0765: lastGlyph++;
0766:
0767: GlyphJustificationInfo gji = getGlyphJustificationInfos()[lastGlyph];
0768: currInfo = jInfos[gji.growPriority];
0769:
0770: if (currInfo != null) {
0771: if (currInfo.useLimits) {
0772: glyphOffset += firstInfo.grow ? gji.growLeftLimit
0773: : -gji.shrinkLeftLimit;
0774: if (currInfo.absorb) {
0775: glyphOffset += gji.weight
0776: * currInfo.absorbedGapPerUnit;
0777: } else if (lastInfo != null
0778: && lastInfo.priority == currInfo.priority) {
0779: glyphOffset += gji.weight
0780: * lastInfo.absorbedGapPerUnit;
0781: }
0782: } else {
0783: glyphOffset += gji.weight * currInfo.gapPerUnit;
0784: }
0785: }
0786:
0787: // Ajust positions of all glyphs after last glyph
0788: for (int i = lastGlyph; i < getGlyphVector()
0789: .getNumGlyphs() + 1; i++) {
0790: Point2D glyphPos = getGlyphVector()
0791: .getGlyphPosition(i);
0792: glyphPos.setLocation(glyphPos.getX() + glyphOffset,
0793: glyphPos.getY());
0794: getGlyphVector().setGlyphPosition(i, glyphPos);
0795: }
0796: } else { // Update position after last glyph in glyph vector -
0797: // to get correct advance for it
0798: Point2D glyphPos = getGlyphVector().getGlyphPosition(
0799: lastGlyph + 1);
0800: glyphPos.setLocation(glyphPos.getX() + glyphOffset,
0801: glyphPos.getY());
0802: getGlyphVector().setGlyphPosition(lastGlyph + 1,
0803: glyphPos);
0804: }
0805:
0806: gjis = null; // We don't need justification infos any more
0807: // Also we have to reset cached bounds and metrics
0808: this .visualBounds = null;
0809: this .logicalBounds = null;
0810:
0811: return glyphOffset; // How much our segment grown or shrunk
0812: }
0813: }
0814:
0815: public static class TextRunSegmentGraphic extends TextRunSegment {
0816: GraphicAttribute ga;
0817: int start;
0818: int length;
0819: float fullAdvance;
0820:
0821: TextRunSegmentGraphic(GraphicAttribute attr, int len, int start) {
0822: this .start = start;
0823: length = len;
0824: ga = attr;
0825: metrics = new BasicMetrics(ga);
0826: fullAdvance = ga.getAdvance() * length;
0827: }
0828:
0829: @Override
0830: public Object clone() {
0831: return new TextRunSegmentGraphic(ga, length, start);
0832: }
0833:
0834: // Renders this text run segment
0835: @Override
0836: void draw(Graphics2D g2d, float xOffset, float yOffset) {
0837: if (decoration != null) {
0838: TextDecorator.prepareGraphics(this , g2d, xOffset,
0839: yOffset);
0840: }
0841:
0842: float xPos = x + xOffset;
0843: float yPos = y + yOffset;
0844:
0845: for (int i = 0; i < length; i++) {
0846: ga.draw(g2d, xPos, yPos);
0847: xPos += ga.getAdvance();
0848: }
0849:
0850: if (decoration != null) {
0851: TextDecorator.drawTextDecorations(this , g2d, xOffset,
0852: yOffset);
0853: TextDecorator.restoreGraphics(decoration, g2d);
0854: }
0855: }
0856:
0857: // Returns visual bounds of this segment
0858: @Override
0859: Rectangle2D getVisualBounds() {
0860: if (visualBounds == null) {
0861: Rectangle2D bounds = ga.getBounds();
0862:
0863: // First and last chars can be out of logical bounds, so we calculate
0864: // (bounds.getWidth() - ga.getAdvance()) which is exactly the difference
0865: bounds.setRect(bounds.getMinX() + x, bounds.getMinY()
0866: + y, bounds.getWidth() - ga.getAdvance()
0867: + getAdvance(), bounds.getHeight());
0868: visualBounds = TextDecorator.extendVisualBounds(this ,
0869: bounds, decoration);
0870: }
0871:
0872: return (Rectangle2D) visualBounds.clone();
0873: }
0874:
0875: @Override
0876: Rectangle2D getLogicalBounds() {
0877: if (logicalBounds == null) {
0878: logicalBounds = new Rectangle2D.Float(x, y
0879: - metrics.ascent, getAdvance(), metrics.ascent
0880: + metrics.descent);
0881: }
0882:
0883: return (Rectangle2D) logicalBounds.clone();
0884: }
0885:
0886: @Override
0887: float getAdvance() {
0888: return fullAdvance;
0889: }
0890:
0891: @Override
0892: float getAdvanceDelta(int start, int end) {
0893: return ga.getAdvance() * (end - start);
0894: }
0895:
0896: @Override
0897: int getCharIndexFromAdvance(float advance, int start) {
0898: start -= this .start;
0899:
0900: if (start < 0) {
0901: start = 0;
0902: }
0903:
0904: int charOffset = (int) (advance / ga.getAdvance());
0905:
0906: if (charOffset + start > length) {
0907: return length + this .start;
0908: }
0909: return charOffset + start + this .start;
0910: }
0911:
0912: @Override
0913: int getStart() {
0914: return start;
0915: }
0916:
0917: @Override
0918: int getEnd() {
0919: return start + length;
0920: }
0921:
0922: @Override
0923: int getLength() {
0924: return length;
0925: }
0926:
0927: @Override
0928: Shape getCharsBlackBoxBounds(int start, int limit) {
0929: start -= this .start;
0930: limit -= this .start;
0931:
0932: if (limit > length) {
0933: limit = length;
0934: }
0935:
0936: Rectangle2D charBounds = ga.getBounds();
0937: charBounds.setRect(charBounds.getX() + ga.getAdvance()
0938: * start + x, charBounds.getY() + y, charBounds
0939: .getWidth()
0940: + ga.getAdvance() * (limit - start), charBounds
0941: .getHeight());
0942:
0943: return charBounds;
0944: }
0945:
0946: @Override
0947: float getCharPosition(int index) {
0948: index -= start;
0949: if (index > length) {
0950: index = length;
0951: }
0952:
0953: return ga.getAdvance() * index + x;
0954: }
0955:
0956: @Override
0957: float getCharAdvance(int index) {
0958: return ga.getAdvance();
0959: }
0960:
0961: @Override
0962: Shape getOutline() {
0963: AffineTransform t = AffineTransform.getTranslateInstance(x,
0964: y);
0965: return t
0966: .createTransformedShape(TextDecorator
0967: .extendOutline(this , getVisualBounds(),
0968: decoration));
0969: }
0970:
0971: @Override
0972: boolean charHasZeroAdvance(int index) {
0973: return false;
0974: }
0975:
0976: @Override
0977: TextHitInfo hitTest(float hitX, float hitY) {
0978: hitX -= x;
0979:
0980: float tmp = hitX / ga.getAdvance();
0981: int hitIndex = Math.round(tmp);
0982:
0983: if (tmp > hitIndex) {
0984: return TextHitInfo.leading(hitIndex + this .start);
0985: }
0986: return TextHitInfo.trailing(hitIndex + this .start);
0987: }
0988:
0989: @Override
0990: void updateJustificationInfo(
0991: TextRunBreaker.JustificationInfo jInfo) {
0992: // Do nothing
0993: }
0994:
0995: @Override
0996: float doJustification(TextRunBreaker.JustificationInfo jInfos[]) {
0997: // Do nothing
0998: return 0;
0999: }
1000: }
1001: }
|