View Javadoc
1   /*
2    * Copyright (c) 2004, 2008, Oracle and/or its affiliates. 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.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  /*
27   *
28   * (C) Copyright IBM Corp. 2005 - All Rights Reserved
29   *
30   * The original version of this source code and documentation is
31   * copyrighted and owned by IBM. These materials are provided
32   * under terms of a License Agreement between IBM and Sun.
33   * This technology is protected by multiple US and International
34   * patents. This notice and attribution to IBM may not be removed.
35   */
36  
37  package sun.font;
38  
39  import static sun.font.EAttribute.*;
40  import static java.lang.Math.*;
41  
42  import java.awt.Font;
43  import java.awt.Paint;
44  import java.awt.Toolkit;
45  import java.awt.font.GraphicAttribute;
46  import java.awt.font.NumericShaper;
47  import java.awt.font.TextAttribute;
48  import java.awt.font.TransformAttribute;
49  import java.awt.geom.AffineTransform;
50  import java.awt.geom.NoninvertibleTransformException;
51  import java.awt.geom.Point2D;
52  import java.awt.im.InputMethodHighlight;
53  import java.io.Serializable;
54  import java.text.Annotation;
55  import java.text.AttributedCharacterIterator.Attribute;
56  import java.util.Map;
57  import java.util.HashMap;
58  import java.util.Hashtable;
59  
60  public final class AttributeValues implements Cloneable {
61      private int defined;
62      private int nondefault;
63  
64      private String family = "Default";
65      private float weight = 1f;
66      private float width = 1f;
67      private float posture; // 0f
68      private float size = 12f;
69      private float tracking; // 0f
70      private NumericShaper numericShaping; // null
71      private AffineTransform transform; // null == identity
72      private GraphicAttribute charReplacement; // null
73      private Paint foreground; // null
74      private Paint background; // null
75      private float justification = 1f;
76      private Object imHighlight; // null
77      // (can be either Attribute wrapping IMH, or IMH itself
78      private Font font; // here for completeness, don't actually use
79      private byte imUnderline = -1; // same default as underline
80      private byte superscript; // 0
81      private byte underline = -1; // arrgh, value for ON is 0
82      private byte runDirection = -2; // BIDI.DIRECTION_DEFAULT_LEFT_TO_RIGHT
83      private byte bidiEmbedding; // 0
84      private byte kerning; // 0
85      private byte ligatures; // 0
86      private boolean strikethrough; // false
87      private boolean swapColors; // false
88  
89      private AffineTransform baselineTransform; // derived from transform
90      private AffineTransform charTransform; // derived from transform
91  
92      private static final AttributeValues DEFAULT = new AttributeValues();
93  
94      // type-specific API
95      public String getFamily() { return family; }
96      public void setFamily(String f) { this.family = f; update(EFAMILY); }
97  
98      public float getWeight() { return weight; }
99      public void setWeight(float f) { this.weight = f; update(EWEIGHT); }
100 
101     public float getWidth() { return width; }
102     public void setWidth(float f) { this.width = f; update(EWIDTH); }
103 
104     public float getPosture() { return posture; }
105     public void setPosture(float f) { this.posture = f; update(EPOSTURE); }
106 
107     public float getSize() { return size; }
108     public void setSize(float f) { this.size = f; update(ESIZE); }
109 
110     public AffineTransform getTransform() { return transform; }
111     public void setTransform(AffineTransform f) {
112         this.transform = (f == null || f.isIdentity())
113             ? DEFAULT.transform
114             : new AffineTransform(f);
115         updateDerivedTransforms();
116         update(ETRANSFORM);
117     }
118     public void setTransform(TransformAttribute f) {
119         this.transform = (f == null || f.isIdentity())
120             ? DEFAULT.transform
121             : f.getTransform();
122         updateDerivedTransforms();
123         update(ETRANSFORM);
124     }
125 
126     public int getSuperscript() { return superscript; }
127     public void setSuperscript(int f) {
128       this.superscript = (byte)f; update(ESUPERSCRIPT); }
129 
130     public Font getFont() { return font; }
131     public void setFont(Font f) { this.font = f; update(EFONT); }
132 
133     public GraphicAttribute getCharReplacement() { return charReplacement; }
134     public void setCharReplacement(GraphicAttribute f) {
135       this.charReplacement = f; update(ECHAR_REPLACEMENT); }
136 
137     public Paint getForeground() { return foreground; }
138     public void setForeground(Paint f) {
139       this.foreground = f; update(EFOREGROUND); }
140 
141     public Paint getBackground() { return background; }
142     public void setBackground(Paint f) {
143       this.background = f; update(EBACKGROUND); }
144 
145     public int getUnderline() { return underline; }
146     public void setUnderline(int f) {
147       this.underline = (byte)f; update(EUNDERLINE); }
148 
149     public boolean getStrikethrough() { return strikethrough; }
150     public void setStrikethrough(boolean f) {
151       this.strikethrough = f; update(ESTRIKETHROUGH); }
152 
153     public int getRunDirection() { return runDirection; }
154     public void setRunDirection(int f) {
155       this.runDirection = (byte)f; update(ERUN_DIRECTION); }
156 
157     public int getBidiEmbedding() { return bidiEmbedding; }
158     public void setBidiEmbedding(int f) {
159       this.bidiEmbedding = (byte)f; update(EBIDI_EMBEDDING); }
160 
161     public float getJustification() { return justification; }
162     public void setJustification(float f) {
163       this.justification = f; update(EJUSTIFICATION); }
164 
165     public Object getInputMethodHighlight() { return imHighlight; }
166     public void setInputMethodHighlight(Annotation f) {
167       this.imHighlight = f; update(EINPUT_METHOD_HIGHLIGHT); }
168     public void setInputMethodHighlight(InputMethodHighlight f) {
169       this.imHighlight = f; update(EINPUT_METHOD_HIGHLIGHT); }
170 
171     public int getInputMethodUnderline() { return imUnderline; }
172     public void setInputMethodUnderline(int f) {
173       this.imUnderline = (byte)f; update(EINPUT_METHOD_UNDERLINE); }
174 
175     public boolean getSwapColors() { return swapColors; }
176     public void setSwapColors(boolean f) {
177       this.swapColors = f; update(ESWAP_COLORS); }
178 
179     public NumericShaper getNumericShaping() { return numericShaping; }
180     public void setNumericShaping(NumericShaper f) {
181       this.numericShaping = f; update(ENUMERIC_SHAPING); }
182 
183     public int getKerning() { return kerning; }
184     public void setKerning(int f) {
185       this.kerning = (byte)f; update(EKERNING); }
186 
187     public float getTracking() { return tracking; }
188     public void setTracking(float f) {
189       this.tracking = (byte)f; update(ETRACKING); }
190 
191     public int getLigatures() { return ligatures; }
192     public void setLigatures(int f) {
193       this.ligatures = (byte)f; update(ELIGATURES); }
194 
195 
196     public AffineTransform getBaselineTransform() { return baselineTransform; }
197     public AffineTransform getCharTransform() { return charTransform; }
198 
199     // mask api
200 
201     public static int getMask(EAttribute att) {
202         return att.mask;
203     }
204 
205     public static int getMask(EAttribute ... atts) {
206         int mask = 0;
207         for (EAttribute a: atts) {
208             mask |= a.mask;
209         }
210         return mask;
211     }
212 
213     public static final int MASK_ALL =
214         getMask(EAttribute.class.getEnumConstants());
215 
216     public void unsetDefault() {
217         defined &= nondefault;
218     }
219 
220     public void defineAll(int mask) {
221         defined |= mask;
222         if ((defined & EBASELINE_TRANSFORM.mask) != 0) {
223             throw new InternalError("can't define derived attribute");
224         }
225     }
226 
227     public boolean allDefined(int mask) {
228         return (defined & mask) == mask;
229     }
230 
231     public boolean anyDefined(int mask) {
232         return (defined & mask) != 0;
233     }
234 
235     public boolean anyNonDefault(int mask) {
236         return (nondefault & mask) != 0;
237     }
238 
239     // generic EAttribute API
240 
241     public boolean isDefined(EAttribute a) {
242         return (defined & a.mask) != 0;
243     }
244 
245     public boolean isNonDefault(EAttribute a) {
246         return (nondefault & a.mask) != 0;
247     }
248 
249     public void setDefault(EAttribute a) {
250         if (a.att == null) {
251             throw new InternalError("can't set default derived attribute: " + a);
252         }
253         i_set(a, DEFAULT);
254         defined |= a.mask;
255         nondefault &= ~a.mask;
256     }
257 
258     public void unset(EAttribute a) {
259         if (a.att == null) {
260             throw new InternalError("can't unset derived attribute: " + a);
261         }
262         i_set(a, DEFAULT);
263         defined &= ~a.mask;
264         nondefault &= ~a.mask;
265     }
266 
267     public void set(EAttribute a, AttributeValues src) {
268         if (a.att == null) {
269             throw new InternalError("can't set derived attribute: " + a);
270         }
271         if (src == null || src == DEFAULT) {
272             setDefault(a);
273         } else {
274             if ((src.defined & a.mask) != 0) {
275                 i_set(a, src);
276                 update(a);
277             }
278         }
279     }
280 
281     public void set(EAttribute a, Object o) {
282         if (a.att == null) {
283             throw new InternalError("can't set derived attribute: " + a);
284         }
285         if (o != null) {
286             try {
287                 i_set(a, o);
288                 update(a);
289                 return;
290             } catch (Exception e) {
291             }
292         }
293         setDefault(a);
294     }
295 
296     public Object get(EAttribute a) {
297         if (a.att == null) {
298             throw new InternalError("can't get derived attribute: " + a);
299         }
300         if ((nondefault & a.mask) != 0) {
301             return i_get(a);
302         }
303         return null;
304     }
305 
306     // merging
307 
308     public AttributeValues merge(Map<? extends Attribute, ?>map) {
309         return merge(map, MASK_ALL);
310     }
311 
312     public AttributeValues merge(Map<? extends Attribute, ?>map,
313                                  int mask) {
314         if (map instanceof AttributeMap &&
315             ((AttributeMap) map).getValues() != null) {
316             merge(((AttributeMap)map).getValues(), mask);
317         } else if (map != null && !map.isEmpty()) {
318             for (Map.Entry<? extends Attribute, ?> e: map.entrySet()) {
319                 try {
320                     EAttribute ea = EAttribute.forAttribute(e.getKey());
321                     if (ea!= null && (mask & ea.mask) != 0) {
322                         set(ea, e.getValue());
323                     }
324                 } catch (ClassCastException cce) {
325                     // IGNORED
326                 }
327             }
328         }
329         return this;
330     }
331 
332     public AttributeValues merge(AttributeValues src) {
333         return merge(src, MASK_ALL);
334     }
335 
336     public AttributeValues merge(AttributeValues src, int mask) {
337         int m = mask & src.defined;
338         for (EAttribute ea: EAttribute.atts) {
339             if (m == 0) {
340                 break;
341             }
342             if ((m & ea.mask) != 0) {
343                 m &= ~ea.mask;
344                 i_set(ea, src);
345                 update(ea);
346             }
347         }
348         return this;
349     }
350 
351     // creation API
352 
353     public static AttributeValues fromMap(Map<? extends Attribute, ?> map) {
354         return fromMap(map, MASK_ALL);
355     }
356 
357     public static AttributeValues fromMap(Map<? extends Attribute, ?> map,
358                                           int mask) {
359         return new AttributeValues().merge(map, mask);
360     }
361 
362     public Map<TextAttribute, Object> toMap(Map<TextAttribute, Object> fill) {
363         if (fill == null) {
364             fill = new HashMap<TextAttribute, Object>();
365         }
366 
367         for (int m = defined, i = 0; m != 0; ++i) {
368             EAttribute ea = EAttribute.atts[i];
369             if ((m & ea.mask) != 0) {
370                 m &= ~ea.mask;
371                 fill.put(ea.att, get(ea));
372             }
373         }
374 
375         return fill;
376     }
377 
378     // key must be serializable, so use String, not Object
379     private static final String DEFINED_KEY =
380         "sun.font.attributevalues.defined_key";
381 
382     public static boolean is16Hashtable(Hashtable<Object, Object> ht) {
383         return ht.containsKey(DEFINED_KEY);
384     }
385 
386     public static AttributeValues
387     fromSerializableHashtable(Hashtable<Object, Object> ht)
388     {
389         AttributeValues result = new AttributeValues();
390         if (ht != null && !ht.isEmpty()) {
391             for (Map.Entry<Object, Object> e: ht.entrySet()) {
392                 Object key = e.getKey();
393                 Object val = e.getValue();
394                 if (key.equals(DEFINED_KEY)) {
395                     result.defineAll(((Integer)val).intValue());
396                 } else {
397                     try {
398                         EAttribute ea =
399                             EAttribute.forAttribute((Attribute)key);
400                         if (ea != null) {
401                             result.set(ea, val);
402                         }
403                     }
404                     catch (ClassCastException ex) {
405                     }
406                 }
407             }
408         }
409         return result;
410     }
411 
412     public Hashtable<Object, Object> toSerializableHashtable() {
413         Hashtable ht = new Hashtable();
414         int hashkey = defined;
415         for (int m = defined, i = 0; m != 0; ++i) {
416             EAttribute ea = EAttribute.atts[i];
417             if ((m & ea.mask) != 0) {
418                 m &= ~ea.mask;
419                 Object o = get(ea);
420                 if (o == null) {
421                     // hashkey will handle it
422                 } else if (o instanceof Serializable) { // check all...
423                     ht.put(ea.att, o);
424                 } else {
425                     hashkey &= ~ea.mask;
426                 }
427             }
428         }
429         ht.put(DEFINED_KEY, Integer.valueOf(hashkey));
430 
431         return ht;
432     }
433 
434     // boilerplate
435     public int hashCode() {
436         return defined << 8 ^ nondefault;
437     }
438 
439     public boolean equals(Object rhs) {
440         try {
441             return equals((AttributeValues)rhs);
442         }
443         catch (ClassCastException e) {
444         }
445         return false;
446     }
447 
448     public boolean equals(AttributeValues rhs) {
449         // test in order of most likely to differ and easiest to compare
450         // also assumes we're generally calling this only if family,
451         // size, weight, posture are the same
452 
453         if (rhs == null) return false;
454         if (rhs == this) return true;
455 
456         return defined == rhs.defined
457             && nondefault == rhs.nondefault
458             && underline == rhs.underline
459             && strikethrough == rhs.strikethrough
460             && superscript == rhs.superscript
461             && width == rhs.width
462             && kerning == rhs.kerning
463             && tracking == rhs.tracking
464             && ligatures == rhs.ligatures
465             && runDirection == rhs.runDirection
466             && bidiEmbedding == rhs.bidiEmbedding
467             && swapColors == rhs.swapColors
468             && equals(transform, rhs.transform)
469             && equals(foreground, rhs.foreground)
470             && equals(background, rhs.background)
471             && equals(numericShaping, rhs.numericShaping)
472             && equals(justification, rhs.justification)
473             && equals(charReplacement, rhs.charReplacement)
474             && size == rhs.size
475             && weight == rhs.weight
476             && posture == rhs.posture
477             && equals(family, rhs.family)
478             && equals(font, rhs.font)
479             && imUnderline == rhs.imUnderline
480             && equals(imHighlight, rhs.imHighlight);
481     }
482 
483     public AttributeValues clone() {
484         try {
485             AttributeValues result = (AttributeValues)super.clone();
486             if (transform != null) { // AffineTransform is mutable
487                 result.transform = new AffineTransform(transform);
488                 result.updateDerivedTransforms();
489             }
490             // if transform is null, derived transforms are null
491             // so there's nothing to do
492             return result;
493         }
494         catch (CloneNotSupportedException e) {
495             // never happens
496             return null;
497         }
498     }
499 
500     public String toString() {
501         StringBuilder b = new StringBuilder();
502         b.append('{');
503         for (int m = defined, i = 0; m != 0; ++i) {
504             EAttribute ea = EAttribute.atts[i];
505             if ((m & ea.mask) != 0) {
506                 m &= ~ea.mask;
507                 if (b.length() > 1) {
508                     b.append(", ");
509                 }
510                 b.append(ea);
511                 b.append('=');
512                 switch (ea) {
513                 case EFAMILY: b.append('"');
514                   b.append(family);
515                   b.append('"'); break;
516                 case EWEIGHT: b.append(weight); break;
517                 case EWIDTH: b.append(width); break;
518                 case EPOSTURE: b.append(posture); break;
519                 case ESIZE: b.append(size); break;
520                 case ETRANSFORM: b.append(transform); break;
521                 case ESUPERSCRIPT: b.append(superscript); break;
522                 case EFONT: b.append(font); break;
523                 case ECHAR_REPLACEMENT: b.append(charReplacement); break;
524                 case EFOREGROUND: b.append(foreground); break;
525                 case EBACKGROUND: b.append(background); break;
526                 case EUNDERLINE: b.append(underline); break;
527                 case ESTRIKETHROUGH: b.append(strikethrough); break;
528                 case ERUN_DIRECTION: b.append(runDirection); break;
529                 case EBIDI_EMBEDDING: b.append(bidiEmbedding); break;
530                 case EJUSTIFICATION: b.append(justification); break;
531                 case EINPUT_METHOD_HIGHLIGHT: b.append(imHighlight); break;
532                 case EINPUT_METHOD_UNDERLINE: b.append(imUnderline); break;
533                 case ESWAP_COLORS: b.append(swapColors); break;
534                 case ENUMERIC_SHAPING: b.append(numericShaping); break;
535                 case EKERNING: b.append(kerning); break;
536                 case ELIGATURES: b.append(ligatures); break;
537                 case ETRACKING: b.append(tracking); break;
538                 default: throw new InternalError();
539                 }
540                 if ((nondefault & ea.mask) == 0) {
541                     b.append('*');
542                 }
543             }
544         }
545         b.append("[btx=" + baselineTransform + ", ctx=" + charTransform + "]");
546         b.append('}');
547         return b.toString();
548     }
549 
550     // internal utilities
551 
552     private static boolean equals(Object lhs, Object rhs) {
553         return lhs == null ? rhs == null : lhs.equals(rhs);
554     }
555 
556     private void update(EAttribute a) {
557         defined |= a.mask;
558         if (i_validate(a)) {
559             if (i_equals(a, DEFAULT)) {
560                 nondefault &= ~a.mask;
561             } else {
562                 nondefault |= a.mask;
563             }
564         } else {
565             setDefault(a);
566         }
567     }
568 
569     // dispatch
570 
571     private void i_set(EAttribute a, AttributeValues src) {
572         switch (a) {
573         case EFAMILY: family = src.family; break;
574         case EWEIGHT: weight = src.weight; break;
575         case EWIDTH: width = src.width; break;
576         case EPOSTURE: posture = src.posture; break;
577         case ESIZE: size = src.size; break;
578         case ETRANSFORM: transform = src.transform; updateDerivedTransforms(); break;
579         case ESUPERSCRIPT: superscript = src.superscript; break;
580         case EFONT: font = src.font; break;
581         case ECHAR_REPLACEMENT: charReplacement = src.charReplacement; break;
582         case EFOREGROUND: foreground = src.foreground; break;
583         case EBACKGROUND: background = src.background; break;
584         case EUNDERLINE: underline = src.underline; break;
585         case ESTRIKETHROUGH: strikethrough = src.strikethrough; break;
586         case ERUN_DIRECTION: runDirection = src.runDirection; break;
587         case EBIDI_EMBEDDING: bidiEmbedding = src.bidiEmbedding; break;
588         case EJUSTIFICATION: justification = src.justification; break;
589         case EINPUT_METHOD_HIGHLIGHT: imHighlight = src.imHighlight; break;
590         case EINPUT_METHOD_UNDERLINE: imUnderline = src.imUnderline; break;
591         case ESWAP_COLORS: swapColors = src.swapColors; break;
592         case ENUMERIC_SHAPING: numericShaping = src.numericShaping; break;
593         case EKERNING: kerning = src.kerning; break;
594         case ELIGATURES: ligatures = src.ligatures; break;
595         case ETRACKING: tracking = src.tracking; break;
596         default: throw new InternalError();
597         }
598     }
599 
600     private boolean i_equals(EAttribute a, AttributeValues src) {
601         switch (a) {
602         case EFAMILY: return equals(family, src.family);
603         case EWEIGHT: return weight == src.weight;
604         case EWIDTH: return width == src.width;
605         case EPOSTURE: return posture == src.posture;
606         case ESIZE: return size == src.size;
607         case ETRANSFORM: return equals(transform, src.transform);
608         case ESUPERSCRIPT: return superscript == src.superscript;
609         case EFONT: return equals(font, src.font);
610         case ECHAR_REPLACEMENT: return equals(charReplacement, src.charReplacement);
611         case EFOREGROUND: return equals(foreground, src.foreground);
612         case EBACKGROUND: return equals(background, src.background);
613         case EUNDERLINE: return underline == src.underline;
614         case ESTRIKETHROUGH: return strikethrough == src.strikethrough;
615         case ERUN_DIRECTION: return runDirection == src.runDirection;
616         case EBIDI_EMBEDDING: return bidiEmbedding == src.bidiEmbedding;
617         case EJUSTIFICATION: return justification == src.justification;
618         case EINPUT_METHOD_HIGHLIGHT: return equals(imHighlight, src.imHighlight);
619         case EINPUT_METHOD_UNDERLINE: return imUnderline == src.imUnderline;
620         case ESWAP_COLORS: return swapColors == src.swapColors;
621         case ENUMERIC_SHAPING: return equals(numericShaping, src.numericShaping);
622         case EKERNING: return kerning == src.kerning;
623         case ELIGATURES: return ligatures == src.ligatures;
624         case ETRACKING: return tracking == src.tracking;
625         default: throw new InternalError();
626         }
627     }
628 
629     private void i_set(EAttribute a, Object o) {
630         switch (a) {
631         case EFAMILY: family = ((String)o).trim(); break;
632         case EWEIGHT: weight = ((Number)o).floatValue(); break;
633         case EWIDTH: width = ((Number)o).floatValue(); break;
634         case EPOSTURE: posture = ((Number)o).floatValue(); break;
635         case ESIZE: size = ((Number)o).floatValue(); break;
636         case ETRANSFORM: {
637             if (o instanceof TransformAttribute) {
638                 TransformAttribute ta = (TransformAttribute)o;
639                 if (ta.isIdentity()) {
640                     transform = null;
641                 } else {
642                     transform = ta.getTransform();
643                 }
644             } else {
645                 transform = new AffineTransform((AffineTransform)o);
646             }
647             updateDerivedTransforms();
648         } break;
649         case ESUPERSCRIPT: superscript = (byte)((Integer)o).intValue(); break;
650         case EFONT: font = (Font)o; break;
651         case ECHAR_REPLACEMENT: charReplacement = (GraphicAttribute)o; break;
652         case EFOREGROUND: foreground = (Paint)o; break;
653         case EBACKGROUND: background = (Paint)o; break;
654         case EUNDERLINE: underline = (byte)((Integer)o).intValue(); break;
655         case ESTRIKETHROUGH: strikethrough = ((Boolean)o).booleanValue(); break;
656         case ERUN_DIRECTION: {
657             if (o instanceof Boolean) {
658                 runDirection = (byte)(TextAttribute.RUN_DIRECTION_LTR.equals(o) ? 0 : 1);
659             } else {
660                 runDirection = (byte)((Integer)o).intValue();
661             }
662         } break;
663         case EBIDI_EMBEDDING: bidiEmbedding = (byte)((Integer)o).intValue(); break;
664         case EJUSTIFICATION: justification = ((Number)o).floatValue(); break;
665         case EINPUT_METHOD_HIGHLIGHT: {
666             if (o instanceof Annotation) {
667                 Annotation at = (Annotation)o;
668                 imHighlight = (InputMethodHighlight)at.getValue();
669             } else {
670                 imHighlight = (InputMethodHighlight)o;
671             }
672         } break;
673         case EINPUT_METHOD_UNDERLINE: imUnderline = (byte)((Integer)o).intValue();
674           break;
675         case ESWAP_COLORS: swapColors = ((Boolean)o).booleanValue(); break;
676         case ENUMERIC_SHAPING: numericShaping = (NumericShaper)o; break;
677         case EKERNING: kerning = (byte)((Integer)o).intValue(); break;
678         case ELIGATURES: ligatures = (byte)((Integer)o).intValue(); break;
679         case ETRACKING: tracking = ((Number)o).floatValue(); break;
680         default: throw new InternalError();
681         }
682     }
683 
684     private Object i_get(EAttribute a) {
685         switch (a) {
686         case EFAMILY: return family;
687         case EWEIGHT: return Float.valueOf(weight);
688         case EWIDTH: return Float.valueOf(width);
689         case EPOSTURE: return Float.valueOf(posture);
690         case ESIZE: return Float.valueOf(size);
691         case ETRANSFORM:
692             return transform == null
693                 ? TransformAttribute.IDENTITY
694                 : new TransformAttribute(transform);
695         case ESUPERSCRIPT: return Integer.valueOf(superscript);
696         case EFONT: return font;
697         case ECHAR_REPLACEMENT: return charReplacement;
698         case EFOREGROUND: return foreground;
699         case EBACKGROUND: return background;
700         case EUNDERLINE: return Integer.valueOf(underline);
701         case ESTRIKETHROUGH: return Boolean.valueOf(strikethrough);
702         case ERUN_DIRECTION: {
703             switch (runDirection) {
704                 // todo: figure out a way to indicate this value
705                 // case -1: return Integer.valueOf(runDirection);
706             case 0: return TextAttribute.RUN_DIRECTION_LTR;
707             case 1: return TextAttribute.RUN_DIRECTION_RTL;
708             default: return null;
709             }
710         } // not reachable
711         case EBIDI_EMBEDDING: return Integer.valueOf(bidiEmbedding);
712         case EJUSTIFICATION: return Float.valueOf(justification);
713         case EINPUT_METHOD_HIGHLIGHT: return imHighlight;
714         case EINPUT_METHOD_UNDERLINE: return Integer.valueOf(imUnderline);
715         case ESWAP_COLORS: return Boolean.valueOf(swapColors);
716         case ENUMERIC_SHAPING: return numericShaping;
717         case EKERNING: return Integer.valueOf(kerning);
718         case ELIGATURES: return Integer.valueOf(ligatures);
719         case ETRACKING: return Float.valueOf(tracking);
720         default: throw new InternalError();
721         }
722     }
723 
724     private boolean i_validate(EAttribute a) {
725         switch (a) {
726         case EFAMILY: if (family == null || family.length() == 0)
727           family = DEFAULT.family; return true;
728         case EWEIGHT: return weight > 0 && weight < 10;
729         case EWIDTH: return width >= .5f && width < 10;
730         case EPOSTURE: return posture >= -1 && posture <= 1;
731         case ESIZE: return size >= 0;
732         case ETRANSFORM: if (transform != null && transform.isIdentity())
733             transform = DEFAULT.transform; return true;
734         case ESUPERSCRIPT: return superscript >= -7 && superscript <= 7;
735         case EFONT: return true;
736         case ECHAR_REPLACEMENT: return true;
737         case EFOREGROUND: return true;
738         case EBACKGROUND: return true;
739         case EUNDERLINE: return underline >= -1 && underline < 6;
740         case ESTRIKETHROUGH: return true;
741         case ERUN_DIRECTION: return runDirection >= -2 && runDirection <= 1;
742         case EBIDI_EMBEDDING: return bidiEmbedding >= -61 && bidiEmbedding < 62;
743         case EJUSTIFICATION: justification = max(0, min (justification, 1));
744             return true;
745         case EINPUT_METHOD_HIGHLIGHT: return true;
746         case EINPUT_METHOD_UNDERLINE: return imUnderline >= -1 && imUnderline < 6;
747         case ESWAP_COLORS: return true;
748         case ENUMERIC_SHAPING: return true;
749         case EKERNING: return kerning >= 0 && kerning <= 1;
750         case ELIGATURES: return ligatures >= 0 && ligatures <= 1;
751         case ETRACKING: return tracking >= -1 && tracking <= 10;
752         default: throw new InternalError("unknown attribute: " + a);
753         }
754     }
755 
756     // Until textlayout is fixed to use AttributeValues, we'll end up
757     // creating a map from the values for it.  This is a compromise between
758     // creating the whole map and just checking a particular value.
759     // Plan to remove these.
760     public static float getJustification(Map<?, ?> map) {
761         if (map != null) {
762             if (map instanceof AttributeMap &&
763                 ((AttributeMap) map).getValues() != null) {
764                 return ((AttributeMap)map).getValues().justification;
765             }
766             Object obj = map.get(TextAttribute.JUSTIFICATION);
767             if (obj != null && obj instanceof Number) {
768                 return max(0, min(1, ((Number)obj).floatValue()));
769             }
770         }
771         return DEFAULT.justification;
772     }
773 
774     public static NumericShaper getNumericShaping(Map<?, ?> map) {
775         if (map != null) {
776             if (map instanceof AttributeMap &&
777                 ((AttributeMap) map).getValues() != null) {
778                 return ((AttributeMap)map).getValues().numericShaping;
779             }
780             Object obj = map.get(TextAttribute.NUMERIC_SHAPING);
781             if (obj != null && obj instanceof NumericShaper) {
782                 return (NumericShaper)obj;
783             }
784         }
785         return DEFAULT.numericShaping;
786     }
787 
788     /**
789      * If this has an imHighlight, create copy of this with those attributes
790      * applied to it.  Otherwise return this unchanged.
791      */
792     public AttributeValues applyIMHighlight() {
793         if (imHighlight != null) {
794             InputMethodHighlight hl = null;
795             if (imHighlight instanceof InputMethodHighlight) {
796                 hl = (InputMethodHighlight)imHighlight;
797             } else {
798                 hl = (InputMethodHighlight)((Annotation)imHighlight).getValue();
799             }
800 
801             Map imStyles = hl.getStyle();
802             if (imStyles == null) {
803                 Toolkit tk = Toolkit.getDefaultToolkit();
804                 imStyles = tk.mapInputMethodHighlight(hl);
805             }
806 
807             if (imStyles != null) {
808                 return clone().merge(imStyles);
809             }
810         }
811 
812         return this;
813     }
814 
815     public static AffineTransform getBaselineTransform(Map<?, ?> map) {
816         if (map != null) {
817             AttributeValues av = null;
818             if (map instanceof AttributeMap &&
819                 ((AttributeMap) map).getValues() != null) {
820                 av = ((AttributeMap)map).getValues();
821             } else if (map.get(TextAttribute.TRANSFORM) != null) {
822                 av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck
823             }
824             if (av != null) {
825                 return av.baselineTransform;
826             }
827         }
828         return null;
829     }
830 
831     public static AffineTransform getCharTransform(Map<?, ?> map) {
832         if (map != null) {
833             AttributeValues av = null;
834             if (map instanceof AttributeMap &&
835                 ((AttributeMap) map).getValues() != null) {
836                 av = ((AttributeMap)map).getValues();
837             } else if (map.get(TextAttribute.TRANSFORM) != null) {
838                 av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck
839             }
840             if (av != null) {
841                 return av.charTransform;
842             }
843         }
844         return null;
845     }
846 
847     public void updateDerivedTransforms() {
848         // this also updates the mask for the baseline transform
849         if (transform == null) {
850             baselineTransform = null;
851             charTransform = null;
852         } else {
853             charTransform = new AffineTransform(transform);
854             baselineTransform = extractXRotation(charTransform, true);
855 
856             if (charTransform.isIdentity()) {
857               charTransform = null;
858             }
859 
860             if (baselineTransform.isIdentity()) {
861               baselineTransform = null;
862             }
863         }
864 
865         if (baselineTransform == null) {
866             nondefault &= ~EBASELINE_TRANSFORM.mask;
867         } else {
868             nondefault |= EBASELINE_TRANSFORM.mask;
869         }
870     }
871 
872     public static AffineTransform extractXRotation(AffineTransform tx,
873                                                    boolean andTranslation) {
874         return extractRotation(new Point2D.Double(1, 0), tx, andTranslation);
875     }
876 
877     public static AffineTransform extractYRotation(AffineTransform tx,
878                                                    boolean andTranslation) {
879         return extractRotation(new Point2D.Double(0, 1), tx, andTranslation);
880     }
881 
882     private static AffineTransform extractRotation(Point2D.Double pt,
883         AffineTransform tx, boolean andTranslation) {
884 
885         tx.deltaTransform(pt, pt);
886         AffineTransform rtx = AffineTransform.getRotateInstance(pt.x, pt.y);
887 
888         try {
889             AffineTransform rtxi = rtx.createInverse();
890             double dx = tx.getTranslateX();
891             double dy = tx.getTranslateY();
892             tx.preConcatenate(rtxi);
893             if (andTranslation) {
894                 if (dx != 0 || dy != 0) {
895                     tx.setTransform(tx.getScaleX(), tx.getShearY(),
896                                     tx.getShearX(), tx.getScaleY(), 0, 0);
897                     rtx.setTransform(rtx.getScaleX(), rtx.getShearY(),
898                                      rtx.getShearX(), rtx.getScaleY(), dx, dy);
899                 }
900             }
901         }
902         catch (NoninvertibleTransformException e) {
903             return null;
904         }
905         return rtx;
906     }
907 }