/**
* @author Stanislav Lapitsky
* @version 1.0
*/
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
public class Hyphenation extends StyledEditorKit {
HyphenationViewFactory factory = new HyphenationViewFactory();
public static final char[] BREAK_CHARS = {' ', '\n', '\t'};
public static FontMetrics fm = null;
public static boolean isBreakChar(char ch) {
for (int i = 0; i < BREAK_CHARS.length; i++) {
if (BREAK_CHARS[i] == ch) {
return true;
}
}
return false;
}
/**
* Collateral method to create application GUI
* @return JFrame
*/
public JFrame init() {
JFrame frame = new JFrame("Hyphenation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JEditorPane editor = new JEditorPane();
editor.setEditorKit(this);
JScrollPane scroll = new JScrollPane(editor);
frame.getContentPane().add(scroll);
frame.setSize(200, 100);
frame.setLocationRelativeTo(null);
return frame;
}
public static int[] getHyphens(String word) {
int[] res = new int[word.length() / 3];
for (int i = 0; i < res.length; i++) {
res[i] = i * 3;
}
return res;
}
public static void main(String[] args) throws Exception {
new Hyphenation().init().setVisible(true);
}
/**
* gets kit view factory.
* @return ViewFactory
*/
public ViewFactory getViewFactory() {
return factory;
}
/**
* The view factory class creates custom views for pagination
* root view (SectionView class) and paragraph (PageableParagraphView class)
*
* @author Stanislav Lapitsky
* @version 1.0
*/
class HyphenationViewFactory implements ViewFactory {
/**
* Creates view for specified element.
* @param elem Element parent element
* @return View created view instance.
*/
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new HyphenatedLabelView(elem);
}
else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new HyphenatedParagraphView(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);
}
}
class HyphenatedParagraphView extends ParagraphView {
protected int[] breakOffsets = new int[0];
protected int size = 0;
public HyphenatedParagraphView(Element elem) {
super(elem);
}
public void preferenceChanged(View child, boolean width, boolean height) {
super.preferenceChanged(child, width, height);
calculateBreakOffsets();
}
protected void calculateBreakOffsets() {
int start = getStartOffset();
int end = getEndOffset();
int len = end - start;
size = 0;
breakOffsets = new int[len / 5];
try {
String text = getDocument().getText(start, end - start);
int index = text.indexOf(' ');
int lastIndex = 0;
while (index > -1) {
String word = text.substring(lastIndex, index);
addHyphenOffsets(word, lastIndex);
addBreakOffset(index + 1, true);
lastIndex = index + 1;
index = text.indexOf(' ', lastIndex);
}
if (lastIndex >= 0) {
String word = text.substring(lastIndex);
addHyphenOffsets(word, lastIndex);
}
}
catch (BadLocationException ex) {
//do nothing
}
}
protected void addHyphenOffsets(String word, int startOffset) {
if (word.length() == 0) {
return;
}
int len = word.length();
StringBuffer subWord = new StringBuffer();
int start = 0;
while (isBreakChar(word.charAt(start))) {
start++;
if (start >= len) {
return;
}
}
char c;
int startWord = start;
for (int j = start; j < len; j++) {
c = word.charAt(j);
if (isBreakChar(word.charAt(j))) {
int offs[] = getHyphens(subWord.toString());
int cnt = offs.length;
for (int i = 0; i < cnt; i++) {
addBreakOffset(startOffset + startWord + offs[i], false);
}
subWord.delete(0, subWord.length());
startWord = j + 1;
}
else {
subWord.append(c);
}
}
if (subWord.length() != 0) {
int offs[] = getHyphens(subWord.toString());
int cnt = offs.length;
for (int i = 0; i < cnt; i++) {
addBreakOffset(startOffset + startWord + offs[i], false);
}
}
}
private void addBreakOffset(int offs, boolean isSpace) {
if (breakOffsets.length == size) {
int[] tmpOffset = breakOffsets;
breakOffsets = new int[Math.max(1, size * 2)];
System.arraycopy(tmpOffset, 0, breakOffsets, 0, tmpOffset.length);
}
breakOffsets[size] = offs;
size++;
}
}
class HyphenatedLabelView extends LabelView {
public HyphenatedLabelView(Element elem) {
super(elem);
}
protected int getBreakSpot(int p0, int p1) {
HyphenatedParagraphView pView = null;
if (getParent() != null && getParent().getParent() != null
&& getParent().getParent() instanceof HyphenatedParagraphView) {
pView = (HyphenatedParagraphView) getParent().getParent();
}
if (pView != null) {
int[] breakOffsets = pView.breakOffsets;
int size = pView.size;
int start = pView.getStartOffset();
for (int i = size - 1; i >= 0; i--) {
if (start + breakOffsets[i] <= p1 && start + breakOffsets[i] > p0) {
return start + breakOffsets[i];
}
}
}
return -1;
}
public int getBreakWeight(int axis, float pos, float len) {
int start = getStartOffset();
int length = getEndOffset() - start;
length = getGlyphPainter().getBoundedPosition(this, start, pos, len) - start;
if (getBreakSpot(start, start + length) != -1) {
return ExcellentBreakWeight;
}
int res = super.getBreakWeight(axis, pos, Math.max(1.0F, len - getHyphenWidth()));
if (res == BadBreakWeight) {
res = GoodBreakWeight;
}
return res;
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
float l = len - getHyphenWidth();
if (l < 0) {
l = 0.0F;
}
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, l);
int breakSpot = getBreakSpot(p0, p1);
if (breakSpot != -1) {
p1 = breakSpot;
}
// else, no break in the region, return a fragment of the
// bounded region.
if (p0 == getStartOffset() && p1 == getEndOffset()) {
return this;
}
return createFragment(p0, p1);
}
return this;
}
public float getHyphenWidth() {
if (fm == null) {
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
}
return fm.stringWidth("-");
}
protected boolean isShowHyphen() {
int end = getEndOffset();
try {
if (getBreakSpot(end - 1, end) == end && !" ".equals(getDocument().getText(end - 1, 1))) {
return true;
}
}
catch (BadLocationException ex) {
}
return false;
}
public float getPreferredSpan(int axis) {
float span = super.getPreferredSpan(axis);
if (axis == View.X_AXIS && isShowHyphen()) {
span += getHyphenWidth();
}
return span;
}
public void paint(Graphics g, Shape a) {
super.paint(g, a);
if (isShowHyphen()) {
Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
int last = (int) (getPreferredSpan(View.X_AXIS) - getHyphenWidth());
Rectangle clip = new Rectangle(alloc.x + last, alloc.y, (int) getHyphenWidth(), alloc.height);
Shape oldClip = g.getClip();
g.setClip(clip);
g.setFont(getFont());
int charHeight = 0;
if (getContainer() != null) {
charHeight = getContainer().getFontMetrics(getFont()).getHeight();
g.drawString("-", alloc.x + last, alloc.y + fm.getMaxAscent() + Math.round( (alloc.height - charHeight) / 2));
}
else {
g.drawString("-", alloc.x + last, alloc.y + alloc.height - fm.getMaxDescent());
}
g.setClip(oldClip);
}
}
}
}