001 /*
002 * Portions Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 *
025 */
026
027 /*
028 * @(#)StyledParagraph.java 1.9 05/08/26
029 * (C) Copyright IBM Corp. 1999, All rights reserved.
030 */
031 package java.awt.font;
032
033 import java.awt.Font;
034 import java.awt.Toolkit;
035 import java.awt.im.InputMethodHighlight;
036 import java.text.Annotation;
037 import java.text.AttributedCharacterIterator;
038 import java.util.Vector;
039 import java.util.HashMap;
040 import java.util.Map;
041 import sun.font.Decoration;
042 import sun.font.FontResolver;
043 import sun.text.CodePointIterator;
044
045 /**
046 * This class stores Font, GraphicAttribute, and Decoration intervals
047 * on a paragraph of styled text.
048 * <p>
049 * Currently, this class is optimized for a small number of intervals
050 * (preferrably 1).
051 */
052 final class StyledParagraph {
053
054 // the length of the paragraph
055 private int length;
056
057 // If there is a single Decoration for the whole paragraph, it
058 // is stored here. Otherwise this field is ignored.
059
060 private Decoration decoration;
061
062 // If there is a single Font or GraphicAttribute for the whole
063 // paragraph, it is stored here. Otherwise this field is ignored.
064 private Object font;
065
066 // If there are multiple Decorations in the paragraph, they are
067 // stored in this Vector, in order. Otherwise this vector and
068 // the decorationStarts array are null.
069 private Vector decorations;
070 // If there are multiple Decorations in the paragraph,
071 // decorationStarts[i] contains the index where decoration i
072 // starts. For convenience, there is an extra entry at the
073 // end of this array with the length of the paragraph.
074 int[] decorationStarts;
075
076 // If there are multiple Fonts/GraphicAttributes in the paragraph,
077 // they are
078 // stored in this Vector, in order. Otherwise this vector and
079 // the fontStarts array are null.
080 private Vector fonts;
081 // If there are multiple Fonts/GraphicAttributes in the paragraph,
082 // fontStarts[i] contains the index where decoration i
083 // starts. For convenience, there is an extra entry at the
084 // end of this array with the length of the paragraph.
085 int[] fontStarts;
086
087 private static int INITIAL_SIZE = 8;
088
089 /**
090 * Create a new StyledParagraph over the given styled text.
091 * @param aci an iterator over the text
092 * @param chars the characters extracted from aci
093 */
094 public StyledParagraph(AttributedCharacterIterator aci, char[] chars) {
095
096 int start = aci.getBeginIndex();
097 int end = aci.getEndIndex();
098 length = end - start;
099
100 int index = start;
101 aci.first();
102
103 do {
104 final int nextRunStart = aci.getRunLimit();
105 final int localIndex = index - start;
106
107 Map attributes = aci.getAttributes();
108 attributes = addInputMethodAttrs(attributes);
109 Decoration d = Decoration.getDecoration(attributes);
110 addDecoration(d, localIndex);
111
112 Object f = getGraphicOrFont(attributes);
113 if (f == null) {
114 addFonts(chars, attributes, localIndex, nextRunStart
115 - start);
116 } else {
117 addFont(f, localIndex);
118 }
119
120 aci.setIndex(nextRunStart);
121 index = nextRunStart;
122
123 } while (index < end);
124
125 // Add extra entries to starts arrays with the length
126 // of the paragraph. 'this' is used as a dummy value
127 // in the Vector.
128 if (decorations != null) {
129 decorationStarts = addToVector(this , length, decorations,
130 decorationStarts);
131 }
132 if (fonts != null) {
133 fontStarts = addToVector(this , length, fonts, fontStarts);
134 }
135 }
136
137 /**
138 * Adjust indices in starts to reflect an insertion after pos.
139 * Any index in starts greater than pos will be increased by 1.
140 */
141 private static void insertInto(int pos, int[] starts, int numStarts) {
142
143 while (starts[--numStarts] > pos) {
144 starts[numStarts] += 1;
145 }
146 }
147
148 /**
149 * Return a StyledParagraph reflecting the insertion of a single character
150 * into the text. This method will attempt to reuse the given paragraph,
151 * but may create a new paragraph.
152 * @param aci an iterator over the text. The text should be the same as the
153 * text used to create (or most recently update) oldParagraph, with
154 * the exception of inserting a single character at insertPos.
155 * @param chars the characters in aci
156 * @param insertPos the index of the new character in aci
157 * @param oldParagraph a StyledParagraph for the text in aci before the
158 * insertion
159 */
160 public static StyledParagraph insertChar(
161 AttributedCharacterIterator aci, char[] chars,
162 int insertPos, StyledParagraph oldParagraph) {
163
164 // If the styles at insertPos match those at insertPos-1,
165 // oldParagraph will be reused. Otherwise we create a new
166 // paragraph.
167
168 char ch = aci.setIndex(insertPos);
169 int relativePos = Math.max(insertPos - aci.getBeginIndex() - 1,
170 0);
171
172 Map attributes = addInputMethodAttrs(aci.getAttributes());
173 Decoration d = Decoration.getDecoration(attributes);
174 if (!oldParagraph.getDecorationAt(relativePos).equals(d)) {
175 return new StyledParagraph(aci, chars);
176 }
177 Object f = getGraphicOrFont(attributes);
178 if (f == null) {
179 FontResolver resolver = FontResolver.getInstance();
180 int fontIndex = resolver.getFontIndex(ch);
181 f = resolver.getFont(fontIndex, attributes);
182 }
183 if (!oldParagraph.getFontOrGraphicAt(relativePos).equals(f)) {
184 return new StyledParagraph(aci, chars);
185 }
186
187 // insert into existing paragraph
188 oldParagraph.length += 1;
189 if (oldParagraph.decorations != null) {
190 insertInto(relativePos, oldParagraph.decorationStarts,
191 oldParagraph.decorations.size());
192 }
193 if (oldParagraph.fonts != null) {
194 insertInto(relativePos, oldParagraph.fontStarts,
195 oldParagraph.fonts.size());
196 }
197 return oldParagraph;
198 }
199
200 /**
201 * Adjust indices in starts to reflect a deletion after deleteAt.
202 * Any index in starts greater than deleteAt will be increased by 1.
203 * It is the caller's responsibility to make sure that no 0-length
204 * runs result.
205 */
206 private static void deleteFrom(int deleteAt, int[] starts,
207 int numStarts) {
208
209 while (starts[--numStarts] > deleteAt) {
210 starts[numStarts] -= 1;
211 }
212 }
213
214 /**
215 * Return a StyledParagraph reflecting the insertion of a single character
216 * into the text. This method will attempt to reuse the given paragraph,
217 * but may create a new paragraph.
218 * @param aci an iterator over the text. The text should be the same as the
219 * text used to create (or most recently update) oldParagraph, with
220 * the exception of deleting a single character at deletePos.
221 * @param chars the characters in aci
222 * @param deletePos the index where a character was removed
223 * @param oldParagraph a StyledParagraph for the text in aci before the
224 * insertion
225 */
226 public static StyledParagraph deleteChar(
227 AttributedCharacterIterator aci, char[] chars,
228 int deletePos, StyledParagraph oldParagraph) {
229
230 // We will reuse oldParagraph unless there was a length-1 run
231 // at deletePos. We could do more work and check the individual
232 // Font and Decoration runs, but we don't right now...
233 deletePos -= aci.getBeginIndex();
234
235 if (oldParagraph.decorations == null
236 && oldParagraph.fonts == null) {
237 oldParagraph.length -= 1;
238 return oldParagraph;
239 }
240
241 if (oldParagraph.getRunLimit(deletePos) == deletePos + 1) {
242 if (deletePos == 0
243 || oldParagraph.getRunLimit(deletePos - 1) == deletePos) {
244 return new StyledParagraph(aci, chars);
245 }
246 }
247
248 oldParagraph.length -= 1;
249 if (oldParagraph.decorations != null) {
250 deleteFrom(deletePos, oldParagraph.decorationStarts,
251 oldParagraph.decorations.size());
252 }
253 if (oldParagraph.fonts != null) {
254 deleteFrom(deletePos, oldParagraph.fontStarts,
255 oldParagraph.fonts.size());
256 }
257 return oldParagraph;
258 }
259
260 /**
261 * Return the index at which there is a different Font, GraphicAttribute, or
262 * Dcoration than at the given index.
263 * @param index a valid index in the paragraph
264 * @return the first index where there is a change in attributes from
265 * those at index
266 */
267 public int getRunLimit(int index) {
268
269 if (index < 0 || index >= length) {
270 throw new IllegalArgumentException("index out of range");
271 }
272 int limit1 = length;
273 if (decorations != null) {
274 int run = findRunContaining(index, decorationStarts);
275 limit1 = decorationStarts[run + 1];
276 }
277 int limit2 = length;
278 if (fonts != null) {
279 int run = findRunContaining(index, fontStarts);
280 limit2 = fontStarts[run + 1];
281 }
282 return Math.min(limit1, limit2);
283 }
284
285 /**
286 * Return the Decoration in effect at the given index.
287 * @param index a valid index in the paragraph
288 * @return the Decoration at index.
289 */
290 public Decoration getDecorationAt(int index) {
291
292 if (index < 0 || index >= length) {
293 throw new IllegalArgumentException("index out of range");
294 }
295 if (decorations == null) {
296 return decoration;
297 }
298 int run = findRunContaining(index, decorationStarts);
299 return (Decoration) decorations.elementAt(run);
300 }
301
302 /**
303 * Return the Font or GraphicAttribute in effect at the given index.
304 * The client must test the type of the return value to determine what
305 * it is.
306 * @param index a valid index in the paragraph
307 * @return the Font or GraphicAttribute at index.
308 */
309 public Object getFontOrGraphicAt(int index) {
310
311 if (index < 0 || index >= length) {
312 throw new IllegalArgumentException("index out of range");
313 }
314 if (fonts == null) {
315 return font;
316 }
317 int run = findRunContaining(index, fontStarts);
318 return fonts.elementAt(run);
319 }
320
321 /**
322 * Return i such that starts[i] <= index < starts[i+1]. starts
323 * must be in increasing order, with at least one element greater
324 * than index.
325 */
326 private static int findRunContaining(int index, int[] starts) {
327
328 for (int i = 1; true; i++) {
329 if (starts[i] > index) {
330 return i - 1;
331 }
332 }
333 }
334
335 /**
336 * Append the given Object to the given Vector. Add
337 * the given index to the given starts array. If the
338 * starts array does not have room for the index, a
339 * new array is created and returned.
340 */
341 private static int[] addToVector(Object obj, int index, Vector v,
342 int[] starts) {
343
344 if (!v.lastElement().equals(obj)) {
345 v.addElement(obj);
346 int count = v.size();
347 if (starts.length == count) {
348 int[] temp = new int[starts.length * 2];
349 System.arraycopy(starts, 0, temp, 0, starts.length);
350 starts = temp;
351 }
352 starts[count - 1] = index;
353 }
354 return starts;
355 }
356
357 /**
358 * Add a new Decoration run with the given Decoration at the
359 * given index.
360 */
361 private void addDecoration(Decoration d, int index) {
362
363 if (decorations != null) {
364 decorationStarts = addToVector(d, index, decorations,
365 decorationStarts);
366 } else if (decoration == null) {
367 decoration = d;
368 } else {
369 if (!decoration.equals(d)) {
370 decorations = new Vector(INITIAL_SIZE);
371 decorations.addElement(decoration);
372 decorations.addElement(d);
373 decorationStarts = new int[INITIAL_SIZE];
374 decorationStarts[0] = 0;
375 decorationStarts[1] = index;
376 }
377 }
378 }
379
380 /**
381 * Add a new Font/GraphicAttribute run with the given object at the
382 * given index.
383 */
384 private void addFont(Object f, int index) {
385
386 if (fonts != null) {
387 fontStarts = addToVector(f, index, fonts, fontStarts);
388 } else if (font == null) {
389 font = f;
390 } else {
391 if (!font.equals(f)) {
392 fonts = new Vector(INITIAL_SIZE);
393 fonts.addElement(font);
394 fonts.addElement(f);
395 fontStarts = new int[INITIAL_SIZE];
396 fontStarts[0] = 0;
397 fontStarts[1] = index;
398 }
399 }
400 }
401
402 /**
403 * Resolve the given chars into Fonts using FontResolver, then add
404 * font runs for each.
405 */
406 private void addFonts(char[] chars, Map attributes, int start,
407 int limit) {
408
409 FontResolver resolver = FontResolver.getInstance();
410 CodePointIterator iter = CodePointIterator.create(chars, start,
411 limit);
412 for (int runStart = iter.charIndex(); runStart < limit; runStart = iter
413 .charIndex()) {
414 int fontIndex = resolver.nextFontRunIndex(iter);
415 addFont(resolver.getFont(fontIndex, attributes), runStart);
416 }
417 }
418
419 /**
420 * Return a Map with entries from oldStyles, as well as input
421 * method entries, if any.
422 */
423 static Map addInputMethodAttrs(Map oldStyles) {
424
425 Object value = oldStyles
426 .get(TextAttribute.INPUT_METHOD_HIGHLIGHT);
427
428 try {
429 if (value != null) {
430 if (value instanceof Annotation) {
431 value = ((Annotation) value).getValue();
432 }
433
434 InputMethodHighlight hl;
435 hl = (InputMethodHighlight) value;
436
437 Map imStyles = null;
438 try {
439 imStyles = hl.getStyle();
440 } catch (NoSuchMethodError e) {
441 }
442
443 if (imStyles == null) {
444 Toolkit tk = Toolkit.getDefaultToolkit();
445 imStyles = tk.mapInputMethodHighlight(hl);
446 }
447
448 if (imStyles != null) {
449 HashMap newStyles = new HashMap(5, (float) 0.9);
450 newStyles.putAll(oldStyles);
451
452 newStyles.putAll(imStyles);
453
454 return newStyles;
455 }
456 }
457 } catch (ClassCastException e) {
458 }
459
460 return oldStyles;
461 }
462
463 /**
464 * Extract a GraphicAttribute or Font from the given attributes.
465 * If attributes does not contain a GraphicAttribute, Font, or
466 * Font family entry this method returns null.
467 */
468 private static Object getGraphicOrFont(Map attributes) {
469
470 Object value = attributes.get(TextAttribute.CHAR_REPLACEMENT);
471 if (value != null) {
472 return value;
473 }
474 value = attributes.get(TextAttribute.FONT);
475 if (value != null) {
476 return value;
477 }
478
479 if (attributes.get(TextAttribute.FAMILY) != null) {
480 return Font.getFont(attributes);
481 } else {
482 return null;
483 }
484 }
485 }
|