1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text.style;
18 
19 import android.annotation.NonNull;
20 import android.content.res.Configuration;
21 import android.graphics.Paint;
22 import android.graphics.Typeface;
23 import android.graphics.fonts.FontStyle;
24 import android.os.Parcel;
25 import android.text.ParcelableSpan;
26 import android.text.TextPaint;
27 import android.text.TextUtils;
28 
29 /**
30  * Span that allows setting the style of the text it's attached to.
31  * Possible styles are: {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC} and
32  * {@link Typeface#BOLD_ITALIC}.
33  * <p>
34  * Note that styles are cumulative -- if both bold and italic are set in
35  * separate spans, or if the base style is bold and a span calls for italic,
36  * you get bold italic.  You can't turn off a style from the base style.
37  * <p>
38  * For example, the <code>StyleSpan</code> can be used like this:
39  * <pre>
40  * SpannableString string = new SpannableString("Bold and italic text");
41  * string.setSpan(new StyleSpan(Typeface.BOLD), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
42  * string.setSpan(new StyleSpan(Typeface.ITALIC), 9, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
43  * </pre>
44  * <img src="{@docRoot}reference/android/images/text/style/stylespan.png" />
45  * <figcaption>Text styled bold and italic with the <code>StyleSpan</code>.</figcaption>
46  */
47 public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
48 
49     private final int mStyle;
50     private final int mFontWeightAdjustment;
51 
52     /**
53      * Creates a {@link StyleSpan} from a style.
54      *
55      * @param style An integer constant describing the style for this span. Examples
56      *              include bold, italic, and normal. Values are constants defined
57      *              in {@link Typeface}.
58      */
StyleSpan(int style)59     public StyleSpan(int style) {
60         this(style, Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED);
61     }
62 
63     /**
64      * Creates a {@link StyleSpan} from a style and font weight adjustment.
65      *
66      * @param style An integer constant describing the style for this span. Examples
67      *              include bold, italic, and normal. Values are constants defined
68      *              in {@link Typeface}.
69      * @param fontWeightAdjustment An integer describing the adjustment to be made to the font
70      *              weight.
71      * @see Configuration#fontWeightAdjustment This is the adjustment in text font weight
72      * that is used to reflect the current user's preference for increasing font weight.
73      * @hide
74      */
StyleSpan(@ypeface.Style int style, int fontWeightAdjustment)75     public StyleSpan(@Typeface.Style int style, int fontWeightAdjustment) {
76         mStyle = style;
77         mFontWeightAdjustment = fontWeightAdjustment;
78     }
79 
80     /**
81      * Creates a {@link StyleSpan} from a parcel.
82      *
83      * @param src the parcel
84      */
StyleSpan(@onNull Parcel src)85     public StyleSpan(@NonNull Parcel src) {
86         mStyle = src.readInt();
87         mFontWeightAdjustment = src.readInt();
88     }
89 
90     @Override
getSpanTypeId()91     public int getSpanTypeId() {
92         return getSpanTypeIdInternal();
93     }
94 
95     /** @hide */
96     @Override
getSpanTypeIdInternal()97     public int getSpanTypeIdInternal() {
98         return TextUtils.STYLE_SPAN;
99     }
100 
101     @Override
describeContents()102     public int describeContents() {
103         return 0;
104     }
105 
106     @Override
writeToParcel(Parcel dest, int flags)107     public void writeToParcel(Parcel dest, int flags) {
108         writeToParcelInternal(dest, flags);
109     }
110 
111     /** @hide */
112     @Override
writeToParcelInternal(@onNull Parcel dest, int flags)113     public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
114         dest.writeInt(mStyle);
115         dest.writeInt(mFontWeightAdjustment);
116     }
117 
118     /**
119      * Returns the style constant defined in {@link Typeface}.
120      */
getStyle()121     public int getStyle() {
122         return mStyle;
123     }
124 
125     /**
126      * Returns the font weight adjustment specified by this span.
127      * @hide
128      */
getFontWeightAdjustment()129     public int getFontWeightAdjustment() {
130         return mFontWeightAdjustment;
131     }
132 
133     @Override
updateDrawState(TextPaint ds)134     public void updateDrawState(TextPaint ds) {
135         apply(ds, mStyle, mFontWeightAdjustment);
136     }
137 
138     @Override
updateMeasureState(TextPaint paint)139     public void updateMeasureState(TextPaint paint) {
140         apply(paint, mStyle, mFontWeightAdjustment);
141     }
142 
apply(Paint paint, int style, int fontWeightAdjustment)143     private static void apply(Paint paint, int style, int fontWeightAdjustment) {
144         int oldStyle;
145 
146         Typeface old = paint.getTypeface();
147         if (old == null) {
148             oldStyle = 0;
149         } else {
150             oldStyle = old.getStyle();
151         }
152 
153         int want = oldStyle | style;
154 
155         Typeface tf;
156         if (old == null) {
157             tf = Typeface.defaultFromStyle(want);
158         } else {
159             tf = Typeface.create(old, want);
160         }
161 
162         // Base typeface may already be bolded by auto bold. Bold further.
163         if ((style & Typeface.BOLD) != 0) {
164             if (fontWeightAdjustment != 0
165                     && fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
166                 int newWeight = Math.min(
167                         Math.max(tf.getWeight() + fontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
168                         FontStyle.FONT_WEIGHT_MAX);
169                 boolean italic = (want & Typeface.ITALIC) != 0;
170                 tf = Typeface.create(tf, newWeight, italic);
171             }
172         }
173 
174         int fake = want & ~tf.getStyle();
175 
176         if ((fake & Typeface.BOLD) != 0) {
177             paint.setFakeBoldText(true);
178         }
179 
180         if ((fake & Typeface.ITALIC) != 0) {
181             paint.setTextSkewX(-0.25f);
182         }
183 
184         paint.setTypeface(tf);
185     }
186 }
187