org.apache.harmony.awt.gl.font.TextRunBreaker 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 org.apache.harmony.awt.gl.font;
import java.text.Annotation;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.harmony.awt.gl.font.TextDecorator.Decoration;
import org.apache.harmony.awt.internal.nls.Messages;
import org.apache.harmony.misc.HashCode;
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.font.FontRenderContext;
import com.google.code.appengine.awt.font.GlyphJustificationInfo;
import com.google.code.appengine.awt.font.GraphicAttribute;
import com.google.code.appengine.awt.font.TextAttribute;
import com.google.code.appengine.awt.font.TextHitInfo;
import com.google.code.appengine.awt.geom.GeneralPath;
import com.google.code.appengine.awt.geom.Rectangle2D;
/**
* This class is responsible for breaking the text into the run segments
* with constant font, style, other text attributes and direction.
* It also stores the created text run segments and covers functionality
* related to the operations on the set of segments, like calculating metrics,
* rendering, justification, hit testing, etc.
*/
public class TextRunBreaker implements Cloneable {
AttributedCharacterIterator aci;
FontRenderContext frc;
char[] text;
byte[] levels;
HashMap fonts;
HashMap decorations;
// Related to default font substitution
int forcedFontRunStarts[];
ArrayList runSegments = new ArrayList();
// For fast retrieving of the segment containing
// character with known logical index
int logical2segment[];
int segment2visual[]; // Visual order of segments TODO - implement
int visual2segment[];
int logical2visual[];
int visual2logical[];
SegmentsInfo storedSegments;
private boolean haveAllSegments = false;
int segmentsStart, segmentsEnd;
float justification = 1.0f;
public TextRunBreaker(AttributedCharacterIterator aci, FontRenderContext frc) {
this.aci = aci;
this.frc = frc;
segmentsStart = aci.getBeginIndex();
segmentsEnd = aci.getEndIndex();
int len = segmentsEnd - segmentsStart;
text = new char[len];
aci.setIndex(segmentsEnd);
while (len-- != 0) { // Going in backward direction is faster? Simplier checks here?
text[len] = aci.previous();
}
createStyleRuns();
}
/**
* Visual order of text segments may differ from the logical order.
* This method calculates visual position of the segment from its logical position.
* @param segmentNum - logical position of the segment
* @return visual position of the segment
*/
int getVisualFromSegmentOrder(int segmentNum) {
return (segment2visual == null) ? segmentNum : segment2visual[segmentNum];
}
/**
* Visual order of text segments may differ from the logical order.
* This method calculates logical position of the segment from its visual position.
* @param visual - visual position of the segment
* @return logical position of the segment
*/
int getSegmentFromVisualOrder(int visual) {
return (visual2segment == null) ? visual : visual2segment[visual];
}
/**
* Visual order of the characters may differ from the logical order.
* This method calculates visual position of the character from its logical position.
* @param logical - logical position of the character
* @return visual position
*/
int getVisualFromLogical(int logical) {
return (logical2visual == null) ? logical : logical2visual[logical];
}
/**
* Visual order of the characters may differ from the logical order.
* This method calculates logical position of the character from its visual position.
* @param visual - visual position
* @return logical position
*/
int getLogicalFromVisual(int visual) {
return (visual2logical == null) ? visual : visual2logical[visual];
}
/**
* Calculates the end index of the level run, limited by the given text run.
* @param runStart - run start
* @param runEnd - run end
* @return end index of the level run
*/
int getLevelRunLimit(int runStart, int runEnd) {
if (levels == null) {
return runEnd;
}
int endLevelRun = runStart + 1;
byte level = levels[runStart];
while (endLevelRun <= runEnd && levels[endLevelRun] == level) {
endLevelRun++;
}
return endLevelRun;
}
/**
* Adds InputMethodHighlight to the attributes
* @param attrs - text attributes
* @return patched text attributes
*/
Map extends Attribute, ?> unpackAttributes(Map extends Attribute, ?> attrs) {
if (attrs.containsKey(TextAttribute.INPUT_METHOD_HIGHLIGHT)) {
Map styles = null;
Object val = attrs.get(TextAttribute.INPUT_METHOD_HIGHLIGHT);
if (val instanceof Annotation) {
val = ((Annotation) val).getValue();
}
//
// if (val instanceof InputMethodHighlight) {
// InputMethodHighlight ihl = ((InputMethodHighlight) val);
// styles = ihl.getStyle();
//
// if (styles == null) {
// Toolkit tk = Toolkit.getDefaultToolkit();
// styles = tk.mapInputMethodHighlight(ihl);
// }
// }
if (styles != null) {
HashMap newAttrs = new HashMap();
newAttrs.putAll(attrs);
newAttrs.putAll(styles);
return newAttrs;
}
}
return attrs;
}
/**
* Breaks the text into separate style runs.
*/
void createStyleRuns() {
// TODO - implement fast and simple case
fonts = new HashMap();
decorations = new HashMap();
////
ArrayList forcedFontRunStartsList = null;
Map extends Attribute, ?> attributes = null;
// Check justification attribute
Object val = aci.getAttribute(TextAttribute.JUSTIFICATION);
if (val != null) {
justification = ((Float) val).floatValue();
}
for (
int index = segmentsStart, nextRunStart = segmentsStart;
index < segmentsEnd;
index = nextRunStart, aci.setIndex(index)
) {
nextRunStart = aci.getRunLimit();
attributes = unpackAttributes(aci.getAttributes());
TextDecorator.Decoration d = TextDecorator.getDecoration(attributes);
decorations.put(new Integer(index), d);
// Find appropriate font or place GraphicAttribute there
// 1. Try to pick up CHAR_REPLACEMENT (compatibility)
Object value = attributes.get(TextAttribute.CHAR_REPLACEMENT);
if (value == null) {
// 2. Try to Get FONT
value = attributes.get(TextAttribute.FONT);
if (value == null) {
// 3. Try to create font from FAMILY
if (attributes.get(TextAttribute.FAMILY) != null) {
value = Font.getFont(attributes);
}
if (value == null) {
// 4. No attributes found, using default.
if (forcedFontRunStartsList == null) {
forcedFontRunStartsList = new ArrayList();
}
FontFinder.findFonts(
text,
index,
nextRunStart,
forcedFontRunStartsList,
fonts
);
value = fonts.get(new Integer(index));
}
}
}
fonts.put(new Integer(index), value);
}
// We have added some default fonts, so we have some extra runs in text
if (forcedFontRunStartsList != null) {
forcedFontRunStarts = new int[forcedFontRunStartsList.size()];
for (int i=0; i runStart) {
maxPos = Math.min(element, maxPos);
break;
}
}
}
return Math.min(aci.getRunLimit(), maxPos);
}
/**
* Creates segments for the text run with
* constant decoration, font and bidi level
* @param runStart - run start
* @param runEnd - run end
*/
public void createSegments(int runStart, int runEnd) {
int endStyleRun, endLevelRun;
// TODO - update levels
int pos = runStart, levelPos;
aci.setIndex(pos);
final int firstRunStart = aci.getRunStart();
Object tdd = decorations.get(new Integer(firstRunStart));
Object fontOrGAttr = fonts.get(new Integer(firstRunStart));
logical2segment = new int[runEnd - runStart];
do {
endStyleRun = getStyleRunLimit(pos, runEnd);
// runStart can be non-zero, but all arrays will be indexed from 0
int ajustedPos = pos - runStart;
int ajustedEndStyleRun = endStyleRun - runStart;
levelPos = ajustedPos;
do {
endLevelRun = getLevelRunLimit(levelPos, ajustedEndStyleRun);
if (fontOrGAttr instanceof GraphicAttribute) {
runSegments.add(
new TextRunSegmentImpl.TextRunSegmentGraphic(
(GraphicAttribute)fontOrGAttr,
endLevelRun - levelPos,
levelPos + runStart)
);
Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1);
} else {
TextRunSegmentImpl.TextSegmentInfo i =
new TextRunSegmentImpl.TextSegmentInfo(
levels == null ? 0 : levels[ajustedPos],
(Font) fontOrGAttr,
frc,
text,
levelPos + runStart,
endLevelRun + runStart
);
runSegments.add(
new TextRunSegmentImpl.TextRunSegmentCommon(
i,
(TextDecorator.Decoration) tdd
)
);
Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1);
}
levelPos = endLevelRun;
} while (levelPos < ajustedEndStyleRun);
// Prepare next iteration
pos = endStyleRun;
tdd = decorations.get(new Integer(pos));
fontOrGAttr = fonts.get(new Integer(pos));
} while (pos < runEnd);
}
/**
* Checks if text run segments are up to date and creates the new segments if not.
*/
public void createAllSegments() {
if ( !haveAllSegments &&
(logical2segment == null ||
logical2segment.length != segmentsEnd - segmentsStart)
) { // Check if we don't have all segments yet
resetSegments();
createSegments(segmentsStart, segmentsEnd);
}
haveAllSegments = true;
}
/**
* Calculates position where line should be broken without
* taking into account word boundaries.
* @param start - start index
* @param maxAdvance - maximum advance, width of the line
* @return position where to break
*/
public int getLineBreakIndex(int start, float maxAdvance) {
int breakIndex;
TextRunSegment s = null;
for (
int segmentIndex = logical2segment[start];
segmentIndex < runSegments.size();
segmentIndex++
) {
s = runSegments.get(segmentIndex);
breakIndex = s.getCharIndexFromAdvance(maxAdvance, start);
if (breakIndex < s.getEnd()) {
return breakIndex;
}
maxAdvance -= s.getAdvanceDelta(start, s.getEnd());
start = s.getEnd();
}
return s.getEnd();
}
/**
* Inserts character into the managed text.
* @param newParagraph - new character iterator
* @param insertPos - insertion position
*/
public void insertChar(AttributedCharacterIterator newParagraph, int insertPos) {
aci = newParagraph;
char insChar = aci.setIndex(insertPos);
Integer key = new Integer(insertPos);
insertPos -= aci.getBeginIndex();
char newText[] = new char[text.length + 1];
System.arraycopy(text, 0, newText, 0, insertPos);
newText[insertPos] = insChar;
System.arraycopy(text, insertPos, newText, insertPos+1, text.length - insertPos);
text = newText;
if (aci.getRunStart() == key.intValue() && aci.getRunLimit() == key.intValue() + 1) {
createStyleRuns(); // We have to create one new run, could be optimized
} else {
shiftStyleRuns(key, 1);
}
resetSegments();
segmentsEnd++;
}
/**
* Deletes character from the managed text.
* @param newParagraph - new character iterator
* @param deletePos - deletion position
*/
public void deleteChar(AttributedCharacterIterator newParagraph, int deletePos) {
aci = newParagraph;
Integer key = new Integer(deletePos);
deletePos -= aci.getBeginIndex();
char newText[] = new char[text.length - 1];
System.arraycopy(text, 0, newText, 0, deletePos);
System.arraycopy(text, deletePos+1, newText, deletePos, newText.length - deletePos);
text = newText;
if (fonts.get(key) != null) {
fonts.remove(key);
}
shiftStyleRuns(key, -1);
resetSegments();
segmentsEnd--;
}
/**
* Shift all runs after specified position, needed to perfom insertion
* or deletion in the managed text
* @param pos - position where to start
* @param shift - shift, could be negative
*/
private void shiftStyleRuns(Integer pos, final int shift) {
ArrayList keys = new ArrayList();
Integer key, oldkey;
for (Iterator it = fonts.keySet().iterator(); it.hasNext(); ) {
oldkey = it.next();
if (oldkey.intValue() > pos.intValue()) {
keys.add(oldkey);
}
}
for (int i=0; i();
logical2segment = null;
segment2visual = null;
visual2segment = null;
levels = null;
haveAllSegments = false;
}
private class SegmentsInfo {
ArrayList runSegments;
int logical2segment[];
int segment2visual[];
int visual2segment[];
byte levels[];
int segmentsStart;
int segmentsEnd;
}
/**
* Saves the internal state of the class
* @param newSegStart - new start index in the text
* @param newSegEnd - new end index in the text
*/
public void pushSegments(int newSegStart, int newSegEnd) {
storedSegments = new SegmentsInfo();
storedSegments.runSegments = this.runSegments;
storedSegments.logical2segment = this.logical2segment;
storedSegments.segment2visual = this.segment2visual;
storedSegments.visual2segment = this.visual2segment;
storedSegments.levels = this.levels;
storedSegments.segmentsStart = segmentsStart;
storedSegments.segmentsEnd = segmentsEnd;
resetSegments();
segmentsStart = newSegStart;
segmentsEnd = newSegEnd;
}
/**
* Restores the internal state of the class
*/
public void popSegments() {
if (storedSegments == null) {
return;
}
this.runSegments = storedSegments.runSegments;
this.logical2segment = storedSegments.logical2segment;
this.segment2visual = storedSegments.segment2visual;
this.visual2segment = storedSegments.visual2segment;
this.levels = storedSegments.levels;
this.segmentsStart = storedSegments.segmentsStart;
this.segmentsEnd = storedSegments.segmentsEnd;
storedSegments = null;
if (runSegments.size() == 0 && logical2segment == null) {
haveAllSegments = false;
} else {
haveAllSegments = true;
}
}
@Override
public Object clone() {
try {
TextRunBreaker res = (TextRunBreaker) super.clone();
res.storedSegments = null;
ArrayList newSegments = new ArrayList(runSegments.size());
for (int i = 0; i < runSegments.size(); i++) {
TextRunSegment seg = runSegments.get(i);
newSegments.add((TextRunSegment)seg.clone());
}
res.runSegments = newSegments;
return res;
} catch (CloneNotSupportedException e) {
// awt.3E=Clone not supported
throw new UnsupportedOperationException(Messages.getString("awt.3E")); //$NON-NLS-1$
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TextRunBreaker)) {
return false;
}
TextRunBreaker br = (TextRunBreaker) obj;
if (br.getACI().equals(aci) && br.frc.equals(frc)) {
return true;
}
return false;
}
@Override
public int hashCode() {
return HashCode.combine(aci.hashCode(), frc.hashCode());
}
/**
* Renders the managed text
* @param g2d - graphics where to render
* @param xOffset - offset in X direction to the upper left corner
* of the layout from the origin of the graphics
* @param yOffset - offset in Y direction to the upper left corner
* of the layout from the origin of the graphics
*/
public void drawSegments(Graphics2D g2d, float xOffset, float yOffset) {
for (int i=0; i= x) || // We are in the segment
(endOfPrevSeg < x && bounds.getMinX() > x)) { // We are somewhere between the segments
return segment.hitTest(x,y);
}
endOfPrevSeg = bounds.getMaxX();
}
return isLTR() ? TextHitInfo.trailing(text.length) : TextHitInfo.leading(0);
}
public float getJustification() {
return justification;
}
/**
* Calculates position of the last non whitespace character
* in the managed text.
* @return position of the last non whitespace character
*/
public int getLastNonWhitespace() {
int lastNonWhitespace = text.length;
while (lastNonWhitespace >= 0) {
lastNonWhitespace--;
if (!Character.isWhitespace(text[lastNonWhitespace])) {
break;
}
}
return lastNonWhitespace;
}
/**
* Performs justification of the managed text by changing segment positions
* and positions of the glyphs inside of the segments.
* @param gap - amount of space which should be compensated by justification
*/
public void justify(float gap) {
// Ignore trailing logical whitespace
int firstIdx = segmentsStart;
int lastIdx = getLastNonWhitespace() + segmentsStart;
JustificationInfo jInfos[] = new JustificationInfo[5];
float gapLeft = gap;
int highestPriority = -1;
// GlyphJustificationInfo.PRIORITY_KASHIDA is 0
// GlyphJustificationInfo.PRIORITY_NONE is 3
for (int priority = 0; priority <= GlyphJustificationInfo.PRIORITY_NONE + 1; priority++) {
JustificationInfo jInfo = new JustificationInfo();
jInfo.lastIdx = lastIdx;
jInfo.firstIdx = firstIdx;
jInfo.grow = gap > 0;
jInfo.gapToFill = gapLeft;
if (priority <= GlyphJustificationInfo.PRIORITY_NONE) {
jInfo.priority = priority;
} else {
jInfo.priority = highestPriority; // Last pass
}
for (int i = 0; i < runSegments.size(); i++) {
TextRunSegment segment = runSegments.get(i);
if (segment.getStart() <= lastIdx) {
segment.updateJustificationInfo(jInfo);
}
}
if (jInfo.priority == highestPriority) {
jInfo.absorb = true;
jInfo.absorbedWeight = jInfo.weight;
}
if (jInfo.weight != 0) {
if (highestPriority < 0) {
highestPriority = priority;
}
jInfos[priority] = jInfo;
} else {
continue;
}
gapLeft -= jInfo.growLimit;
if (((gapLeft > 0) ^ jInfo.grow) || gapLeft == 0) {
gapLeft = 0;
jInfo.gapPerUnit = jInfo.gapToFill/jInfo.weight;
break;
}
jInfo.useLimits = true;
if (jInfo.absorbedWeight > 0) {
jInfo.absorb = true;
jInfo.absorbedGapPerUnit =
(jInfo.gapToFill-jInfo.growLimit)/jInfo.absorbedWeight;
break;
}
}
float currJustificationOffset = 0;
for (int i = 0; i < runSegments.size(); i++) {
TextRunSegment segment =
runSegments.get(getSegmentFromVisualOrder(i));
segment.x += currJustificationOffset;
currJustificationOffset += segment.doJustification(jInfos);
}
justification = -1; // Make further justification impossible
}
/**
* This class represents the information collected before the actual
* justification is started and needed to perform the justification.
* This information is closely related to the information stored in the
* GlyphJustificationInfo for the text represented by glyph vectors.
*/
class JustificationInfo {
boolean grow;
boolean absorb = false;
boolean useLimits = false;
int priority = 0;
float weight = 0;
float absorbedWeight = 0;
float growLimit = 0;
int lastIdx;
int firstIdx;
float gapToFill;
float gapPerUnit = 0; // Precalculated value, gapToFill / weight
float absorbedGapPerUnit = 0; // Precalculated value, gapToFill / weight
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy