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
37package sun.font;
38
39import static sun.font.EAttribute.*;
40import static java.lang.Math.*;
41
42import java.awt.Font;
43import java.awt.Paint;
44import java.awt.Toolkit;
45import java.awt.font.GraphicAttribute;
46import java.awt.font.NumericShaper;
47import java.awt.font.TextAttribute;
48import java.awt.font.TransformAttribute;
49import java.awt.geom.AffineTransform;
50import java.awt.geom.NoninvertibleTransformException;
51import java.awt.geom.Point2D;
52import java.awt.im.InputMethodHighlight;
53import java.io.Serializable;
54import java.text.Annotation;
55import java.text.AttributedCharacterIterator.Attribute;
56import java.util.Map;
57import java.util.HashMap;
58import java.util.Hashtable;
59
60public 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=" + 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<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}
910