blob: 97748de5e29c462e763491bc551ff25fb1879b1c [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Portions Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 *
25 */
26
27/*
28 * (C) Copyright IBM Corp. 1999, All rights reserved.
29 */
30package java.awt.font;
31
32import java.awt.Font;
33import java.awt.Toolkit;
34import java.awt.im.InputMethodHighlight;
35import java.text.Annotation;
36import java.text.AttributedCharacterIterator;
37import java.util.Vector;
38import java.util.HashMap;
39import java.util.Map;
40import sun.font.Decoration;
41import sun.font.FontResolver;
42import sun.text.CodePointIterator;
43
44/**
45 * This class stores Font, GraphicAttribute, and Decoration intervals
46 * on a paragraph of styled text.
47 * <p>
48 * Currently, this class is optimized for a small number of intervals
49 * (preferrably 1).
50 */
51final class StyledParagraph {
52
53 // the length of the paragraph
54 private int length;
55
56 // If there is a single Decoration for the whole paragraph, it
57 // is stored here. Otherwise this field is ignored.
58
59 private Decoration decoration;
60
61 // If there is a single Font or GraphicAttribute for the whole
62 // paragraph, it is stored here. Otherwise this field is ignored.
63 private Object font;
64
65 // If there are multiple Decorations in the paragraph, they are
66 // stored in this Vector, in order. Otherwise this vector and
67 // the decorationStarts array are null.
68 private Vector decorations;
69 // If there are multiple Decorations in the paragraph,
70 // decorationStarts[i] contains the index where decoration i
71 // starts. For convenience, there is an extra entry at the
72 // end of this array with the length of the paragraph.
73 int[] decorationStarts;
74
75 // If there are multiple Fonts/GraphicAttributes in the paragraph,
76 // they are
77 // stored in this Vector, in order. Otherwise this vector and
78 // the fontStarts array are null.
79 private Vector fonts;
80 // If there are multiple Fonts/GraphicAttributes in the paragraph,
81 // fontStarts[i] contains the index where decoration i
82 // starts. For convenience, there is an extra entry at the
83 // end of this array with the length of the paragraph.
84 int[] fontStarts;
85
86 private static int INITIAL_SIZE = 8;
87
88 /**
89 * Create a new StyledParagraph over the given styled text.
90 * @param aci an iterator over the text
91 * @param chars the characters extracted from aci
92 */
93 public StyledParagraph(AttributedCharacterIterator aci,
94 char[] chars) {
95
96 int start = aci.getBeginIndex();
97 int end = aci.getEndIndex();
98 length = end - start;
99
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-start);
115 }
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, decorationStarts);
130 }
131 if (fonts != null) {
132 fontStarts = addToVector(this, length, fonts, fontStarts);
133 }
134 }
135
136 /**
137 * Adjust indices in starts to reflect an insertion after pos.
138 * Any index in starts greater than pos will be increased by 1.
139 */
140 private static void insertInto(int pos, int[] starts, int numStarts) {
141
142 while (starts[--numStarts] > pos) {
143 starts[numStarts] += 1;
144 }
145 }
146
147 /**
148 * Return a StyledParagraph reflecting the insertion of a single character
149 * into the text. This method will attempt to reuse the given paragraph,
150 * but may create a new paragraph.
151 * @param aci an iterator over the text. The text should be the same as the
152 * text used to create (or most recently update) oldParagraph, with
153 * the exception of inserting a single character at insertPos.
154 * @param chars the characters in aci
155 * @param insertPos the index of the new character in aci
156 * @param oldParagraph a StyledParagraph for the text in aci before the
157 * insertion
158 */
159 public static StyledParagraph insertChar(AttributedCharacterIterator aci,
160 char[] chars,
161 int insertPos,
162 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, 0);
170
171 Map attributes = addInputMethodAttrs(aci.getAttributes());
172 Decoration d = Decoration.getDecoration(attributes);
173 if (!oldParagraph.getDecorationAt(relativePos).equals(d)) {
174 return new StyledParagraph(aci, chars);
175 }
176 Object f = getGraphicOrFont(attributes);
177 if (f == null) {
178 FontResolver resolver = FontResolver.getInstance();
179 int fontIndex = resolver.getFontIndex(ch);
180 f = resolver.getFont(fontIndex, attributes);
181 }
182 if (!oldParagraph.getFontOrGraphicAt(relativePos).equals(f)) {
183 return new StyledParagraph(aci, chars);
184 }
185
186 // insert into existing paragraph
187 oldParagraph.length += 1;
188 if (oldParagraph.decorations != null) {
189 insertInto(relativePos,
190 oldParagraph.decorationStarts,
191 oldParagraph.decorations.size());
192 }
193 if (oldParagraph.fonts != null) {
194 insertInto(relativePos,
195 oldParagraph.fontStarts,
196 oldParagraph.fonts.size());
197 }
198 return oldParagraph;
199 }
200
201 /**
202 * Adjust indices in starts to reflect a deletion after deleteAt.
203 * Any index in starts greater than deleteAt will be increased by 1.
204 * It is the caller's responsibility to make sure that no 0-length
205 * runs result.
206 */
207 private static void deleteFrom(int deleteAt, int[] starts, 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(AttributedCharacterIterator aci,
227 char[] chars,
228 int deletePos,
229 StyledParagraph oldParagraph) {
230
231 // We will reuse oldParagraph unless there was a length-1 run
232 // at deletePos. We could do more work and check the individual
233 // Font and Decoration runs, but we don't right now...
234 deletePos -= aci.getBeginIndex();
235
236 if (oldParagraph.decorations == null && oldParagraph.fonts == null) {
237 oldParagraph.length -= 1;
238 return oldParagraph;
239 }
240
241 if (oldParagraph.getRunLimit(deletePos) == deletePos+1) {
242 if (deletePos == 0 || oldParagraph.getRunLimit(deletePos-1) == deletePos) {
243 return new StyledParagraph(aci, chars);
244 }
245 }
246
247 oldParagraph.length -= 1;
248 if (oldParagraph.decorations != null) {
249 deleteFrom(deletePos,
250 oldParagraph.decorationStarts,
251 oldParagraph.decorations.size());
252 }
253 if (oldParagraph.fonts != null) {
254 deleteFrom(deletePos,
255 oldParagraph.fontStarts,
256 oldParagraph.fonts.size());
257 }
258 return oldParagraph;
259 }
260
261 /**
262 * Return the index at which there is a different Font, GraphicAttribute, or
263 * Dcoration than at the given index.
264 * @param index a valid index in the paragraph
265 * @return the first index where there is a change in attributes from
266 * those at index
267 */
268 public int getRunLimit(int index) {
269
270 if (index < 0 || index >= length) {
271 throw new IllegalArgumentException("index out of range");
272 }
273 int limit1 = length;
274 if (decorations != null) {
275 int run = findRunContaining(index, decorationStarts);
276 limit1 = decorationStarts[run+1];
277 }
278 int limit2 = length;
279 if (fonts != null) {
280 int run = findRunContaining(index, fontStarts);
281 limit2 = fontStarts[run+1];
282 }
283 return Math.min(limit1, limit2);
284 }
285
286 /**
287 * Return the Decoration in effect at the given index.
288 * @param index a valid index in the paragraph
289 * @return the Decoration at index.
290 */
291 public Decoration getDecorationAt(int index) {
292
293 if (index < 0 || index >= length) {
294 throw new IllegalArgumentException("index out of range");
295 }
296 if (decorations == null) {
297 return decoration;
298 }
299 int run = findRunContaining(index, decorationStarts);
300 return (Decoration) decorations.elementAt(run);
301 }
302
303 /**
304 * Return the Font or GraphicAttribute in effect at the given index.
305 * The client must test the type of the return value to determine what
306 * it is.
307 * @param index a valid index in the paragraph
308 * @return the Font or GraphicAttribute at index.
309 */
310 public Object getFontOrGraphicAt(int index) {
311
312 if (index < 0 || index >= length) {
313 throw new IllegalArgumentException("index out of range");
314 }
315 if (fonts == null) {
316 return font;
317 }
318 int run = findRunContaining(index, fontStarts);
319 return fonts.elementAt(run);
320 }
321
322 /**
323 * Return i such that starts[i] <= index < starts[i+1]. starts
324 * must be in increasing order, with at least one element greater
325 * than index.
326 */
327 private static int findRunContaining(int index, int[] starts) {
328
329 for (int i=1; true; i++) {
330 if (starts[i] > index) {
331 return i-1;
332 }
333 }
334 }
335
336 /**
337 * Append the given Object to the given Vector. Add
338 * the given index to the given starts array. If the
339 * starts array does not have room for the index, a
340 * new array is created and returned.
341 */
342 private static int[] addToVector(Object obj,
343 int index,
344 Vector v,
345 int[] starts) {
346
347 if (!v.lastElement().equals(obj)) {
348 v.addElement(obj);
349 int count = v.size();
350 if (starts.length == count) {
351 int[] temp = new int[starts.length*2];
352 System.arraycopy(starts, 0, temp, 0, starts.length);
353 starts = temp;
354 }
355 starts[count-1] = index;
356 }
357 return starts;
358 }
359
360 /**
361 * Add a new Decoration run with the given Decoration at the
362 * given index.
363 */
364 private void addDecoration(Decoration d, int index) {
365
366 if (decorations != null) {
367 decorationStarts = addToVector(d,
368 index,
369 decorations,
370 decorationStarts);
371 }
372 else if (decoration == null) {
373 decoration = d;
374 }
375 else {
376 if (!decoration.equals(d)) {
377 decorations = new Vector(INITIAL_SIZE);
378 decorations.addElement(decoration);
379 decorations.addElement(d);
380 decorationStarts = new int[INITIAL_SIZE];
381 decorationStarts[0] = 0;
382 decorationStarts[1] = index;
383 }
384 }
385 }
386
387 /**
388 * Add a new Font/GraphicAttribute run with the given object at the
389 * given index.
390 */
391 private void addFont(Object f, int index) {
392
393 if (fonts != null) {
394 fontStarts = addToVector(f, index, fonts, fontStarts);
395 }
396 else if (font == null) {
397 font = f;
398 }
399 else {
400 if (!font.equals(f)) {
401 fonts = new Vector(INITIAL_SIZE);
402 fonts.addElement(font);
403 fonts.addElement(f);
404 fontStarts = new int[INITIAL_SIZE];
405 fontStarts[0] = 0;
406 fontStarts[1] = index;
407 }
408 }
409 }
410
411 /**
412 * Resolve the given chars into Fonts using FontResolver, then add
413 * font runs for each.
414 */
415 private void addFonts(char[] chars, Map attributes, int start, int limit) {
416
417 FontResolver resolver = FontResolver.getInstance();
418 CodePointIterator iter = CodePointIterator.create(chars, start, limit);
419 for (int runStart = iter.charIndex(); runStart < limit; runStart = iter.charIndex()) {
420 int fontIndex = resolver.nextFontRunIndex(iter);
421 addFont(resolver.getFont(fontIndex, attributes), runStart);
422 }
423 }
424
425 /**
426 * Return a Map with entries from oldStyles, as well as input
427 * method entries, if any.
428 */
429 static Map addInputMethodAttrs(Map oldStyles) {
430
431 Object value = oldStyles.get(TextAttribute.INPUT_METHOD_HIGHLIGHT);
432
433 try {
434 if (value != null) {
435 if (value instanceof Annotation) {
436 value = ((Annotation)value).getValue();
437 }
438
439 InputMethodHighlight hl;
440 hl = (InputMethodHighlight) value;
441
442 Map imStyles = null;
443 try {
444 imStyles = hl.getStyle();
445 } catch (NoSuchMethodError e) {
446 }
447
448 if (imStyles == null) {
449 Toolkit tk = Toolkit.getDefaultToolkit();
450 imStyles = tk.mapInputMethodHighlight(hl);
451 }
452
453 if (imStyles != null) {
454 HashMap newStyles = new HashMap(5, (float)0.9);
455 newStyles.putAll(oldStyles);
456
457 newStyles.putAll(imStyles);
458
459 return newStyles;
460 }
461 }
462 }
463 catch(ClassCastException e) {
464 }
465
466 return oldStyles;
467 }
468
469 /**
470 * Extract a GraphicAttribute or Font from the given attributes.
471 * If attributes does not contain a GraphicAttribute, Font, or
472 * Font family entry this method returns null.
473 */
474 private static Object getGraphicOrFont(Map attributes) {
475
476 Object value = attributes.get(TextAttribute.CHAR_REPLACEMENT);
477 if (value != null) {
478 return value;
479 }
480 value = attributes.get(TextAttribute.FONT);
481 if (value != null) {
482 return value;
483 }
484
485 if (attributes.get(TextAttribute.FAMILY) != null) {
486 return Font.getFont(attributes);
487 }
488 else {
489 return null;
490 }
491 }
492}