1 /*
   2  * Copyright (c) 2004, 2014, 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<Object, Object> 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=").append(baselineTransform).append(", ctx=").append(charTransform).append(']');
 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<TextAttribute, ?> 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     @SuppressWarnings("unchecked")
 816     public static AffineTransform getBaselineTransform(Map<?, ?> map) {
 817         if (map != null) {
 818             AttributeValues av = null;
 819             if (map instanceof AttributeMap &&
 820                 ((AttributeMap) map).getValues() != null) {
 821                 av = ((AttributeMap)map).getValues();
 822             } else if (map.get(TextAttribute.TRANSFORM) != null) {
 823                 av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck
 824             }
 825             if (av != null) {
 826                 return av.baselineTransform;
 827             }
 828         }
 829         return null;
 830     }
 831 
 832     @SuppressWarnings("unchecked")
 833     public static AffineTransform getCharTransform(Map<?, ?> map) {
 834         if (map != null) {
 835             AttributeValues av = null;
 836             if (map instanceof AttributeMap &&
 837                 ((AttributeMap) map).getValues() != null) {
 838                 av = ((AttributeMap)map).getValues();
 839             } else if (map.get(TextAttribute.TRANSFORM) != null) {
 840                 av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck
 841             }
 842             if (av != null) {
 843                 return av.charTransform;
 844             }
 845         }
 846         return null;
 847     }
 848 
 849     public void updateDerivedTransforms() {
 850         // this also updates the mask for the baseline transform
 851         if (transform == null) {
 852             baselineTransform = null;
 853             charTransform = null;
 854         } else {
 855             charTransform = new AffineTransform(transform);
 856             baselineTransform = extractXRotation(charTransform, true);
 857 
 858             if (charTransform.isIdentity()) {
 859               charTransform = null;
 860             }
 861 
 862             if (baselineTransform.isIdentity()) {
 863               baselineTransform = null;
 864             }
 865         }
 866 
 867         if (baselineTransform == null) {
 868             nondefault &= ~EBASELINE_TRANSFORM.mask;
 869         } else {
 870             nondefault |= EBASELINE_TRANSFORM.mask;
 871         }
 872     }
 873 
 874     public static AffineTransform extractXRotation(AffineTransform tx,
 875                                                    boolean andTranslation) {
 876         return extractRotation(new Point2D.Double(1, 0), tx, andTranslation);
 877     }
 878 
 879     public static AffineTransform extractYRotation(AffineTransform tx,
 880                                                    boolean andTranslation) {
 881         return extractRotation(new Point2D.Double(0, 1), tx, andTranslation);
 882     }
 883 
 884     private static AffineTransform extractRotation(Point2D.Double pt,
 885         AffineTransform tx, boolean andTranslation) {
 886 
 887         tx.deltaTransform(pt, pt);
 888         AffineTransform rtx = AffineTransform.getRotateInstance(pt.x, pt.y);
 889 
 890         try {
 891             AffineTransform rtxi = rtx.createInverse();
 892             double dx = tx.getTranslateX();
 893             double dy = tx.getTranslateY();
 894             tx.preConcatenate(rtxi);
 895             if (andTranslation) {
 896                 if (dx != 0 || dy != 0) {
 897                     tx.setTransform(tx.getScaleX(), tx.getShearY(),
 898                                     tx.getShearX(), tx.getScaleY(), 0, 0);
 899                     rtx.setTransform(rtx.getScaleX(), rtx.getShearY(),
 900                                      rtx.getShearX(), rtx.getScaleY(), dx, dy);
 901                 }
 902             }
 903         }
 904         catch (NoninvertibleTransformException e) {
 905             return null;
 906         }
 907         return rtx;
 908     }
 909 }