001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Oleg V. Khaschansky
019: * @version $Revision$
020: */package java.awt.font;
021:
022: import java.awt.Font;
023: import java.awt.Graphics2D;
024: import java.awt.Shape;
025: import java.awt.geom.AffineTransform;
026: import java.awt.geom.Rectangle2D;
027: import java.awt.geom.GeneralPath;
028: import java.text.AttributedCharacterIterator;
029: import java.text.AttributedString;
030: import java.util.Map;
031:
032: import org.apache.harmony.awt.gl.font.BasicMetrics;
033: import org.apache.harmony.awt.gl.font.CaretManager;
034: import org.apache.harmony.awt.gl.font.TextMetricsCalculator;
035: import org.apache.harmony.awt.gl.font.TextRunBreaker;
036: import org.apache.harmony.awt.internal.nls.Messages;
037:
038: public final class TextLayout implements Cloneable {
039:
040: public static class CaretPolicy {
041:
042: public CaretPolicy() {
043: // Nothing to do
044: }
045:
046: public TextHitInfo getStrongCaret(TextHitInfo hit1,
047: TextHitInfo hit2, TextLayout layout) {
048: // Stronger hit is the one with greater level.
049: // If the level is same, leading edge is stronger.
050:
051: int level1 = layout.getCharacterLevel(hit1.getCharIndex());
052: int level2 = layout.getCharacterLevel(hit2.getCharIndex());
053:
054: if (level1 == level2) {
055: return (hit2.isLeadingEdge() && (!hit1.isLeadingEdge())) ? hit2
056: : hit1;
057: }
058: return level1 > level2 ? hit1 : hit2;
059: }
060:
061: }
062:
063: public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
064:
065: private TextRunBreaker breaker;
066: private boolean metricsValid = false;
067: private TextMetricsCalculator tmc;
068: private BasicMetrics metrics;
069: private CaretManager caretManager;
070: float justificationWidth = -1;
071:
072: public TextLayout(String string, Font font, FontRenderContext frc) {
073: if (string == null) {
074: // awt.01='{0}' parameter is null
075: throw new IllegalArgumentException(Messages.getString(
076: "awt.01", "string")); //$NON-NLS-1$ //$NON-NLS-2$
077: }
078:
079: if (font == null) {
080: // awt.01='{0}' parameter is null
081: throw new IllegalArgumentException(Messages.getString(
082: "awt.01", "font")); //$NON-NLS-1$ //$NON-NLS-2$
083: }
084:
085: if (string.length() == 0) {
086: // awt.02='{0}' parameter has zero length
087: throw new IllegalArgumentException(Messages.getString(
088: "awt.02", "string")); //$NON-NLS-1$ //$NON-NLS-2$
089: }
090:
091: AttributedString as = new AttributedString(string);
092: as.addAttribute(TextAttribute.FONT, font);
093: this .breaker = new TextRunBreaker(as.getIterator(), frc);
094: caretManager = new CaretManager(breaker);
095: }
096:
097: public TextLayout(
098: String string,
099: Map<? extends java.text.AttributedCharacterIterator.Attribute, ?> attributes,
100: FontRenderContext frc) {
101: if (string == null) {
102: // awt.01='{0}' parameter is null
103: throw new IllegalArgumentException(Messages.getString(
104: "awt.01", "string")); //$NON-NLS-1$ //$NON-NLS-2$
105: }
106:
107: if (attributes == null) {
108: // awt.01='{0}' parameter is null
109: throw new IllegalArgumentException(Messages.getString(
110: "awt.01", "attributes")); //$NON-NLS-1$ //$NON-NLS-2$
111: }
112:
113: if (string.length() == 0) {
114: // awt.02='{0}' parameter has zero length
115: throw new IllegalArgumentException(Messages.getString(
116: "awt.02", "string")); //$NON-NLS-1$ //$NON-NLS-2$
117: }
118:
119: AttributedString as = new AttributedString(string);
120: as.addAttributes(attributes, 0, string.length());
121: this .breaker = new TextRunBreaker(as.getIterator(), frc);
122: caretManager = new CaretManager(breaker);
123: }
124:
125: public TextLayout(AttributedCharacterIterator text,
126: FontRenderContext frc) {
127: if (text == null) {
128: // awt.03='{0}' iterator parameter is null
129: throw new IllegalArgumentException(Messages.getString(
130: "awt.03", "text")); //$NON-NLS-1$ //$NON-NLS-2$
131: }
132:
133: if (text.getBeginIndex() == text.getEndIndex()) {
134: // awt.04='{0}' iterator parameter has zero length
135: throw new IllegalArgumentException(Messages.getString(
136: "awt.04", "text")); //$NON-NLS-1$ //$NON-NLS-2$
137: }
138:
139: this .breaker = new TextRunBreaker(text, frc);
140: caretManager = new CaretManager(breaker);
141: }
142:
143: TextLayout(TextRunBreaker breaker) {
144: this .breaker = breaker;
145: caretManager = new CaretManager(this .breaker);
146: }
147:
148: @Override
149: public int hashCode() {
150: return breaker.hashCode();
151: }
152:
153: @Override
154: protected Object clone() {
155: TextLayout res = new TextLayout((TextRunBreaker) breaker
156: .clone());
157:
158: if (justificationWidth >= 0) {
159: res.handleJustify(justificationWidth);
160: }
161:
162: return res;
163: }
164:
165: public boolean equals(TextLayout layout) {
166: if (layout == null) {
167: return false;
168: }
169: return this .breaker.equals(layout.breaker);
170: }
171:
172: @Override
173: public boolean equals(Object obj) {
174: return obj instanceof TextLayout ? equals((TextLayout) obj)
175: : false;
176: }
177:
178: @Override
179: public String toString() { // what for?
180: return super .toString();
181: }
182:
183: public void draw(Graphics2D g2d, float x, float y) {
184: updateMetrics();
185: breaker.drawSegments(g2d, x, y);
186: }
187:
188: private void updateMetrics() {
189: if (!metricsValid) {
190: breaker.createAllSegments();
191: tmc = new TextMetricsCalculator(breaker);
192: metrics = tmc.createMetrics();
193: metricsValid = true;
194: }
195: }
196:
197: public float getAdvance() {
198: updateMetrics();
199: return metrics.getAdvance();
200: }
201:
202: public float getAscent() {
203: updateMetrics();
204: return metrics.getAscent();
205: }
206:
207: public byte getBaseline() {
208: updateMetrics();
209: return (byte) metrics.getBaseLineIndex();
210: }
211:
212: public float[] getBaselineOffsets() {
213: updateMetrics();
214: return tmc.getBaselineOffsets();
215: }
216:
217: public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
218: updateMetrics();
219: if (firstEndpoint < secondEndpoint) {
220: return breaker.getBlackBoxBounds(firstEndpoint,
221: secondEndpoint);
222: }
223: return breaker.getBlackBoxBounds(secondEndpoint, firstEndpoint);
224: }
225:
226: public Rectangle2D getBounds() {
227: updateMetrics();
228: return breaker.getVisualBounds();
229: }
230:
231: public float[] getCaretInfo(TextHitInfo hitInfo) {
232: updateMetrics();
233: return caretManager.getCaretInfo(hitInfo);
234: }
235:
236: public float[] getCaretInfo(TextHitInfo hitInfo, Rectangle2D bounds) {
237: updateMetrics();
238: return caretManager.getCaretInfo(hitInfo);
239: }
240:
241: public Shape getCaretShape(TextHitInfo hitInfo, Rectangle2D bounds) {
242: updateMetrics();
243: return caretManager.getCaretShape(hitInfo, this );
244: }
245:
246: public Shape getCaretShape(TextHitInfo hitInfo) {
247: updateMetrics();
248: return caretManager.getCaretShape(hitInfo, this );
249: }
250:
251: public Shape[] getCaretShapes(int offset) {
252: return getCaretShapes(offset, null,
253: TextLayout.DEFAULT_CARET_POLICY);
254: }
255:
256: public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
257: return getCaretShapes(offset, bounds,
258: TextLayout.DEFAULT_CARET_POLICY);
259: }
260:
261: public Shape[] getCaretShapes(int offset, Rectangle2D bounds,
262: TextLayout.CaretPolicy policy) {
263: if (offset < 0 || offset > breaker.getCharCount()) {
264: // awt.195=Offset is out of bounds
265: throw new IllegalArgumentException(Messages
266: .getString("awt.195")); //$NON-NLS-1$
267: }
268:
269: updateMetrics();
270: return caretManager
271: .getCaretShapes(offset, bounds, policy, this );
272: }
273:
274: public int getCharacterCount() {
275: return breaker.getCharCount();
276: }
277:
278: public byte getCharacterLevel(int index) {
279: if (index == -1 || index == getCharacterCount()) {
280: return (byte) breaker.getBaseLevel();
281: }
282: return breaker.getLevel(index);
283: }
284:
285: public float getDescent() {
286: updateMetrics();
287: return metrics.getDescent();
288: }
289:
290: public TextLayout getJustifiedLayout(float justificationWidth)
291: throws Error {
292: float justification = breaker.getJustification();
293:
294: if (justification < 0) {
295: // awt.196=Justification impossible, layout already justified
296: throw new Error(Messages.getString("awt.196")); //$NON-NLS-1$
297: } else if (justification == 0) {
298: return this ;
299: }
300:
301: TextLayout justifiedLayout = new TextLayout(
302: (TextRunBreaker) breaker.clone());
303: justifiedLayout.handleJustify(justificationWidth);
304: return justifiedLayout;
305: }
306:
307: public float getLeading() {
308: updateMetrics();
309: return metrics.getLeading();
310: }
311:
312: public Shape getLogicalHighlightShape(int firstEndpoint,
313: int secondEndpoint) {
314: updateMetrics();
315: return getLogicalHighlightShape(firstEndpoint, secondEndpoint,
316: breaker.getLogicalBounds());
317: }
318:
319: public Shape getLogicalHighlightShape(int firstEndpoint,
320: int secondEndpoint, Rectangle2D bounds) {
321: updateMetrics();
322:
323: if (firstEndpoint > secondEndpoint) {
324: if (secondEndpoint < 0
325: || firstEndpoint > breaker.getCharCount()) {
326: // awt.197=Endpoints are out of range
327: throw new IllegalArgumentException(Messages
328: .getString("awt.197")); //$NON-NLS-1$
329: }
330: return caretManager.getLogicalHighlightShape(
331: secondEndpoint, firstEndpoint, bounds, this );
332: }
333: if (firstEndpoint < 0
334: || secondEndpoint > breaker.getCharCount()) {
335: // awt.197=Endpoints are out of range
336: throw new IllegalArgumentException(Messages
337: .getString("awt.197")); //$NON-NLS-1$
338: }
339: return caretManager.getLogicalHighlightShape(firstEndpoint,
340: secondEndpoint, bounds, this );
341: }
342:
343: public int[] getLogicalRangesForVisualSelection(TextHitInfo hit1,
344: TextHitInfo hit2) {
345: return caretManager.getLogicalRangesForVisualSelection(hit1,
346: hit2);
347: }
348:
349: public TextHitInfo getNextLeftHit(int offset) {
350: return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
351: }
352:
353: public TextHitInfo getNextLeftHit(TextHitInfo hitInfo) {
354: breaker.createAllSegments();
355: return caretManager.getNextLeftHit(hitInfo);
356: }
357:
358: public TextHitInfo getNextLeftHit(int offset,
359: TextLayout.CaretPolicy policy) {
360: if (offset < 0 || offset > breaker.getCharCount()) {
361: // awt.195=Offset is out of bounds
362: throw new IllegalArgumentException(Messages
363: .getString("awt.195")); //$NON-NLS-1$
364: }
365:
366: TextHitInfo hit = TextHitInfo.afterOffset(offset);
367: TextHitInfo strongHit = policy.getStrongCaret(hit, hit
368: .getOtherHit(), this );
369: TextHitInfo nextLeftHit = getNextLeftHit(strongHit);
370:
371: if (nextLeftHit != null) {
372: return policy.getStrongCaret(
373: getVisualOtherHit(nextLeftHit), nextLeftHit, this );
374: }
375: return null;
376: }
377:
378: public TextHitInfo getNextRightHit(TextHitInfo hitInfo) {
379: breaker.createAllSegments();
380: return caretManager.getNextRightHit(hitInfo);
381: }
382:
383: public TextHitInfo getNextRightHit(int offset) {
384: return getNextRightHit(offset, DEFAULT_CARET_POLICY);
385: }
386:
387: public TextHitInfo getNextRightHit(int offset,
388: TextLayout.CaretPolicy policy) {
389: if (offset < 0 || offset > breaker.getCharCount()) {
390: // awt.195=Offset is out of bounds
391: throw new IllegalArgumentException(Messages
392: .getString("awt.195")); //$NON-NLS-1$
393: }
394:
395: TextHitInfo hit = TextHitInfo.afterOffset(offset);
396: TextHitInfo strongHit = policy.getStrongCaret(hit, hit
397: .getOtherHit(), this );
398: TextHitInfo nextRightHit = getNextRightHit(strongHit);
399:
400: if (nextRightHit != null) {
401: return policy
402: .getStrongCaret(getVisualOtherHit(nextRightHit),
403: nextRightHit, this );
404: }
405: return null;
406: }
407:
408: public Shape getOutline(AffineTransform xform) {
409: breaker.createAllSegments();
410:
411: GeneralPath outline = breaker.getOutline();
412:
413: if (outline != null && xform != null) {
414: outline.transform(xform);
415: }
416:
417: return outline;
418: }
419:
420: public float getVisibleAdvance() {
421: updateMetrics();
422:
423: // Trailing whitespace _SHOULD_ be reordered (Unicode spec) to
424: // base direction, so it is also trailing
425: // in logical representation. We use this fact.
426: int lastNonWhitespace = breaker.getLastNonWhitespace();
427:
428: if (lastNonWhitespace < 0) {
429: return 0;
430: } else if (lastNonWhitespace == getCharacterCount() - 1) {
431: return getAdvance();
432: } else if (justificationWidth >= 0) { // Layout is justified
433: return justificationWidth;
434: } else {
435: breaker.pushSegments(breaker.getACI().getBeginIndex(),
436: lastNonWhitespace
437: + breaker.getACI().getBeginIndex() + 1);
438:
439: breaker.createAllSegments();
440:
441: float visAdvance = tmc.createMetrics().getAdvance();
442:
443: breaker.popSegments();
444: return visAdvance;
445: }
446: }
447:
448: public Shape getVisualHighlightShape(TextHitInfo hit1,
449: TextHitInfo hit2, Rectangle2D bounds) {
450: return caretManager.getVisualHighlightShape(hit1, hit2, bounds,
451: this );
452: }
453:
454: public Shape getVisualHighlightShape(TextHitInfo hit1,
455: TextHitInfo hit2) {
456: breaker.createAllSegments();
457: return caretManager.getVisualHighlightShape(hit1, hit2, breaker
458: .getLogicalBounds(), this );
459: }
460:
461: public TextHitInfo getVisualOtherHit(TextHitInfo hitInfo) {
462: return caretManager.getVisualOtherHit(hitInfo);
463: }
464:
465: protected void handleJustify(float justificationWidth) {
466: float justification = breaker.getJustification();
467:
468: if (justification < 0) {
469: // awt.196=Justification impossible, layout already justified
470: throw new IllegalStateException(Messages
471: .getString("awt.196")); //$NON-NLS-1$
472: } else if (justification == 0) {
473: return;
474: }
475:
476: float gap = (justificationWidth - getVisibleAdvance())
477: * justification;
478: breaker.justify(gap);
479: this .justificationWidth = justificationWidth;
480:
481: // Correct metrics
482: tmc = new TextMetricsCalculator(breaker);
483: tmc.correctAdvance(metrics);
484: }
485:
486: public TextHitInfo hitTestChar(float x, float y) {
487: return hitTestChar(x, y, getBounds());
488: }
489:
490: public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
491: if (x > bounds.getMaxX()) {
492: return breaker.isLTR() ? TextHitInfo.trailing(breaker
493: .getCharCount() - 1) : TextHitInfo.leading(0);
494: }
495:
496: if (x < bounds.getMinX()) {
497: return breaker.isLTR() ? TextHitInfo.leading(0)
498: : TextHitInfo.trailing(breaker.getCharCount() - 1);
499: }
500:
501: return breaker.hitTest(x, y);
502: }
503:
504: public boolean isLeftToRight() {
505: return breaker.isLTR();
506: }
507:
508: public boolean isVertical() {
509: return false;
510: }
511: }
|