/*
 * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 *
 * (C) Copyright IBM Corp. 2005 - All Rights Reserved
 *
 * The original version of this source code and documentation is
 * copyrighted and owned by IBM. These materials are provided
 * under terms of a License Agreement between IBM and Sun.
 * This technology is protected by multiple US and International
 * patents. This notice and attribution to IBM may not be removed.
 */

package sun.font;

import static sun.font.EAttribute.*;
import static java.lang.Math.*;

import java.awt.Font;
import java.awt.Paint;
import java.awt.Toolkit;
import java.awt.font.GraphicAttribute;
import java.awt.font.NumericShaper;
import java.awt.font.TextAttribute;
import java.awt.font.TransformAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.im.InputMethodHighlight;
import java.io.Serializable;
import java.text.Annotation;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.Map;
import java.util.HashMap;
import java.util.Hashtable;

public final class AttributeValues implements Cloneable {
    private int defined;
    private int nondefault;

    private String family = "Default";
    private float weight = 1f;
    private float width = 1f;
    private float posture; // 0f
    private float size = 12f;
    private float tracking; // 0f
    private NumericShaper numericShaping; // null
    private AffineTransform transform; // null == identity
    private GraphicAttribute charReplacement; // null
    private Paint foreground; // null
    private Paint background; // null
    private float justification = 1f;
    private Object imHighlight; // null
    // (can be either Attribute wrapping IMH, or IMH itself
    private Font font; // here for completeness, don't actually use
    private byte imUnderline = -1; // same default as underline
    private byte superscript; // 0
    private byte underline = -1; // arrgh, value for ON is 0
    private byte runDirection = -2; // BIDI.DIRECTION_DEFAULT_LEFT_TO_RIGHT
    private byte bidiEmbedding; // 0
    private byte kerning; // 0
    private byte ligatures; // 0
    private boolean strikethrough; // false
    private boolean swapColors; // false

    private AffineTransform baselineTransform; // derived from transform
    private AffineTransform charTransform; // derived from transform

    private static final AttributeValues DEFAULT = new AttributeValues();

    // type-specific API
    public String getFamily() { return family; }
    public void setFamily(String f) { this.family = f; update(EFAMILY); }

    public float getWeight() { return weight; }
    public void setWeight(float f) { this.weight = f; update(EWEIGHT); }

    public float getWidth() { return width; }
    public void setWidth(float f) { this.width = f; update(EWIDTH); }

    public float getPosture() { return posture; }
    public void setPosture(float f) { this.posture = f; update(EPOSTURE); }

    public float getSize() { return size; }
    public void setSize(float f) { this.size = f; update(ESIZE); }

    public AffineTransform getTransform() { return transform; }
    public void setTransform(AffineTransform f) {
        this.transform = (f == null || f.isIdentity())
            ? DEFAULT.transform
            : new AffineTransform(f);
        updateDerivedTransforms();
        update(ETRANSFORM);
    }
    public void setTransform(TransformAttribute f) {
        this.transform = (f == null || f.isIdentity())
            ? DEFAULT.transform
            : f.getTransform();
        updateDerivedTransforms();
        update(ETRANSFORM);
    }

    public int getSuperscript() { return superscript; }
    public void setSuperscript(int f) {
      this.superscript = (byte)f; update(ESUPERSCRIPT); }

    public Font getFont() { return font; }
    public void setFont(Font f) { this.font = f; update(EFONT); }

    public GraphicAttribute getCharReplacement() { return charReplacement; }
    public void setCharReplacement(GraphicAttribute f) {
      this.charReplacement = f; update(ECHAR_REPLACEMENT); }

    public Paint getForeground() { return foreground; }
    public void setForeground(Paint f) {
      this.foreground = f; update(EFOREGROUND); }

    public Paint getBackground() { return background; }
    public void setBackground(Paint f) {
      this.background = f; update(EBACKGROUND); }

    public int getUnderline() { return underline; }
    public void setUnderline(int f) {
      this.underline = (byte)f; update(EUNDERLINE); }

    public boolean getStrikethrough() { return strikethrough; }
    public void setStrikethrough(boolean f) {
      this.strikethrough = f; update(ESTRIKETHROUGH); }

    public int getRunDirection() { return runDirection; }
    public void setRunDirection(int f) {
      this.runDirection = (byte)f; update(ERUN_DIRECTION); }

    public int getBidiEmbedding() { return bidiEmbedding; }
    public void setBidiEmbedding(int f) {
      this.bidiEmbedding = (byte)f; update(EBIDI_EMBEDDING); }

    public float getJustification() { return justification; }
    public void setJustification(float f) {
      this.justification = f; update(EJUSTIFICATION); }

    public Object getInputMethodHighlight() { return imHighlight; }
    public void setInputMethodHighlight(Annotation f) {
      this.imHighlight = f; update(EINPUT_METHOD_HIGHLIGHT); }
    public void setInputMethodHighlight(InputMethodHighlight f) {
      this.imHighlight = f; update(EINPUT_METHOD_HIGHLIGHT); }

    public int getInputMethodUnderline() { return imUnderline; }
    public void setInputMethodUnderline(int f) {
      this.imUnderline = (byte)f; update(EINPUT_METHOD_UNDERLINE); }

    public boolean getSwapColors() { return swapColors; }
    public void setSwapColors(boolean f) {
      this.swapColors = f; update(ESWAP_COLORS); }

    public NumericShaper getNumericShaping() { return numericShaping; }
    public void setNumericShaping(NumericShaper f) {
      this.numericShaping = f; update(ENUMERIC_SHAPING); }

    public int getKerning() { return kerning; }
    public void setKerning(int f) {
      this.kerning = (byte)f; update(EKERNING); }

    public float getTracking() { return tracking; }
    public void setTracking(float f) {
      this.tracking = (byte)f; update(ETRACKING); }

    public int getLigatures() { return ligatures; }
    public void setLigatures(int f) {
      this.ligatures = (byte)f; update(ELIGATURES); }


    public AffineTransform getBaselineTransform() { return baselineTransform; }
    public AffineTransform getCharTransform() { return charTransform; }

    // mask api

    public static int getMask(EAttribute att) {
        return att.mask;
    }

    public static int getMask(EAttribute ... atts) {
        int mask = 0;
        for (EAttribute a: atts) {
            mask |= a.mask;
        }
        return mask;
    }

    public static final int MASK_ALL =
        getMask(EAttribute.class.getEnumConstants());

    public void unsetDefault() {
        defined &= nondefault;
    }

    public void defineAll(int mask) {
        defined |= mask;
        if ((defined & EBASELINE_TRANSFORM.mask) != 0) {
            throw new InternalError("can't define derived attribute");
        }
    }

    public boolean allDefined(int mask) {
        return (defined & mask) == mask;
    }

    public boolean anyDefined(int mask) {
        return (defined & mask) != 0;
    }

    public boolean anyNonDefault(int mask) {
        return (nondefault & mask) != 0;
    }

    // generic EAttribute API

    public boolean isDefined(EAttribute a) {
        return (defined & a.mask) != 0;
    }

    public boolean isNonDefault(EAttribute a) {
        return (nondefault & a.mask) != 0;
    }

    public void setDefault(EAttribute a) {
        if (a.att == null) {
            throw new InternalError("can't set default derived attribute: " + a);
        }
        i_set(a, DEFAULT);
        defined |= a.mask;
        nondefault &= ~a.mask;
    }

    public void unset(EAttribute a) {
        if (a.att == null) {
            throw new InternalError("can't unset derived attribute: " + a);
        }
        i_set(a, DEFAULT);
        defined &= ~a.mask;
        nondefault &= ~a.mask;
    }

    public void set(EAttribute a, AttributeValues src) {
        if (a.att == null) {
            throw new InternalError("can't set derived attribute: " + a);
        }
        if (src == null || src == DEFAULT) {
            setDefault(a);
        } else {
            if ((src.defined & a.mask) != 0) {
                i_set(a, src);
                update(a);
            }
        }
    }

    public void set(EAttribute a, Object o) {
        if (a.att == null) {
            throw new InternalError("can't set derived attribute: " + a);
        }
        if (o != null) {
            try {
                i_set(a, o);
                update(a);
                return;
            } catch (Exception e) {
            }
        }
        setDefault(a);
    }

    public Object get(EAttribute a) {
        if (a.att == null) {
            throw new InternalError("can't get derived attribute: " + a);
        }
        if ((nondefault & a.mask) != 0) {
            return i_get(a);
        }
        return null;
    }

    // merging

    public AttributeValues merge(Map<? extends Attribute, ?>map) {
        return merge(map, MASK_ALL);
    }

    public AttributeValues merge(Map<? extends Attribute, ?>map,
                                 int mask) {
        if (map instanceof AttributeMap &&
            ((AttributeMap) map).getValues() != null) {
            merge(((AttributeMap)map).getValues(), mask);
        } else if (map != null && !map.isEmpty()) {
            for (Map.Entry<? extends Attribute, ?> e: map.entrySet()) {
                try {
                    EAttribute ea = EAttribute.forAttribute(e.getKey());
                    if (ea!= null && (mask & ea.mask) != 0) {
                        set(ea, e.getValue());
                    }
                } catch (ClassCastException cce) {
                    // IGNORED
                }
            }
        }
        return this;
    }

    public AttributeValues merge(AttributeValues src) {
        return merge(src, MASK_ALL);
    }

    public AttributeValues merge(AttributeValues src, int mask) {
        int m = mask & src.defined;
        for (EAttribute ea: EAttribute.atts) {
            if (m == 0) {
                break;
            }
            if ((m & ea.mask) != 0) {
                m &= ~ea.mask;
                i_set(ea, src);
                update(ea);
            }
        }
        return this;
    }

    // creation API

    public static AttributeValues fromMap(Map<? extends Attribute, ?> map) {
        return fromMap(map, MASK_ALL);
    }

    public static AttributeValues fromMap(Map<? extends Attribute, ?> map,
                                          int mask) {
        return new AttributeValues().merge(map, mask);
    }

    public Map<TextAttribute, Object> toMap(Map<TextAttribute, Object> fill) {
        if (fill == null) {
            fill = new HashMap<TextAttribute, Object>();
        }

        for (int m = defined, i = 0; m != 0; ++i) {
            EAttribute ea = EAttribute.atts[i];
            if ((m & ea.mask) != 0) {
                m &= ~ea.mask;
                fill.put(ea.att, get(ea));
            }
        }

        return fill;
    }

    // key must be serializable, so use String, not Object
    private static final String DEFINED_KEY =
        "sun.font.attributevalues.defined_key";

    public static boolean is16Hashtable(Hashtable<Object, Object> ht) {
        return ht.containsKey(DEFINED_KEY);
    }

    public static AttributeValues
    fromSerializableHashtable(Hashtable<Object, Object> ht)
    {
        AttributeValues result = new AttributeValues();
        if (ht != null && !ht.isEmpty()) {
            for (Map.Entry<Object, Object> e: ht.entrySet()) {
                Object key = e.getKey();
                Object val = e.getValue();
                if (key.equals(DEFINED_KEY)) {
                    result.defineAll(((Integer)val).intValue());
                } else {
                    try {
                        EAttribute ea =
                            EAttribute.forAttribute((Attribute)key);
                        if (ea != null) {
                            result.set(ea, val);
                        }
                    }
                    catch (ClassCastException ex) {
                    }
                }
            }
        }
        return result;
    }

    public Hashtable<Object, Object> toSerializableHashtable() {
        Hashtable ht = new Hashtable();
        int hashkey = defined;
        for (int m = defined, i = 0; m != 0; ++i) {
            EAttribute ea = EAttribute.atts[i];
            if ((m & ea.mask) != 0) {
                m &= ~ea.mask;
                Object o = get(ea);
                if (o == null) {
                    // hashkey will handle it
                } else if (o instanceof Serializable) { // check all...
                    ht.put(ea.att, o);
                } else {
                    hashkey &= ~ea.mask;
                }
            }
        }
        ht.put(DEFINED_KEY, Integer.valueOf(hashkey));

        return ht;
    }

    // boilerplate
    public int hashCode() {
        return defined << 8 ^ nondefault;
    }

    public boolean equals(Object rhs) {
        try {
            return equals((AttributeValues)rhs);
        }
        catch (ClassCastException e) {
        }
        return false;
    }

    public boolean equals(AttributeValues rhs) {
        // test in order of most likely to differ and easiest to compare
        // also assumes we're generally calling this only if family,
        // size, weight, posture are the same

        if (rhs == null) return false;
        if (rhs == this) return true;

        return defined == rhs.defined
            && nondefault == rhs.nondefault
            && underline == rhs.underline
            && strikethrough == rhs.strikethrough
            && superscript == rhs.superscript
            && width == rhs.width
            && kerning == rhs.kerning
            && tracking == rhs.tracking
            && ligatures == rhs.ligatures
            && runDirection == rhs.runDirection
            && bidiEmbedding == rhs.bidiEmbedding
            && swapColors == rhs.swapColors
            && equals(transform, rhs.transform)
            && equals(foreground, rhs.foreground)
            && equals(background, rhs.background)
            && equals(numericShaping, rhs.numericShaping)
            && equals(justification, rhs.justification)
            && equals(charReplacement, rhs.charReplacement)
            && size == rhs.size
            && weight == rhs.weight
            && posture == rhs.posture
            && equals(family, rhs.family)
            && equals(font, rhs.font)
            && imUnderline == rhs.imUnderline
            && equals(imHighlight, rhs.imHighlight);
    }

    public AttributeValues clone() {
        try {
            AttributeValues result = (AttributeValues)super.clone();
            if (transform != null) { // AffineTransform is mutable
                result.transform = new AffineTransform(transform);
                result.updateDerivedTransforms();
            }
            // if transform is null, derived transforms are null
            // so there's nothing to do
            return result;
        }
        catch (CloneNotSupportedException e) {
            // never happens
            return null;
        }
    }

    public String toString() {
        StringBuilder b = new StringBuilder();
        b.append('{');
        for (int m = defined, i = 0; m != 0; ++i) {
            EAttribute ea = EAttribute.atts[i];
            if ((m & ea.mask) != 0) {
                m &= ~ea.mask;
                if (b.length() > 1) {
                    b.append(", ");
                }
                b.append(ea);
                b.append('=');
                switch (ea) {
                case EFAMILY: b.append('"');
                  b.append(family);
                  b.append('"'); break;
                case EWEIGHT: b.append(weight); break;
                case EWIDTH: b.append(width); break;
                case EPOSTURE: b.append(posture); break;
                case ESIZE: b.append(size); break;
                case ETRANSFORM: b.append(transform); break;
                case ESUPERSCRIPT: b.append(superscript); break;
                case EFONT: b.append(font); break;
                case ECHAR_REPLACEMENT: b.append(charReplacement); break;
                case EFOREGROUND: b.append(foreground); break;
                case EBACKGROUND: b.append(background); break;
                case EUNDERLINE: b.append(underline); break;
                case ESTRIKETHROUGH: b.append(strikethrough); break;
                case ERUN_DIRECTION: b.append(runDirection); break;
                case EBIDI_EMBEDDING: b.append(bidiEmbedding); break;
                case EJUSTIFICATION: b.append(justification); break;
                case EINPUT_METHOD_HIGHLIGHT: b.append(imHighlight); break;
                case EINPUT_METHOD_UNDERLINE: b.append(imUnderline); break;
                case ESWAP_COLORS: b.append(swapColors); break;
                case ENUMERIC_SHAPING: b.append(numericShaping); break;
                case EKERNING: b.append(kerning); break;
                case ELIGATURES: b.append(ligatures); break;
                case ETRACKING: b.append(tracking); break;
                default: throw new InternalError();
                }
                if ((nondefault & ea.mask) == 0) {
                    b.append('*');
                }
            }
        }
        b.append("[btx=" + baselineTransform + ", ctx=" + charTransform + "]");
        b.append('}');
        return b.toString();
    }

    // internal utilities

    private static boolean equals(Object lhs, Object rhs) {
        return lhs == null ? rhs == null : lhs.equals(rhs);
    }

    private void update(EAttribute a) {
        defined |= a.mask;
        if (i_validate(a)) {
            if (i_equals(a, DEFAULT)) {
                nondefault &= ~a.mask;
            } else {
                nondefault |= a.mask;
            }
        } else {
            setDefault(a);
        }
    }

    // dispatch

    private void i_set(EAttribute a, AttributeValues src) {
        switch (a) {
        case EFAMILY: family = src.family; break;
        case EWEIGHT: weight = src.weight; break;
        case EWIDTH: width = src.width; break;
        case EPOSTURE: posture = src.posture; break;
        case ESIZE: size = src.size; break;
        case ETRANSFORM: transform = src.transform; updateDerivedTransforms(); break;
        case ESUPERSCRIPT: superscript = src.superscript; break;
        case EFONT: font = src.font; break;
        case ECHAR_REPLACEMENT: charReplacement = src.charReplacement; break;
        case EFOREGROUND: foreground = src.foreground; break;
        case EBACKGROUND: background = src.background; break;
        case EUNDERLINE: underline = src.underline; break;
        case ESTRIKETHROUGH: strikethrough = src.strikethrough; break;
        case ERUN_DIRECTION: runDirection = src.runDirection; break;
        case EBIDI_EMBEDDING: bidiEmbedding = src.bidiEmbedding; break;
        case EJUSTIFICATION: justification = src.justification; break;
        case EINPUT_METHOD_HIGHLIGHT: imHighlight = src.imHighlight; break;
        case EINPUT_METHOD_UNDERLINE: imUnderline = src.imUnderline; break;
        case ESWAP_COLORS: swapColors = src.swapColors; break;
        case ENUMERIC_SHAPING: numericShaping = src.numericShaping; break;
        case EKERNING: kerning = src.kerning; break;
        case ELIGATURES: ligatures = src.ligatures; break;
        case ETRACKING: tracking = src.tracking; break;
        default: throw new InternalError();
        }
    }

    private boolean i_equals(EAttribute a, AttributeValues src) {
        switch (a) {
        case EFAMILY: return equals(family, src.family);
        case EWEIGHT: return weight == src.weight;
        case EWIDTH: return width == src.width;
        case EPOSTURE: return posture == src.posture;
        case ESIZE: return size == src.size;
        case ETRANSFORM: return equals(transform, src.transform);
        case ESUPERSCRIPT: return superscript == src.superscript;
        case EFONT: return equals(font, src.font);
        case ECHAR_REPLACEMENT: return equals(charReplacement, src.charReplacement);
        case EFOREGROUND: return equals(foreground, src.foreground);
        case EBACKGROUND: return equals(background, src.background);
        case EUNDERLINE: return underline == src.underline;
        case ESTRIKETHROUGH: return strikethrough == src.strikethrough;
        case ERUN_DIRECTION: return runDirection == src.runDirection;
        case EBIDI_EMBEDDING: return bidiEmbedding == src.bidiEmbedding;
        case EJUSTIFICATION: return justification == src.justification;
        case EINPUT_METHOD_HIGHLIGHT: return equals(imHighlight, src.imHighlight);
        case EINPUT_METHOD_UNDERLINE: return imUnderline == src.imUnderline;
        case ESWAP_COLORS: return swapColors == src.swapColors;
        case ENUMERIC_SHAPING: return equals(numericShaping, src.numericShaping);
        case EKERNING: return kerning == src.kerning;
        case ELIGATURES: return ligatures == src.ligatures;
        case ETRACKING: return tracking == src.tracking;
        default: throw new InternalError();
        }
    }

    private void i_set(EAttribute a, Object o) {
        switch (a) {
        case EFAMILY: family = ((String)o).trim(); break;
        case EWEIGHT: weight = ((Number)o).floatValue(); break;
        case EWIDTH: width = ((Number)o).floatValue(); break;
        case EPOSTURE: posture = ((Number)o).floatValue(); break;
        case ESIZE: size = ((Number)o).floatValue(); break;
        case ETRANSFORM: {
            if (o instanceof TransformAttribute) {
                TransformAttribute ta = (TransformAttribute)o;
                if (ta.isIdentity()) {
                    transform = null;
                } else {
                    transform = ta.getTransform();
                }
            } else {
                transform = new AffineTransform((AffineTransform)o);
            }
            updateDerivedTransforms();
        } break;
        case ESUPERSCRIPT: superscript = (byte)((Integer)o).intValue(); break;
        case EFONT: font = (Font)o; break;
        case ECHAR_REPLACEMENT: charReplacement = (GraphicAttribute)o; break;
        case EFOREGROUND: foreground = (Paint)o; break;
        case EBACKGROUND: background = (Paint)o; break;
        case EUNDERLINE: underline = (byte)((Integer)o).intValue(); break;
        case ESTRIKETHROUGH: strikethrough = ((Boolean)o).booleanValue(); break;
        case ERUN_DIRECTION: {
            if (o instanceof Boolean) {
                runDirection = (byte)(TextAttribute.RUN_DIRECTION_LTR.equals(o) ? 0 : 1);
            } else {
                runDirection = (byte)((Integer)o).intValue();
            }
        } break;
        case EBIDI_EMBEDDING: bidiEmbedding = (byte)((Integer)o).intValue(); break;
        case EJUSTIFICATION: justification = ((Number)o).floatValue(); break;
        case EINPUT_METHOD_HIGHLIGHT: {
            if (o instanceof Annotation) {
                Annotation at = (Annotation)o;
                imHighlight = (InputMethodHighlight)at.getValue();
            } else {
                imHighlight = (InputMethodHighlight)o;
            }
        } break;
        case EINPUT_METHOD_UNDERLINE: imUnderline = (byte)((Integer)o).intValue();
          break;
        case ESWAP_COLORS: swapColors = ((Boolean)o).booleanValue(); break;
        case ENUMERIC_SHAPING: numericShaping = (NumericShaper)o; break;
        case EKERNING: kerning = (byte)((Integer)o).intValue(); break;
        case ELIGATURES: ligatures = (byte)((Integer)o).intValue(); break;
        case ETRACKING: tracking = ((Number)o).floatValue(); break;
        default: throw new InternalError();
        }
    }

    private Object i_get(EAttribute a) {
        switch (a) {
        case EFAMILY: return family;
        case EWEIGHT: return Float.valueOf(weight);
        case EWIDTH: return Float.valueOf(width);
        case EPOSTURE: return Float.valueOf(posture);
        case ESIZE: return Float.valueOf(size);
        case ETRANSFORM:
            return transform == null
                ? TransformAttribute.IDENTITY
                : new TransformAttribute(transform);
        case ESUPERSCRIPT: return Integer.valueOf(superscript);
        case EFONT: return font;
        case ECHAR_REPLACEMENT: return charReplacement;
        case EFOREGROUND: return foreground;
        case EBACKGROUND: return background;
        case EUNDERLINE: return Integer.valueOf(underline);
        case ESTRIKETHROUGH: return Boolean.valueOf(strikethrough);
        case ERUN_DIRECTION: {
            switch (runDirection) {
                // todo: figure out a way to indicate this value
                // case -1: return Integer.valueOf(runDirection);
            case 0: return TextAttribute.RUN_DIRECTION_LTR;
            case 1: return TextAttribute.RUN_DIRECTION_RTL;
            default: return null;
            }
        } // not reachable
        case EBIDI_EMBEDDING: return Integer.valueOf(bidiEmbedding);
        case EJUSTIFICATION: return Float.valueOf(justification);
        case EINPUT_METHOD_HIGHLIGHT: return imHighlight;
        case EINPUT_METHOD_UNDERLINE: return Integer.valueOf(imUnderline);
        case ESWAP_COLORS: return Boolean.valueOf(swapColors);
        case ENUMERIC_SHAPING: return numericShaping;
        case EKERNING: return Integer.valueOf(kerning);
        case ELIGATURES: return Integer.valueOf(ligatures);
        case ETRACKING: return Float.valueOf(tracking);
        default: throw new InternalError();
        }
    }

    private boolean i_validate(EAttribute a) {
        switch (a) {
        case EFAMILY: if (family == null || family.length() == 0)
          family = DEFAULT.family; return true;
        case EWEIGHT: return weight > 0 && weight < 10;
        case EWIDTH: return width >= .5f && width < 10;
        case EPOSTURE: return posture >= -1 && posture <= 1;
        case ESIZE: return size >= 0;
        case ETRANSFORM: if (transform != null && transform.isIdentity())
            transform = DEFAULT.transform; return true;
        case ESUPERSCRIPT: return superscript >= -7 && superscript <= 7;
        case EFONT: return true;
        case ECHAR_REPLACEMENT: return true;
        case EFOREGROUND: return true;
        case EBACKGROUND: return true;
        case EUNDERLINE: return underline >= -1 && underline < 6;
        case ESTRIKETHROUGH: return true;
        case ERUN_DIRECTION: return runDirection >= -2 && runDirection <= 1;
        case EBIDI_EMBEDDING: return bidiEmbedding >= -61 && bidiEmbedding < 62;
        case EJUSTIFICATION: justification = max(0, min (justification, 1));
            return true;
        case EINPUT_METHOD_HIGHLIGHT: return true;
        case EINPUT_METHOD_UNDERLINE: return imUnderline >= -1 && imUnderline < 6;
        case ESWAP_COLORS: return true;
        case ENUMERIC_SHAPING: return true;
        case EKERNING: return kerning >= 0 && kerning <= 1;
        case ELIGATURES: return ligatures >= 0 && ligatures <= 1;
        case ETRACKING: return tracking >= -1 && tracking <= 10;
        default: throw new InternalError("unknown attribute: " + a);
        }
    }

    // Until textlayout is fixed to use AttributeValues, we'll end up
    // creating a map from the values for it.  This is a compromise between
    // creating the whole map and just checking a particular value.
    // Plan to remove these.
    public static float getJustification(Map<?, ?> map) {
        if (map != null) {
            if (map instanceof AttributeMap &&
                ((AttributeMap) map).getValues() != null) {
                return ((AttributeMap)map).getValues().justification;
            }
            Object obj = map.get(TextAttribute.JUSTIFICATION);
            if (obj != null && obj instanceof Number) {
                return max(0, min(1, ((Number)obj).floatValue()));
            }
        }
        return DEFAULT.justification;
    }

    public static NumericShaper getNumericShaping(Map<?, ?> map) {
        if (map != null) {
            if (map instanceof AttributeMap &&
                ((AttributeMap) map).getValues() != null) {
                return ((AttributeMap)map).getValues().numericShaping;
            }
            Object obj = map.get(TextAttribute.NUMERIC_SHAPING);
            if (obj != null && obj instanceof NumericShaper) {
                return (NumericShaper)obj;
            }
        }
        return DEFAULT.numericShaping;
    }

    
If this has an imHighlight, create copy of this with those attributes applied to it. Otherwise return this unchanged.
/** * If this has an imHighlight, create copy of this with those attributes * applied to it. Otherwise return this unchanged. */
public AttributeValues applyIMHighlight() { if (imHighlight != null) { InputMethodHighlight hl = null; if (imHighlight instanceof InputMethodHighlight) { hl = (InputMethodHighlight)imHighlight; } else { hl = (InputMethodHighlight)((Annotation)imHighlight).getValue(); } Map imStyles = hl.getStyle(); if (imStyles == null) { Toolkit tk = Toolkit.getDefaultToolkit(); imStyles = tk.mapInputMethodHighlight(hl); } if (imStyles != null) { return clone().merge(imStyles); } } return this; } public static AffineTransform getBaselineTransform(Map<?, ?> map) { if (map != null) { AttributeValues av = null; if (map instanceof AttributeMap && ((AttributeMap) map).getValues() != null) { av = ((AttributeMap)map).getValues(); } else if (map.get(TextAttribute.TRANSFORM) != null) { av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck } if (av != null) { return av.baselineTransform; } } return null; } public static AffineTransform getCharTransform(Map<?, ?> map) { if (map != null) { AttributeValues av = null; if (map instanceof AttributeMap && ((AttributeMap) map).getValues() != null) { av = ((AttributeMap)map).getValues(); } else if (map.get(TextAttribute.TRANSFORM) != null) { av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck } if (av != null) { return av.charTransform; } } return null; } public void updateDerivedTransforms() { // this also updates the mask for the baseline transform if (transform == null) { baselineTransform = null; charTransform = null; } else { charTransform = new AffineTransform(transform); baselineTransform = extractXRotation(charTransform, true); if (charTransform.isIdentity()) { charTransform = null; } if (baselineTransform.isIdentity()) { baselineTransform = null; } } if (baselineTransform == null) { nondefault &= ~EBASELINE_TRANSFORM.mask; } else { nondefault |= EBASELINE_TRANSFORM.mask; } } public static AffineTransform extractXRotation(AffineTransform tx, boolean andTranslation) { return extractRotation(new Point2D.Double(1, 0), tx, andTranslation); } public static AffineTransform extractYRotation(AffineTransform tx, boolean andTranslation) { return extractRotation(new Point2D.Double(0, 1), tx, andTranslation); } private static AffineTransform extractRotation(Point2D.Double pt, AffineTransform tx, boolean andTranslation) { tx.deltaTransform(pt, pt); AffineTransform rtx = AffineTransform.getRotateInstance(pt.x, pt.y); try { AffineTransform rtxi = rtx.createInverse(); double dx = tx.getTranslateX(); double dy = tx.getTranslateY(); tx.preConcatenate(rtxi); if (andTranslation) { if (dx != 0 || dy != 0) { tx.setTransform(tx.getScaleX(), tx.getShearY(), tx.getShearX(), tx.getScaleY(), 0, 0); rtx.setTransform(rtx.getScaleX(), rtx.getShearY(), rtx.getShearX(), rtx.getScaleY(), dx, dy); } } } catch (NoninvertibleTransformException e) { return null; } return rtx; } }