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]
# 1
Could you give an example of the behaviour?I guess the problem lies in rounding errors.regards,Stas
StanislavL at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 2

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;

}

}

}

}

borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 3
btw; using java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON doesn't make a difference...
borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 4

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);

}

}

}

borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 5
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
StanislavL at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 6

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;

}

StanislavL at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 7

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

borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 8
Also, please note that I updated the example, so the last one should be able to run :)
borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 9

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 ...

borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 10

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 ...

borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 11

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

StanislavL at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 12
np, thanks for your input once again Stas :)- borbjo
borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 13

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.

borbjo at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 14
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
StanislavL at 2007-7-7 2:46:44 > top of Java-index,Desktop,Core GUI APIs...
# 15

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 ..

borbjoa at 2007-7-20 0:23:58 > top of Java-index,Desktop,Core GUI APIs...
# 16

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

StanislavLa at 2007-7-20 0:23:58 > top of Java-index,Desktop,Core GUI APIs...
# 17
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.
borbjoa at 2007-7-20 0:23:58 > top of Java-index,Desktop,Core GUI APIs...