modelToView() question
I have a problem with modelToView() not returning the correct location when I'm using scaling.
Is there a way to measure the width of a specific segment in a DefaultStyledDocument (different styles and tab length may occur)? Utilities.getTabbedTextWidth does not seem to return the right results either.
[317 byte] By [
borbjo] at [2007-9-30 21:14:03]

Could you give an example of the behaviour?I guess the problem lies in rounding errors.regards,Stas
Sure .. if you save this file as ScaledTextPaneTest.java and run it you'll see the problem if you insert multiple m's .. the caret is increasing the distance from the last character...
/*
* User: Bj齬n B齬resen <bjorn@xait.no>
* Date: 28.sep.2004
* Time: 10:22:28
*/
import javax.swing.*;
import javax.swing.text.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.PrintStream;
/**
* Test of ScaledTextPane
*/
public class ScaledTextPaneTest extends JFrame implements ActionListener {
JTextPane tp;
ScaledTextPane sp;
public ScaledTextPaneTest() {
super("ScaledTextPane w/hanging indent");
// create document with 'hanging indent style'
DefaultStyledDocument doc = new DefaultStyledDocument();
Style s = doc.addStyle("body", null);
StyleConstants.setFirstLineIndent(s, -50);
StyleConstants.setLeftIndent(s, 50);
StyleConstants.setFontFamily(s, "Dialog");
StyleConstants.setFontSize(s, 12);
doc.setParagraphAttributes(0, 1, s, true);
doc.putProperty("ZOOM_FACTOR", new Double(2));
//tp = new ScaledTextPane();
tp = new JTextPane();
tp.setEditorKit(new FixedStyledEditorKit());
//tp.setDocument(doc);
sp = new ScaledTextPane();
sp.setDocument(doc);
Container c = this.getContentPane();
c.add(sp, BorderLayout.CENTER);
JButton printB = new JButton("Print Views");
printB.addActionListener(this);
printB.setActionCommand("printb");
c.add(printB, BorderLayout.SOUTH);
}
public static void displayViews(JTextComponent comp,
PrintStream out) {
View rootView = comp.getUI().getRootView(comp);
displayView(rootView, 0, comp.getDocument(), out);
}
public static void displayView(View view, int indent,
Document doc,
PrintStream out) {
String name = view.getClass().getName();
for (int i = 0; i < indent; i++) {
out.print("\t");
}
int start = view.getStartOffset();
int end = view.getEndOffset();
out.println(name + "; offsets [" + start + ", " +
end + "]");
int viewCount = view.getViewCount();
if (viewCount == 0) {
int length = Math.min(32, end - start);
try {
String txt = doc.getText(start, length);
for (int i = 0; i < indent + 1; i++) {
out.print("\t");
}
out.println("[" + txt + "]");
} catch (BadLocationException e) {
}
} else {
for (int i = 0; i < viewCount; i++) {
displayView(view.getView(i), indent + 1,
doc, out);
}
}
}
public static void main(String args[]) {
ScaledTextPaneTest test = new ScaledTextPaneTest();
test.setBounds(200, 200, 600, 400);
test.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals("printb")) {
displayViews(tp, System.out);
}
}
public class ScaledTextPane extends JTextPane {
public JComboBox zoomCombo = new JComboBox(new String[]{"50%", "75%",
"100%", "200%",
"250%"});
public ScaledTextPane() {
super();
setOpaque(false);
setEditorKit(new ScaledEditorKit());
zoomCombo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String s = (String) zoomCombo.getSelectedItem();
s = s.substring(0, s.length() - 1);
double scale = new Double(s).doubleValue() / 100;
ScaledTextPane.this.getDocument().putProperty("ZOOM_FACTOR",
new Double(scale));
ScaledTextPane.this.getDocument().putProperty("i18n", Boolean.TRUE);
try {
ScaledTextPane.this.getDocument().insertString(0, "",
null);//refresh
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
zoomCombo.setSelectedItem("250%");
}
public void repaint(int x, int y, int width, int height) {
super.repaint(0, 0, getWidth(), getHeight());
}
}
class ScaledEditorKit extends StyledEditorKit {
public ViewFactory getViewFactory() {
return new StyledViewFactory();
}
class StyledViewFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem);
} else if (kind.equals(AbstractDocument.
ParagraphElementName)) {
return new AdvancedParagraphView(elem);
} else if (kind.equals(AbstractDocument.
SectionElementName)) {
return new ScaledView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.
ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
}
//--
class ScaledView extends BoxView {
int linespacing = 0;
public ScaledView(Element elem, int axis) {
super(elem, axis);
}
public double getZoomFactor() {
Double scale = (Double) getDocument().getProperty("ZOOM_FACTOR");
if (scale != null) {
return scale.doubleValue();
}
return 1;
}
public void paint(Graphics g, Shape allocation) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
//g2d.setRenderingHint(java.awt.RenderingHints.KEY_FRACTIONALMETRICS, java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON);
/**
* Problemet er en kombinasjon av RenderingHint'et KEY_FRACTIONALMETRICS og skalering. Ved 100% og uten KEY_FRACTIONALMETRICS_ON fungerer
* alt kjempeflott.
*/
double zoomFactor = getZoomFactor();
AffineTransform old = g2d.getTransform();
g2d.scale(zoomFactor, zoomFactor);
super.paint(g2d, allocation);
g2d.setTransform(old);
}
protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
super.layoutMajorAxis(targetSpan, axis, offsets, spans);
if (axis == View.Y_AXIS) {
int shift = 0;
for (int i = 0; i < offsets.length; i++) {
offsets[i] = offsets[i] + shift;
shift += linespacing;
}
for (int i = 0; i < spans.length; i++) {
spans[i] = spans[i] + linespacing;
}
}
}
public float getMinimumSpan(int axis) {
float f = super.getMinimumSpan(axis);
if (axis == View.Y_AXIS) {
f += (linespacing * getViewCount());
}
f *= getZoomFactor();
return f;
}
public float getMaximumSpan(int axis) {
float f = super.getMaximumSpan(axis);
f *= getZoomFactor();
return f;
}
public float getPreferredSpan(int axis) {
float f = super.getPreferredSpan(axis);
if (axis == View.Y_AXIS) {
f += (linespacing * getViewCount());
}
f *= getZoomFactor();
return f;
}
protected void layout(int width, int height) {
super.layout(new Double(width / getZoomFactor()).intValue(),
new Double(height *
getZoomFactor()).intValue());
}
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException {
double zoomFactor = getZoomFactor();
Rectangle2D alloc;
alloc = a.getBounds();
/*FontMetrics fm = getFontMetrics(getFont());
System.out.println(getFont());
int tw = Utilities.getTabbedTextWidth(new Segment((getDocument().getText(0, getDocument().getLength())).toCharArray(), 0, getDocument().getLength()),
fm,
0,
null,
0);
tw *= zoomFactor;
alloc.setFrame(tw, alloc.getY(), alloc.getWidth(), alloc.getHeight());
System.out.println("ScaledView.modelToView() - tw="+tw);
*/
Shape s = super.modelToView(pos, alloc, b);
alloc = s.getBounds();
double newx = alloc.getX() * zoomFactor;
double newy = alloc.getY() * zoomFactor;
double newwidth = alloc.getWidth() * zoomFactor;
double newheight = alloc.getHeight() * zoomFactor;
alloc.setRect(newx, newy, newwidth, newheight);
return new Rectangle((int) Math.ceil(alloc.getX()),
(int) Math.ceil(alloc.getY()),
(int) Math.ceil( alloc.getWidth() ),
(int) Math.ceil(alloc.getHeight()));
}
private Rectangle2D getScaledBounds(Shape s) {
Rectangle2D alloc = s.getBounds();
double zoomFactor = getZoomFactor();
double newx = alloc.getX() / zoomFactor;
double newy = alloc.getY() / zoomFactor;
double newwidth = alloc.getWidth() / zoomFactor;
double newheight = alloc.getHeight() / zoomFactor;
alloc.setRect(newx, newy, newwidth, newheight);
return alloc;
}
private Rectangle getScaledBoundsRect(Shape s) {
Rectangle alloc = s.getBounds();
double zoomFactor = getZoomFactor();
alloc.x /= zoomFactor;
alloc.y /= zoomFactor;
alloc.width /= zoomFactor;
alloc.height /= zoomFactor;
return alloc;
}
/**
* We restore the method's parameters as though they were without scaling and invoke the super method of ancestor.
* @param x
* @param y
* @param a
* @param bias
* @return
*/
public int viewToModel(float x, float y, Shape a,
Position.Bias[] bias) {
double zoomFactor = getZoomFactor();
x /= zoomFactor;
y /= zoomFactor;
return super.viewToModel(x, y, getScaledBounds(a), bias);
}
}
/**
* The AdvancedParagraphView class
*/
class AdvancedParagraphView extends ParagraphView {
public AdvancedParagraphView(Element elem) {
super(elem);
strategy = new AdvancedFlowStrategy();
}
protected View createRow() {
Element elem = getElement();
return new AdvancedRow(elem);
}
protected int getSpaceCount(String content) {
int result = 0;
int index = content.indexOf(' ');
while (index >= 0) {
result++;
index = content.indexOf(' ', index + 1);
}
return result;
}
protected int[] getSpaceIndexes(String content, int shift) {
int cnt = getSpaceCount(content);
int[] result = new int[cnt];
int counter = 0;
int index = content.indexOf(' ');
while (index >= 0) {
result[counter] = index + shift;
counter++;
index = content.indexOf(' ', index + 1);
}
return result;
}
class AdvancedFlowStrategy
extends FlowStrategy {
public void layout(FlowView fv) {
super.layout(fv);
AttributeSet attr = fv.getElement().getAttributes();
float lineSpacing = StyleConstants.getLineSpacing(attr);
boolean justifiedAlignment = (StyleConstants.getAlignment(attr) ==
StyleConstants.ALIGN_JUSTIFIED);
if (!(justifiedAlignment || (lineSpacing > 1))) {
return;
}
int cnt = fv.getViewCount();
for (int i = 0; i < cnt - 1; i++) {
AdvancedRow row = (AdvancedRow) fv.getView(i);
if (lineSpacing > 1) {
float height = row.getMinimumSpan(View.Y_AXIS);
float addition = (height * lineSpacing) - height;
if (addition > 0) {
row.setInsets(row.getTopInset(), row.getLeftInset(),
(short) addition, row.getRightInset());
}
}
if (justifiedAlignment) {
restructureRow(row, i);
row.setRowNumber(i + 1);
}
}
}
protected void restructureRow(View row, int rowNum) {
int rowStartOffset = row.getStartOffset();
int rowEndOffset = row.getEndOffset();
String rowContent = "";
try {
rowContent = row.getDocument().getText(rowStartOffset,
rowEndOffset - rowStartOffset);
if (rowNum == 0) {
int index = 0;
while (rowContent.charAt(0) == ' ') {
rowContent = rowContent.substring(1);
if (rowContent.length() == 0)
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
int rowSpaceCount = getSpaceCount(rowContent);
if (rowSpaceCount < 1)
return;
int[] rowSpaceIndexes = getSpaceIndexes(rowContent, row.getStartOffset());
int currentSpaceIndex = 0;
for (int i = 0; i < row.getViewCount(); i++) {
View child = row.getView(i);
if ((child.getStartOffset() < rowSpaceIndexes[currentSpaceIndex]) &&
(child.getEndOffset() > rowSpaceIndexes[currentSpaceIndex])) {
//split view
View first = child.createFragment(child.getStartOffset(),
rowSpaceIndexes[currentSpaceIndex]);
View second = child.createFragment(rowSpaceIndexes[currentSpaceIndex],
child.getEndOffset());
View[] repl = new View[2];
repl[0] = first;
repl[1] = second;
row.replace(i, 1, repl);
currentSpaceIndex++;
if (currentSpaceIndex >= rowSpaceIndexes.length)
break;
}
}
int childCnt = row.getViewCount();
}
}
class AdvancedRow
extends BoxView {
private int rowNumber = 0;
AdvancedRow(Element elem) {
super(elem, View.X_AXIS);
}
protected void loadChildren(ViewFactory f) {
}
public AttributeSet getAttributes() {
View p = getParent();
return (p != null) ? p.getAttributes() : null;
}
public float getAlignment(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
int justification = StyleConstants.getAlignment(attr);
switch (justification) {
case StyleConstants.ALIGN_LEFT:
case StyleConstants.ALIGN_JUSTIFIED:
return 0;
case StyleConstants.ALIGN_RIGHT:
return 1;
case StyleConstants.ALIGN_CENTER:
return 0.5f;
}
}
return super.getAlignment(axis);
}
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
/*FontMetrics fm = getFontMetrics(getFont());
int tw = Utilities.getTabbedTextWidth(new Segment((getDocument().getText(0, getDocument().getLength())).toCharArray(), 0, getDocument().getLength()),
fm,
0,
null,
0);
*/
Rectangle r = a.getBounds();
View v = getViewAtPosition(pos, r);
if ((v != null) && (!v.getElement().isLeaf())) {
return super.modelToView(pos, a, b);// Don't adjust the height if the view represents a branch.
}
r = a.getBounds();
int height = r.height;
int y = r.y;
Shape loc = super.modelToView(pos, a, b); // get bounds from super
r = loc.getBounds();
r.height = height;// adjust height & y that was returned by super, to the incomming height/y. E.g. don't change Y/height.
r.y = y;
return r;
}
public int getStartOffset() {
int offs = Integer.MAX_VALUE;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
offs = Math.min(offs, v.getStartOffset());
}
return offs;
}
public int getEndOffset() {
int offs = 0;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
offs = Math.max(offs, v.getEndOffset());
}
return offs;
}
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
int[] spans) {
baselineLayout(targetSpan, axis, offsets, spans);
}
protected SizeRequirements calculateMinorAxisRequirements(int axis,
SizeRequirements r) {
return baselineRequirements(axis, r);
}
/**
* Fetches the child view index representing the given position in the model.
* This is implemented to fetch the view in the case where there is a child view for each child element.
*
* @param pos
* @return
*/
protected int getViewIndexAtPosition(int pos) {
// This is expensive, but are views are not necessarily layed
// out in model order.
if (pos < getStartOffset() || pos >= getEndOffset())
return -1;
for (int counter = getViewCount() - 1; counter >= 0; counter--) {
View v = getView(counter);
if (pos >= v.getStartOffset() &&
pos < v.getEndOffset()) {
return counter;
}
}
return -1;
}
public short getTopInset() {
return super.getTopInset();
}
public short getLeftInset() {
return super.getLeftInset();
}
public short getRightInset() {
return super.getRightInset();
}
public void setInsets(short topInset, short leftInset, short bottomInset,
short rightInset) {
super.setInsets(topInset, leftInset, bottomInset, rightInset);
}
protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
int[] spans) {
super.layoutMajorAxis(targetSpan, axis, offsets, spans);
AttributeSet attr = getAttributes();
if ((StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) &&
(axis != View.X_AXIS)) {
return;
}
int cnt = offsets.length;
int span = 0;
for (int i = 0; i < cnt; i++) {
span += spans[i];
}
if (getRowNumber() == 0)
return;
int startOffset = getStartOffset();
int len = getEndOffset() - startOffset;
String context = "";
try {
context = getElement().getDocument().getText(startOffset, len);
} catch (Exception e) {
e.printStackTrace();
}
int spaceCount = getSpaceCount(context) - 1;
int pixelsToAdd = targetSpan - span;
if (this.getRowNumber() == 1) {
int firstLineIndent = (int) StyleConstants.getFirstLineIndent(getAttributes());
pixelsToAdd -= firstLineIndent;
}
int[] spaces = getSpaces(pixelsToAdd, spaceCount);
int j = 0;
int shift = 0;
for (int i = 1; i < cnt; i++) {
LabelView v = (LabelView) getView(i);
offsets[i] += shift;
if ((isContainSpace(v)) && (i != cnt - 1)) {
offsets[i] += spaces[j];
spans[i - 1] += spaces[j];
shift += spaces[j];
j++;
}
}
}
protected int[] getSpaces(int space, int cnt) {
int[] result = new int[cnt];
if (cnt == 0)
return result;
int base = space / cnt;
int rst = space % cnt;
for (int i = 0; i < cnt; i++) {
result[i] = base;
if (rst > 0) {
result[i]++;
rst--;
}
}
return result;
}
public float getMinimumSpan(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
if (StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) {
return super.getMinimumSpan(axis);
} else {
return this.getParent().getMinimumSpan(axis);
}
} else {
return super.getMinimumSpan(axis);
}
}
public float getMaximumSpan(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
if (StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) {
return super.getMaximumSpan(axis);
} else {
return this.getParent().getMaximumSpan(axis);
}
} else {
return super.getMaximumSpan(axis);
}
}
public float getPreferredSpan(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
if (StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) {
return super.getPreferredSpan(axis);
} else {
return this.getParent().getPreferredSpan(axis);
}
} else {
return super.getPreferredSpan(axis);
}
}
public void setRowNumber(int value) {
rowNumber = value;
}
public int getRowNumber() {
return rowNumber;
}
}
public int getFlowSpan(int index) {
int span = super.getFlowSpan(index);
if (index == 0) {
int firstLineIdent = (int) StyleConstants.getFirstLineIndent(this.
getAttributes());
span -= firstLineIdent;
}
return span;
}
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
int[] spans) {
super.layoutMinorAxis(targetSpan, axis, offsets, spans);
int firstLineIdent = (int) StyleConstants.getFirstLineIndent(this.
getAttributes());
offsets[0] += firstLineIdent;
}
protected boolean isContainSpace(View v) {
int startOffset = v.getStartOffset();
int len = v.getEndOffset() - startOffset;
try {
String text = v.getDocument().getText(startOffset, len);
if (text.indexOf(' ') >= 0)
return true;
else
return false;
} catch (Exception ex) {
return false;
}
}
}
}
btw; using java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON doesn't make a difference...
ups, I forgot to put the source for the View and ViewFactory in the same file .. here's the complete code:
import javax.swing.*;
import javax.swing.text.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.PrintStream;
/**
* Test of ScaledTextPane
*/
public class ScaledTextPaneTest extends JFrame implements ActionListener {
JTextPane tp;
ScaledTextPane sp;
public ScaledTextPaneTest() {
super("ScaledTextPane w/hanging indent");
// create document with 'hanging indent style'
DefaultStyledDocument doc = new DefaultStyledDocument();
Style s = doc.addStyle("body", null);
StyleConstants.setFirstLineIndent(s, -50);
StyleConstants.setLeftIndent(s, 50);
StyleConstants.setFontFamily(s, "Dialog");
StyleConstants.setFontSize(s, 12);
doc.setParagraphAttributes(0, 1, s, true);
doc.putProperty("ZOOM_FACTOR", new Double(2));
//tp = new ScaledTextPane();
tp = new JTextPane();
tp.setEditorKit(new FixedStyledEditorKit());
//tp.setDocument(doc);
sp = new ScaledTextPane();
sp.setDocument(doc);
Container c = this.getContentPane();
c.add(sp, BorderLayout.CENTER);
JButton printB = new JButton("Print Views");
printB.addActionListener(this);
printB.setActionCommand("printb");
c.add(printB, BorderLayout.SOUTH);
}
public static void displayViews(JTextComponent comp,
PrintStream out) {
View rootView = comp.getUI().getRootView(comp);
displayView(rootView, 0, comp.getDocument(), out);
}
public static void displayView(View view, int indent,
Document doc,
PrintStream out) {
String name = view.getClass().getName();
for (int i = 0; i < indent; i++) {
out.print("\t");
}
int start = view.getStartOffset();
int end = view.getEndOffset();
out.println(name + "; offsets [" + start + ", " +
end + "]");
int viewCount = view.getViewCount();
if (viewCount == 0) {
int length = Math.min(32, end - start);
try {
String txt = doc.getText(start, length);
for (int i = 0; i < indent + 1; i++) {
out.print("\t");
}
out.println("[" + txt + "]");
} catch (BadLocationException e) {
}
} else {
for (int i = 0; i < viewCount; i++) {
displayView(view.getView(i), indent + 1,
doc, out);
}
}
}
public static void main(String args[]) {
ScaledTextPaneTest test = new ScaledTextPaneTest();
test.setBounds(200, 200, 600, 400);
test.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals("printb")) {
displayViews(tp, System.out);
}
}
public class ScaledTextPane extends JTextPane {
public JComboBox zoomCombo = new JComboBox(new String[]{"50%", "75%",
"100%", "200%",
"250%"});
public ScaledTextPane() {
super();
setOpaque(false);
setEditorKit(new ScaledEditorKit());
zoomCombo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String s = (String) zoomCombo.getSelectedItem();
s = s.substring(0, s.length() - 1);
double scale = new Double(s).doubleValue() / 100;
ScaledTextPane.this.getDocument().putProperty("ZOOM_FACTOR",
new Double(scale));
ScaledTextPane.this.getDocument().putProperty("i18n", Boolean.TRUE);
try {
ScaledTextPane.this.getDocument().insertString(0, "",
null);//refresh
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
zoomCombo.setSelectedItem("250%");
}
public void repaint(int x, int y, int width, int height) {
super.repaint(0, 0, getWidth(), getHeight());
}
}
class ScaledEditorKit extends StyledEditorKit {
public ViewFactory getViewFactory() {
return new StyledViewFactory();
}
class StyledViewFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem);
} else if (kind.equals(AbstractDocument.
ParagraphElementName)) {
return new AdvancedParagraphView(elem);
} else if (kind.equals(AbstractDocument.
SectionElementName)) {
return new ScaledView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.
ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
}
//--
class ScaledView extends BoxView {
int linespacing = 0;
public ScaledView(Element elem, int axis) {
super(elem, axis);
}
public double getZoomFactor() {
Double scale = (Double) getDocument().getProperty("ZOOM_FACTOR");
if (scale != null) {
return scale.doubleValue();
}
return 1;
}
public void paint(Graphics g, Shape allocation) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(java.awt.RenderingHints.KEY_FRACTIONALMETRICS, java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON);
/**
* Problemet er en kombinasjon av RenderingHint'et KEY_FRACTIONALMETRICS og skalering. Ved 100% og uten KEY_FRACTIONALMETRICS_ON fungerer
* alt kjempeflott.
*/
double zoomFactor = getZoomFactor();
AffineTransform old = g2d.getTransform();
g2d.scale(zoomFactor, zoomFactor);
super.paint(g2d, allocation);
g2d.setTransform(old);
}
protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
super.layoutMajorAxis(targetSpan, axis, offsets, spans);
if (axis == View.Y_AXIS) {
int shift = 0;
for (int i = 0; i < offsets.length; i++) {
offsets[i] = offsets[i] + shift;
shift += linespacing;
}
for (int i = 0; i < spans.length; i++) {
spans[i] = spans[i] + linespacing;
}
}
}
public float getMinimumSpan(int axis) {
float f = super.getMinimumSpan(axis);
if (axis == View.Y_AXIS) {
f += (linespacing * getViewCount());
}
f *= getZoomFactor();
return f;
}
public float getMaximumSpan(int axis) {
float f = super.getMaximumSpan(axis);
f *= getZoomFactor();
return f;
}
public float getPreferredSpan(int axis) {
float f = super.getPreferredSpan(axis);
if (axis == View.Y_AXIS) {
f += (linespacing * getViewCount());
}
f *= getZoomFactor();
return f;
}
protected void layout(int width, int height) {
super.layout(new Double(width / getZoomFactor()).intValue(),
new Double(height *
getZoomFactor()).intValue());
}
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException {
double zoomFactor = getZoomFactor();
Rectangle2D alloc;
alloc = a.getBounds();
/*FontMetrics fm = getFontMetrics(getFont());
System.out.println(getFont());
int tw = Utilities.getTabbedTextWidth(new Segment((getDocument().getText(0, getDocument().getLength())).toCharArray(), 0, getDocument().getLength()),
fm,
0,
null,
0);
tw *= zoomFactor;
alloc.setFrame(tw, alloc.getY(), alloc.getWidth(), alloc.getHeight());
System.out.println("ScaledView.modelToView() - tw="+tw);
*/
Shape s = super.modelToView(pos, alloc, b);
alloc = s.getBounds();
double newx = alloc.getX() * zoomFactor;
double newy = alloc.getY() * zoomFactor;
double newwidth = alloc.getWidth() * zoomFactor;
double newheight = alloc.getHeight() * zoomFactor;
alloc.setRect(newx, newy, newwidth, newheight);
return new Rectangle((int) Math.ceil(alloc.getX()),
(int) Math.ceil(alloc.getY()),
(int) Math.ceil( alloc.getWidth() ),
(int) Math.ceil(alloc.getHeight()));
}
private Rectangle2D getScaledBounds(Shape s) {
Rectangle2D alloc = s.getBounds();
double zoomFactor = getZoomFactor();
double newx = alloc.getX() / zoomFactor;
double newy = alloc.getY() / zoomFactor;
double newwidth = alloc.getWidth() / zoomFactor;
double newheight = alloc.getHeight() / zoomFactor;
alloc.setRect(newx, newy, newwidth, newheight);
return alloc;
}
private Rectangle getScaledBoundsRect(Shape s) {
Rectangle alloc = s.getBounds();
double zoomFactor = getZoomFactor();
alloc.x /= zoomFactor;
alloc.y /= zoomFactor;
alloc.width /= zoomFactor;
alloc.height /= zoomFactor;
return alloc;
}
/**
* We restore the method's parameters as though they were without scaling and invoke the super method of ancestor.
* @param x
* @param y
* @param a
* @param bias
* @return
*/
public int viewToModel(float x, float y, Shape a,
Position.Bias[] bias) {
double zoomFactor = getZoomFactor();
x /= zoomFactor;
y /= zoomFactor;
return super.viewToModel(x, y, getScaledBounds(a), bias);
}
}
/**
* The AdvancedParagraphView class
*/
class AdvancedParagraphView extends ParagraphView {
public AdvancedParagraphView(Element elem) {
super(elem);
strategy = new AdvancedFlowStrategy();
}
protected View createRow() {
Element elem = getElement();
return new AdvancedRow(elem);
}
protected int getSpaceCount(String content) {
int result = 0;
int index = content.indexOf(' ');
while (index >= 0) {
result++;
index = content.indexOf(' ', index + 1);
}
return result;
}
protected int[] getSpaceIndexes(String content, int shift) {
int cnt = getSpaceCount(content);
int[] result = new int[cnt];
int counter = 0;
int index = content.indexOf(' ');
while (index >= 0) {
result[counter] = index + shift;
counter++;
index = content.indexOf(' ', index + 1);
}
return result;
}
class AdvancedFlowStrategy
extends FlowStrategy {
public void layout(FlowView fv) {
super.layout(fv);
AttributeSet attr = fv.getElement().getAttributes();
float lineSpacing = StyleConstants.getLineSpacing(attr);
boolean justifiedAlignment = (StyleConstants.getAlignment(attr) ==
StyleConstants.ALIGN_JUSTIFIED);
if (!(justifiedAlignment || (lineSpacing > 1))) {
return;
}
int cnt = fv.getViewCount();
for (int i = 0; i < cnt - 1; i++) {
AdvancedRow row = (AdvancedRow) fv.getView(i);
if (lineSpacing > 1) {
float height = row.getMinimumSpan(View.Y_AXIS);
float addition = (height * lineSpacing) - height;
if (addition > 0) {
row.setInsets(row.getTopInset(), row.getLeftInset(),
(short) addition, row.getRightInset());
}
}
if (justifiedAlignment) {
restructureRow(row, i);
row.setRowNumber(i + 1);
}
}
}
protected void restructureRow(View row, int rowNum) {
int rowStartOffset = row.getStartOffset();
int rowEndOffset = row.getEndOffset();
String rowContent = "";
try {
rowContent = row.getDocument().getText(rowStartOffset,
rowEndOffset - rowStartOffset);
if (rowNum == 0) {
int index = 0;
while (rowContent.charAt(0) == ' ') {
rowContent = rowContent.substring(1);
if (rowContent.length() == 0)
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
int rowSpaceCount = getSpaceCount(rowContent);
if (rowSpaceCount < 1)
return;
int[] rowSpaceIndexes = getSpaceIndexes(rowContent, row.getStartOffset());
int currentSpaceIndex = 0;
for (int i = 0; i < row.getViewCount(); i++) {
View child = row.getView(i);
if ((child.getStartOffset() < rowSpaceIndexes[currentSpaceIndex]) &&
(child.getEndOffset() > rowSpaceIndexes[currentSpaceIndex])) {
//split view
View first = child.createFragment(child.getStartOffset(),
rowSpaceIndexes[currentSpaceIndex]);
View second = child.createFragment(rowSpaceIndexes[currentSpaceIndex],
child.getEndOffset());
View[] repl = new View[2];
repl[0] = first;
repl[1] = second;
row.replace(i, 1, repl);
currentSpaceIndex++;
if (currentSpaceIndex >= rowSpaceIndexes.length)
break;
}
}
int childCnt = row.getViewCount();
}
}
class AdvancedRow
extends BoxView {
private int rowNumber = 0;
AdvancedRow(Element elem) {
super(elem, View.X_AXIS);
}
protected void loadChildren(ViewFactory f) {
}
public AttributeSet getAttributes() {
View p = getParent();
return (p != null) ? p.getAttributes() : null;
}
public float getAlignment(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
int justification = StyleConstants.getAlignment(attr);
switch (justification) {
case StyleConstants.ALIGN_LEFT:
case StyleConstants.ALIGN_JUSTIFIED:
return 0;
case StyleConstants.ALIGN_RIGHT:
return 1;
case StyleConstants.ALIGN_CENTER:
return 0.5f;
}
}
return super.getAlignment(axis);
}
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
/*FontMetrics fm = getFontMetrics(getFont());
int tw = Utilities.getTabbedTextWidth(new Segment((getDocument().getText(0, getDocument().getLength())).toCharArray(), 0, getDocument().getLength()),
fm,
0,
null,
0);
*/
Rectangle r = a.getBounds();
View v = getViewAtPosition(pos, r);
if ((v != null) && (!v.getElement().isLeaf())) {
return super.modelToView(pos, a, b);// Don't adjust the height if the view represents a branch.
}
r = a.getBounds();
int height = r.height;
int y = r.y;
Shape loc = super.modelToView(pos, a, b); // get bounds from super
r = loc.getBounds();
r.height = height;// adjust height & y that was returned by super, to the incomming height/y. E.g. don't change Y/height.
r.y = y;
return r;
}
public int getStartOffset() {
int offs = Integer.MAX_VALUE;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
offs = Math.min(offs, v.getStartOffset());
}
return offs;
}
public int getEndOffset() {
int offs = 0;
int n = getViewCount();
for (int i = 0; i < n; i++) {
View v = getView(i);
offs = Math.max(offs, v.getEndOffset());
}
return offs;
}
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
int[] spans) {
baselineLayout(targetSpan, axis, offsets, spans);
}
protected SizeRequirements calculateMinorAxisRequirements(int axis,
SizeRequirements r) {
return baselineRequirements(axis, r);
}
/**
* Fetches the child view index representing the given position in the model.
* This is implemented to fetch the view in the case where there is a child view for each child element.
*
* @param pos
* @return
*/
protected int getViewIndexAtPosition(int pos) {
// This is expensive, but are views are not necessarily layed
// out in model order.
if (pos < getStartOffset() || pos >= getEndOffset())
return -1;
for (int counter = getViewCount() - 1; counter >= 0; counter--) {
View v = getView(counter);
if (pos >= v.getStartOffset() &&
pos < v.getEndOffset()) {
return counter;
}
}
return -1;
}
public short getTopInset() {
return super.getTopInset();
}
public short getLeftInset() {
return super.getLeftInset();
}
public short getRightInset() {
return super.getRightInset();
}
public void setInsets(short topInset, short leftInset, short bottomInset,
short rightInset) {
super.setInsets(topInset, leftInset, bottomInset, rightInset);
}
protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
int[] spans) {
super.layoutMajorAxis(targetSpan, axis, offsets, spans);
AttributeSet attr = getAttributes();
if ((StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) &&
(axis != View.X_AXIS)) {
return;
}
int cnt = offsets.length;
int span = 0;
for (int i = 0; i < cnt; i++) {
span += spans[i];
}
if (getRowNumber() == 0)
return;
int startOffset = getStartOffset();
int len = getEndOffset() - startOffset;
String context = "";
try {
context = getElement().getDocument().getText(startOffset, len);
} catch (Exception e) {
e.printStackTrace();
}
int spaceCount = getSpaceCount(context) - 1;
int pixelsToAdd = targetSpan - span;
if (this.getRowNumber() == 1) {
int firstLineIndent = (int) StyleConstants.getFirstLineIndent(getAttributes());
pixelsToAdd -= firstLineIndent;
}
int[] spaces = getSpaces(pixelsToAdd, spaceCount);
int j = 0;
int shift = 0;
for (int i = 1; i < cnt; i++) {
LabelView v = (LabelView) getView(i);
offsets[i] += shift;
if ((isContainSpace(v)) && (i != cnt - 1)) {
offsets[i] += spaces[j];
spans[i - 1] += spaces[j];
shift += spaces[j];
j++;
}
}
}
protected int[] getSpaces(int space, int cnt) {
int[] result = new int[cnt];
if (cnt == 0)
return result;
int base = space / cnt;
int rst = space % cnt;
for (int i = 0; i < cnt; i++) {
result[i] = base;
if (rst > 0) {
result[i]++;
rst--;
}
}
return result;
}
public float getMinimumSpan(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
if (StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) {
return super.getMinimumSpan(axis);
} else {
return this.getParent().getMinimumSpan(axis);
}
} else {
return super.getMinimumSpan(axis);
}
}
public float getMaximumSpan(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
if (StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) {
return super.getMaximumSpan(axis);
} else {
return this.getParent().getMaximumSpan(axis);
}
} else {
return super.getMaximumSpan(axis);
}
}
public float getPreferredSpan(int axis) {
if (axis == View.X_AXIS) {
AttributeSet attr = getAttributes();
if (StyleConstants.getAlignment(attr) != StyleConstants.ALIGN_JUSTIFIED) {
return super.getPreferredSpan(axis);
} else {
return this.getParent().getPreferredSpan(axis);
}
} else {
return super.getPreferredSpan(axis);
}
}
public void setRowNumber(int value) {
rowNumber = value;
}
public int getRowNumber() {
return rowNumber;
}
}
public int getFlowSpan(int index) {
int span = super.getFlowSpan(index);
if (index == 0) {
int firstLineIdent = (int) StyleConstants.getFirstLineIndent(this.
getAttributes());
span -= firstLineIdent;
}
return span;
}
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
int[] spans) {
super.layoutMinorAxis(targetSpan, axis, offsets, spans);
int firstLineIdent = (int) StyleConstants.getFirstLineIndent(this.
getAttributes());
offsets[0] += firstLineIdent;
}
protected boolean isContainSpace(View v) {
int startOffset = v.getStartOffset();
int len = v.getEndOffset() - startOffset;
try {
String text = v.getDocument().getText(startOffset, len);
if (text.indexOf(' ') >= 0)
return true;
else
return false;
} catch (Exception ex) {
return false;
}
}
}
class FixedStyledEditorKit extends StyledEditorKit {
public ViewFactory getViewFactory() {
return new FixedStyleViewFactory();
}
}
class FixedStyleViewFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem);
}
else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
//return new OldAdvancedParagraphView(elem);
}
else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
}
else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
}
else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
}
I checked this.The reason is definitely rounding error. Why it happens I can't figure out now.In the example there are some troubles with assigning missing kit class. regards,Stas
I think ptoblem is something here. You use getBounds() which returns int based rectangle i.e. "rounding"
regards,
Stas
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
/*FontMetrics fm = getFontMetrics(getFont());
int tw = Utilities.getTabbedTextWidth(new Segment((getDocument().getText(0, getDocument().getLength())).toCharArray(), 0, getDocument().getLength()),
fm,
0,
null,
0);
*/
Rectangle r = a.getBounds();
View v = getViewAtPosition(pos, r);
if ((v != null) && (!v.getElement().isLeaf())) {
return super.modelToView(pos, a, b);// Don't adjust the height if the view represents a branch.
}
r = a.getBounds();
int height = r.height;
int y = r.y;
Shape loc = super.modelToView(pos, a, b); // get bounds from super
r = loc.getBounds();
r.height = height;// adjust height & y that was returned by super, to the incomming height/y. E.g. don't change Y/height.
r.y = y;
return r;
}
hmm, the Shape object that is sent to and returned from modelToView is a Rectangle .. so using a.getBounds2D() will just give me a Rectangle, e.g.
java.awt.Rectangle[x=3,y=3,width=22,height=16]
.. so, no more precise than that.
If this was the problem then using Rectangle2D should fix it I would think.
Best regards,
borbjo
Also, please note that I updated the example, so the last one should be able to run :)
I am able to reproduce the problem in your ScaledTextPane class from www.developer.com/java/other/article.php/3315511 Stas, by removing the line:
scaledTextPane.getDocument().putProperty("i18n", Boolean.TRUE);
.. which makes sense since GlyphPainter will cause rounding errors.
What's strange is that I do have this line in my implementation to enforce GlyphPainter 2...
The reason I'm not using your implementation is that it will have problems with the document you see I use in the first example (hanging indent) - as the start of the line won't be shown ...
Ok, I'm able to fix the problem with your ScaledTextPane class using this ParagraphView:
class MyParagraphView extends ParagraphView {
protected int tabBase;
protected Rectangle tempRect = new Rectangle();
public MyParagraphView(Element el) {
super(el);
}
public void paint(Graphics g, Shape a) {
Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
tabBase = alloc.x + getLeftInset();
int n = getViewCount();
int x = alloc.x + getLeftInset();
int y = alloc.y + getTopInset();
Rectangle clip = g.getClipBounds();
for (int i = 0; i < n; i++) {
tempRect.x = x + getOffset(X_AXIS, i);
tempRect.y = y + getOffset(Y_AXIS, i);
tempRect.width = getSpan(X_AXIS, i);
tempRect.height = getSpan(Y_AXIS, i);
System.out.println("tempRect="+tempRect);
//if (tempRect.intersects(clip)) {
paintChild(g, tempRect, i);
//}
}
}
}
As you see I've commented out the if(tempRect.interesects(clip)) check and paint the child anyway. This is because tempRect.width has a negative value from getSpan(). I'm not sure why this happens, but it seems that spans[childIndex] contain negative values.
Please let me know if you have any input on this ...
Definitely. GlyphPainter2 returns really float values thus we avoid rounding values.
As for negative rect I think it's because this code.
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
int[] spans) {
super.layoutMinorAxis(targetSpan, axis, offsets, spans);
int firstLineIdent = (int) StyleConstants.getFirstLineIndent(this.
getAttributes());
offsets[0] += firstLineIdent;
}
when firstLineIndent is negative offset[0] could be negative.
Then when visible rectangle is calculated the temp rect gets wrong value.
Sorry for delaying with answer.... A lot of work to do.
regards,
Stas
np, thanks for your input once again Stas :)- borbjo
Stas, did you know that the i18n property in the document disables antialiasing? This must be a bug?
I use
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(java.awt.RenderingHints.KEY_FRACTIONALMETRICS, java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON);
but when I add the property i18n (TRUE) to my document antialiasing is disabled. If I remove the property, antialiasing is back on.
As far as I know (I wrote some code with that the i18n property doesn't switch off antialiasing. Where you set the hints?Try to set them in the paint() method of LabelView/ParagraphView.regards,Stas
I've tried setting them all over the place .. both the LabelView, ParagraphView and ScaledView ..
As I said, as soon as I remove the i18n property antialiasing works ...
I'll experiment a little bit more with it and see if I can reproduce it in a simple JTextPane. If I can, I think I'll submit a bug-report ..
I found what is the reason of such behaviour.
Open javax.swing.text.TextLayoutStrategy class
line 289
// As a practical matter, this FRC will almost always
// be the right one.
AffineTransform xf
= GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration()
.getDefaultTransform();
frc = new FontRenderContext(xf, false, false);
FontRenderContext is created with two parameters set to false. There are isAntialiased and isFractionaletrics.
regards,
Stas
Yes, that's the same conclusion that they have in the bug report: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6193606However, they also say that "Swing doesn't yet support anti-aliased text"? .. If that's the case I'll stay away from it until it's 100% supported.