001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: *
041: * Contributor(s): Ivan Soleimanipour.
042: */
043:
044: /*
045: * "MyFontMetrics"
046: * MyFontMetrics.java 1.5 01/07/10
047: */
048:
049: package org.netbeans.lib.terminalemulator;
050:
051: import java.awt.*;
052: import java.util.AbstractMap;
053: import java.util.HashMap;
054:
055: class MyFontMetrics {
056:
057: /**
058: * WidthCache contains a byte array that maps a character to it's cell width.
059: * It also keeps track of whether we've discovered that we're dealing with a
060: * font that has wide characters. This information is kept with WidthCache
061: * because the caches are shared between MyFontMetrics and we test for
062: * multi-cellnes only on a cache miss. So we got into a situation where one
063: * Term got a wide character, missed the cache, figured that it's multi-cell,
064: * then another Term got the same character didn't miss the cache and didn't
065: * set it's own multi-cell bit.
066: *
067: * The reference counting stuff is explained with CacheFactory.
068: */
069:
070: static class WidthCache {
071: byte[] cache = new byte[Character.MAX_VALUE + 1];
072: int reference_count = 1;
073:
074: public void up() {
075: reference_count++;
076: if (reference_count == 1)
077: cache = new byte[Character.MAX_VALUE + 1];
078: }
079:
080: public void down() {
081: if (reference_count == 0)
082: return;
083: reference_count--;
084: if (reference_count == 0)
085: cache = null;
086: }
087:
088: public boolean isMultiCell() {
089: return multiCell;
090: }
091:
092: public void setMultiCell(boolean multiCell) {
093: this .multiCell = multiCell;
094: }
095:
096: private boolean multiCell = false;
097: }
098:
099: /**
100: *
101: * CacheFactory doles out WidthCaches.
102: *
103: * These caches are 64Kb (Character.MAX_VALUE+1) and we don't really want
104: * Each interp to have it's own. So we share them in a map using FontMetrics
105: * as a key. Unfortunately stuff will accumulate in the map. A WeakHashMap
106: * is no good because the keys (FontMetrics) are usually alive. For all I
107: * know Jave might be cacheing them in turn. I actually tried using a
108: * WeakHashMap and wouldn't see things going away, even after System.gc().
109: * <p>
110: * So we get this slightly more involved manager.
111: * <br>
112: * A WidthCache holds on to the actual WidthCache and reference counts it.
113: * When the count goes to 0 the actual cache array is "free"d be nulling
114: * it's reference. To make the reference count go down CacheFactory.disposeBy()
115: * is used. And that is called from MyFontMetrics.finalize().
116: *
117: * NOTE: The actual WidthCache's instances _will_ accumulate, but they are small and
118: * there are only so many font variations an app can go through. As I
119: * mentioned above using a WeakHashMap doesn't help much because WidthCache's
120: * are keyed by relatively persistent FontMetrics.
121: */
122:
123: private static class CacheFactory {
124: static synchronized WidthCache cacheForFontMetrics(
125: FontMetrics fm) {
126: WidthCache entry = (WidthCache) map.get(fm);
127: if (entry == null) {
128: entry = new WidthCache();
129: map.put(fm, entry);
130: } else {
131: entry.up();
132: }
133: return entry;
134: }
135:
136: static synchronized void disposeBy(FontMetrics fm) {
137: WidthCache entry = (WidthCache) map.get(fm);
138: if (entry != null)
139: entry.down();
140: }
141:
142: private static AbstractMap map = new HashMap();
143: }
144:
145: public MyFontMetrics(Component component, Font font) {
146: fm = component.getFontMetrics(font);
147: width = fm.charWidth('a');
148: height = fm.getHeight();
149: ascent = fm.getAscent();
150: leading = fm.getLeading();
151:
152: // HACK
153: // From all I can tell both xterm and DtTerm ignore the leading.
154: // Maybe X font's don't have a leading and Java sets it to one
155: // artificially? Because unless I do the below I get one extra pixel
156: // between my lines.
157: //
158: // Some code takes the leading into account and some code doesn't.
159: // the following makes things match up, but if we ever undo this
160: // we'll have to go and adjust how everything is drawn (cursor,
161: // reverse-video attribute, underscore, bg stripe, selection etc.
162:
163: height -= leading;
164: leading = 0;
165:
166: cwidth_cache = CacheFactory.cacheForFontMetrics(fm);
167: }
168:
169: protected void finalize() {
170: CacheFactory.disposeBy(fm);
171: }
172:
173: public int width;
174: public int height;
175: public int ascent;
176: public int leading;
177: public FontMetrics fm;
178:
179: private WidthCache cwidth_cache;
180:
181: public boolean isMultiCell() {
182: return cwidth_cache.isMultiCell();
183: }
184:
185: /*
186: * Called 'wcwidth' for historical reasons. (see wcwidth(3) on unix.)
187: * Return how many cells this character occupies.
188: */
189:
190: public int wcwidth(char c) {
191: int cell_width = cwidth_cache.cache[c]; // how many cells wide
192:
193: if (cell_width == 0) {
194: // width not cached yet so figure it out
195: int pixel_width = fm.charWidth(c);
196:
197: if (pixel_width == width) {
198: cell_width = 1;
199:
200: } else if (pixel_width == 0) {
201: cell_width = 1;
202:
203: } else {
204: // round up pixel width to multiple of cell size
205: // then distill into a width in terms of cells.
206: int mod = pixel_width % width;
207: int rounded_width = pixel_width;
208: if (mod != 0)
209: rounded_width = pixel_width + (width - mod);
210: cell_width = rounded_width / width;
211: if (cell_width == 0)
212: cell_width = 1;
213:
214: cwidth_cache.setMultiCell(true);
215: }
216:
217: cwidth_cache.cache[c] = (byte) cell_width;
218: }
219: return cell_width;
220: }
221:
222: /*
223: * Shift to the multi-cell character regime as soon as we spot one.
224: * The actual work is done in wcwidth() itself.
225: */
226: void checkForMultiCell(char c) {
227: wcwidth(c);
228: }
229: }
|