
com.google.code.appengine.awt.font.TextLayout Maven / Gradle / Ivy
The newest version!
/*
* 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
*/
package com.google.code.appengine.awt.font;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Map;
import org.apache.harmony.awt.gl.font.BasicMetrics;
import org.apache.harmony.awt.gl.font.CaretManager;
import org.apache.harmony.awt.gl.font.TextMetricsCalculator;
import org.apache.harmony.awt.gl.font.TextRunBreaker;
import org.apache.harmony.awt.internal.nls.Messages;
import com.google.code.appengine.awt.Font;
import com.google.code.appengine.awt.Graphics2D;
import com.google.code.appengine.awt.Shape;
import com.google.code.appengine.awt.geom.AffineTransform;
import com.google.code.appengine.awt.geom.GeneralPath;
import com.google.code.appengine.awt.geom.Rectangle2D;
public final class TextLayout implements Cloneable {
public static class CaretPolicy {
public CaretPolicy() {
// Nothing to do
}
public TextHitInfo getStrongCaret(TextHitInfo hit1, TextHitInfo hit2, TextLayout layout) {
// Stronger hit is the one with greater level.
// If the level is same, leading edge is stronger.
int level1 = layout.getCharacterLevel(hit1.getCharIndex());
int level2 = layout.getCharacterLevel(hit2.getCharIndex());
if (level1 == level2) {
return (hit2.isLeadingEdge() && (!hit1.isLeadingEdge())) ? hit2 : hit1;
}
return level1 > level2 ? hit1 : hit2;
}
}
public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
private TextRunBreaker breaker;
private boolean metricsValid = false;
private TextMetricsCalculator tmc;
private BasicMetrics metrics;
private CaretManager caretManager;
float justificationWidth = -1;
public TextLayout(String string, Font font, FontRenderContext frc) {
if (string == null){
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (font == null){
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "font")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (string.length() == 0){
// awt.02='{0}' parameter has zero length
throw new IllegalArgumentException(Messages.getString("awt.02", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
AttributedString as = new AttributedString(string);
as.addAttribute(TextAttribute.FONT, font);
this.breaker = new TextRunBreaker(as.getIterator(), frc);
caretManager = new CaretManager(breaker);
}
public TextLayout(
String string,
Map extends java.text.AttributedCharacterIterator.Attribute, ?> attributes,
FontRenderContext frc ) {
if (string == null){
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (attributes == null){
// awt.01='{0}' parameter is null
throw new IllegalArgumentException(Messages.getString("awt.01", "attributes")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (string.length() == 0){
// awt.02='{0}' parameter has zero length
throw new IllegalArgumentException(Messages.getString("awt.02", "string")); //$NON-NLS-1$ //$NON-NLS-2$
}
AttributedString as = new AttributedString(string);
as.addAttributes(attributes, 0, string.length());
this.breaker = new TextRunBreaker(as.getIterator(), frc);
caretManager = new CaretManager(breaker);
}
public TextLayout(AttributedCharacterIterator text, FontRenderContext frc) {
if (text == null){
// awt.03='{0}' iterator parameter is null
throw new IllegalArgumentException(Messages.getString("awt.03", "text")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (text.getBeginIndex() == text.getEndIndex()){
// awt.04='{0}' iterator parameter has zero length
throw new IllegalArgumentException(Messages.getString("awt.04", "text")); //$NON-NLS-1$ //$NON-NLS-2$
}
this.breaker = new TextRunBreaker(text, frc);
caretManager = new CaretManager(breaker);
}
TextLayout(TextRunBreaker breaker) {
this.breaker = breaker;
caretManager = new CaretManager(this.breaker);
}
@Override
public int hashCode() {
return breaker.hashCode();
}
@Override
protected Object clone() {
TextLayout res = new TextLayout((TextRunBreaker) breaker.clone());
if (justificationWidth >= 0) {
res.handleJustify(justificationWidth);
}
return res;
}
public boolean equals(TextLayout layout) {
if (layout == null) {
return false;
}
return this.breaker.equals(layout.breaker);
}
@Override
public boolean equals(Object obj) {
return obj instanceof TextLayout ? equals((TextLayout) obj) : false;
}
@Override
public String toString() { // what for?
return super.toString();
}
public void draw(Graphics2D g2d, float x, float y) {
updateMetrics();
breaker.drawSegments(g2d, x ,y);
}
private void updateMetrics() {
if (!metricsValid) {
breaker.createAllSegments();
tmc = new TextMetricsCalculator(breaker);
metrics = tmc.createMetrics();
metricsValid = true;
}
}
public float getAdvance() {
updateMetrics();
return metrics.getAdvance();
}
public float getAscent() {
updateMetrics();
return metrics.getAscent();
}
public byte getBaseline() {
updateMetrics();
return (byte) metrics.getBaseLineIndex();
}
public float[] getBaselineOffsets() {
updateMetrics();
return tmc.getBaselineOffsets();
}
public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
updateMetrics();
if (firstEndpoint < secondEndpoint) {
return breaker.getBlackBoxBounds(firstEndpoint, secondEndpoint);
}
return breaker.getBlackBoxBounds(secondEndpoint, firstEndpoint);
}
public Rectangle2D getBounds() {
updateMetrics();
return breaker.getVisualBounds();
}
public float[] getCaretInfo(TextHitInfo hitInfo) {
updateMetrics();
return caretManager.getCaretInfo(hitInfo);
}
public float[] getCaretInfo(TextHitInfo hitInfo, Rectangle2D bounds) {
updateMetrics();
return caretManager.getCaretInfo(hitInfo);
}
public Shape getCaretShape(TextHitInfo hitInfo, Rectangle2D bounds) {
updateMetrics();
return caretManager.getCaretShape(hitInfo, this);
}
public Shape getCaretShape(TextHitInfo hitInfo) {
updateMetrics();
return caretManager.getCaretShape(hitInfo, this);
}
public Shape[] getCaretShapes(int offset) {
return getCaretShapes(offset, null, TextLayout.DEFAULT_CARET_POLICY);
}
public Shape[] getCaretShapes(int offset, Rectangle2D bounds) {
return getCaretShapes(offset, bounds, TextLayout.DEFAULT_CARET_POLICY);
}
public Shape[] getCaretShapes(int offset, Rectangle2D bounds, TextLayout.CaretPolicy policy) {
if (offset < 0 || offset > breaker.getCharCount()) {
// awt.195=Offset is out of bounds
throw new IllegalArgumentException(Messages.getString("awt.195")); //$NON-NLS-1$
}
updateMetrics();
return caretManager.getCaretShapes(offset, bounds, policy, this);
}
public int getCharacterCount() {
return breaker.getCharCount();
}
public byte getCharacterLevel(int index) {
if (index == -1 || index == getCharacterCount()) {
return (byte) breaker.getBaseLevel();
}
return breaker.getLevel(index);
}
public float getDescent() {
updateMetrics();
return metrics.getDescent();
}
public TextLayout getJustifiedLayout(float justificationWidth) throws Error {
float justification = breaker.getJustification();
if (justification < 0) {
// awt.196=Justification impossible, layout already justified
throw new Error(Messages.getString("awt.196")); //$NON-NLS-1$
} else if (justification == 0) {
return this;
}
TextLayout justifiedLayout = new TextLayout((TextRunBreaker) breaker.clone());
justifiedLayout.handleJustify(justificationWidth);
return justifiedLayout;
}
public float getLeading() {
updateMetrics();
return metrics.getLeading();
}
public Shape getLogicalHighlightShape(int firstEndpoint, int secondEndpoint) {
updateMetrics();
return getLogicalHighlightShape(firstEndpoint, secondEndpoint, breaker.getLogicalBounds());
}
public Shape getLogicalHighlightShape(
int firstEndpoint,
int secondEndpoint,
Rectangle2D bounds
) {
updateMetrics();
if (firstEndpoint > secondEndpoint) {
if (secondEndpoint < 0 || firstEndpoint > breaker.getCharCount()) {
// awt.197=Endpoints are out of range
throw new IllegalArgumentException(Messages.getString("awt.197")); //$NON-NLS-1$
}
return caretManager.getLogicalHighlightShape(
secondEndpoint,
firstEndpoint,
bounds,
this
);
}
if (firstEndpoint < 0 || secondEndpoint > breaker.getCharCount()) {
// awt.197=Endpoints are out of range
throw new IllegalArgumentException(Messages.getString("awt.197")); //$NON-NLS-1$
}
return caretManager.getLogicalHighlightShape(
firstEndpoint,
secondEndpoint,
bounds,
this
);
}
public int[] getLogicalRangesForVisualSelection(TextHitInfo hit1, TextHitInfo hit2) {
return caretManager.getLogicalRangesForVisualSelection(hit1, hit2);
}
public TextHitInfo getNextLeftHit(int offset) {
return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
}
public TextHitInfo getNextLeftHit(TextHitInfo hitInfo) {
breaker.createAllSegments();
return caretManager.getNextLeftHit(hitInfo);
}
public TextHitInfo getNextLeftHit(int offset, TextLayout.CaretPolicy policy) {
if (offset < 0 || offset > breaker.getCharCount()) {
// awt.195=Offset is out of bounds
throw new IllegalArgumentException(Messages.getString("awt.195")); //$NON-NLS-1$
}
TextHitInfo hit = TextHitInfo.afterOffset(offset);
TextHitInfo strongHit = policy.getStrongCaret(hit, hit.getOtherHit(), this);
TextHitInfo nextLeftHit = getNextLeftHit(strongHit);
if (nextLeftHit != null) {
return policy.getStrongCaret(getVisualOtherHit(nextLeftHit), nextLeftHit, this);
}
return null;
}
public TextHitInfo getNextRightHit(TextHitInfo hitInfo) {
breaker.createAllSegments();
return caretManager.getNextRightHit(hitInfo);
}
public TextHitInfo getNextRightHit(int offset) {
return getNextRightHit(offset, DEFAULT_CARET_POLICY);
}
public TextHitInfo getNextRightHit(int offset, TextLayout.CaretPolicy policy) {
if (offset < 0 || offset > breaker.getCharCount()) {
// awt.195=Offset is out of bounds
throw new IllegalArgumentException(Messages.getString("awt.195")); //$NON-NLS-1$
}
TextHitInfo hit = TextHitInfo.afterOffset(offset);
TextHitInfo strongHit = policy.getStrongCaret(hit, hit.getOtherHit(), this);
TextHitInfo nextRightHit = getNextRightHit(strongHit);
if (nextRightHit != null) {
return policy.getStrongCaret(getVisualOtherHit(nextRightHit), nextRightHit, this);
}
return null;
}
public Shape getOutline(AffineTransform xform) {
breaker.createAllSegments();
GeneralPath outline = breaker.getOutline();
if (outline != null && xform != null) {
outline.transform(xform);
}
return outline;
}
public float getVisibleAdvance() {
updateMetrics();
// Trailing whitespace _SHOULD_ be reordered (Unicode spec) to
// base direction, so it is also trailing
// in logical representation. We use this fact.
int lastNonWhitespace = breaker.getLastNonWhitespace();
if (lastNonWhitespace < 0) {
return 0;
} else if (lastNonWhitespace == getCharacterCount()-1) {
return getAdvance();
} else if (justificationWidth >= 0) { // Layout is justified
return justificationWidth;
} else {
breaker.pushSegments(
breaker.getACI().getBeginIndex(),
lastNonWhitespace + breaker.getACI().getBeginIndex() + 1
);
breaker.createAllSegments();
float visAdvance = tmc.createMetrics().getAdvance();
breaker.popSegments();
return visAdvance;
}
}
public Shape getVisualHighlightShape(TextHitInfo hit1, TextHitInfo hit2, Rectangle2D bounds) {
return caretManager.getVisualHighlightShape(hit1, hit2, bounds, this);
}
public Shape getVisualHighlightShape(TextHitInfo hit1, TextHitInfo hit2) {
breaker.createAllSegments();
return caretManager.getVisualHighlightShape(hit1, hit2, breaker.getLogicalBounds(), this);
}
public TextHitInfo getVisualOtherHit(TextHitInfo hitInfo) {
return caretManager.getVisualOtherHit(hitInfo);
}
protected void handleJustify(float justificationWidth) {
float justification = breaker.getJustification();
if (justification < 0) {
// awt.196=Justification impossible, layout already justified
throw new IllegalStateException(Messages.getString("awt.196")); //$NON-NLS-1$
} else if (justification == 0) {
return;
}
float gap = (justificationWidth - getVisibleAdvance()) * justification;
breaker.justify(gap);
this.justificationWidth = justificationWidth;
// Correct metrics
tmc = new TextMetricsCalculator(breaker);
tmc.correctAdvance(metrics);
}
public TextHitInfo hitTestChar(float x, float y) {
return hitTestChar(x, y, getBounds());
}
public TextHitInfo hitTestChar(float x, float y, Rectangle2D bounds) {
if (x > bounds.getMaxX()) {
return breaker.isLTR() ?
TextHitInfo.trailing(breaker.getCharCount() - 1) : TextHitInfo.leading(0);
}
if (x < bounds.getMinX()) {
return breaker.isLTR() ?
TextHitInfo.leading(0) : TextHitInfo.trailing(breaker.getCharCount() - 1);
}
return breaker.hitTest(x, y);
}
public boolean isLeftToRight() {
return breaker.isLTR();
}
public boolean isVertical() {
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy