/* dIntProg Browser. A webbrowser written in Java. * Copyright (C) 2001 Martin Geisler <[email protected]> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ import java.awt.Graphics; import java.awt.Color; import java.awt.Font; import javax.swing.*; import java.text.*; import java.util.*; import java.net.*; /** A fragment of text. This is the basic building-block of all * paragraphs. A <code>TextFragment</code> is meant to be used * together with other fragments to form larger pieces of text. Each * fragment has it's own content, font, and may be a link. */ public class TextFragment extends AbstractRigidBox { private String content; private Graphics pen; private Font font; private URL url; /* A very small font - used when switching to the real font. */ private Font switch_font = new Font(null, Font.PLAIN, 4); /** Creates a new <code>TextFragment</code>. * @param str the string contained within this fragment. The * string should contain any white-space necessary to ensure * correct spacing when this fragment is placed side-to-side with * other fragments. * @param f the font that will be used when rendering the * fragment. The font is also used when the content is meassured. * @param u the URL that this fragment links to. If the fragment * doesn't link to anywhere <code>u</code> should be * <code>null</code>. */ public TextFragment(String str, Font f, URL u) { content = str; font = f; url = u; } /** Creates a new <code>TextFragment</code>. This is used when a * fragment is split into two. The new fragments will have to be * created by this constructor, as this is the only way to give * the new fragments the needed graphics context. * @param str the string contained within this fragment. The * string should contain any white-space necessary to ensure * correct spacing when this fragment is placed side-to-side with * other fragments. * @param f the font that will be used when rendering the * fragment. The font is also used when the content is meassured. * @param u the URL that this fragment links to. If the fragment * doesn't link to anywhere <code>u</code> should be * <code>null</code>. */ private TextFragment(String str, Font f, URL u, Graphics g) { this(str, f, u); doLayout(g, null, 0); } public void doLayout(Graphics g, JComponent c, int w) { if (width == -1) { /* Argh! Java has a strange and stupid bug that means that * font changes doesn't apply until you change the /size/ * of the font /and/ draw with it. */ g.setFont(switch_font); g.drawString("", 0, 0); /* We can now use the real font. */ g.setFont(font); width = g.getFontMetrics().stringWidth(content); height = g.getFontMetrics().getHeight(); pen = g; Browser.debug("TextFragment.doLayout: " + this); } } /** Returns the width of it's argument. * @param s the string to be meassured. */ private int stringWidth(String s) { pen.setFont(font); return pen.getFontMetrics().stringWidth(s); } /** Returns the minimum width of this <code>TextFragment</code>. * The result is calculated by meassuring every word in the * string and is only calculated once for each fragment. * @return the minimum width.*/ public int getMinimumWidth() { /* We calculate the minimum width, if necessary */ if (min_width == -1) { StringTokenizer st = new StringTokenizer(content, " "); while (st.hasMoreTokens()) { String s = st.nextToken(); int w = stringWidth(s); if (w > min_width) { min_width = w; } } } return min_width; } /** Reports if a split is possible at <code>w</code>. For a * fragment to be split, it must contain more than a single word. * If it does, the width of the initial word (plus any white * space at the beginning) is meassured and compared with * <code>w</code>. * @param w the desired width of the head. * @return <code>true</code> if <code>w</code> is greather than * the length of the initial word. */ public boolean splitIsPossible(int w) { int offset = content.indexOf(' '); if (offset == 0) { /* The string starts with a space. We look the for * next space. */ offset = content.indexOf(' ', 1); } if (offset == -1 || offset == content.length()-1) { /* There is only a single word in the string, which * might end with a space. */ return false; } else { /* We return the length of the first word (plus the * initial white space, if any) */ String substr = content.substring(0, offset); return (w > stringWidth(substr)); } } public void drawAt(int x, int y, DocumentView v) { if (url != null) { v.drawString(x, y, height, content, font, new HyperLink(x, y, width, height, url)); } else { v.drawString(x, y, height, content, font, null); } if (Browser.debugging) { v.drawRect(x, y, width, height, Color.orange); } } public Box splitHead(int w) { StringTokenizer st = new StringTokenizer(content, " ", true); String result = ""; String s = st.nextToken(); int x_offset = stringWidth(s); while (st.hasMoreTokens() && x_offset < w) { result = result + s; s = st.nextToken(); x_offset = x_offset + stringWidth(s); } return new TextFragment(result, font, url, pen); } public Box splitTail(int w) { StringTokenizer st = new StringTokenizer(content, " ", true); String result = ""; String s = st.nextToken(); int x_offset = stringWidth(s); while (st.hasMoreTokens() && x_offset < w) { result = result + s; s = st.nextToken(); x_offset = x_offset + stringWidth(s); } result = content.substring(result.length()); /* The tail shouldn't start with a space. */ if (result.charAt(0) == ' ') { result = result.substring(1); } return new TextFragment(result, font, url, pen); } public Box getSmallestHead() { int offset = content.indexOf(' '); if (offset == -1 || offset == content.length()-1) { /* There is only a single word in the string. */ return null; } else { return new TextFragment(content.substring(0, offset), font, url, pen); } } public Box getLargestTail() { int offset = content.indexOf(' '); if (offset == -1 || offset == content.length()-1) { /* There is only a single word in the string. */ return null; } else { return new TextFragment(content.substring(offset + 1), font, url, pen); } } public void trim(int edge) { Browser.debug("Trimming: " + this); if (content.length() > 0) { if (edge == RigidBox.LEFT && content.charAt(0) == ' ') { content = content.substring(0); } else if (edge == RigidBox.RIGHT && content.charAt(content.length()-1) == ' ') { content = content.substring(0, content.length()-1); } else if (edge == RigidBox.BOTH) { content = content.trim(); } width = stringWidth(content); } } public String toString() { if (content.length() > 16) { return "'" + content.substring(0, 13) + "...': " + width + "x" + height; } else { return "'" + content + "': " + width + "x" + height; } } }