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. This is added to the value of the current weight returned by
71      *              {@link Typeface#getWeight()}.
72      * @see Configuration#fontWeightAdjustment This is the adjustment in text font weight
73      * that is used to reflect the current user's preference for increasing font weight.
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      * <p>
128      * This can be {@link Configuration#FONT_WEIGHT_ADJUSTMENT_UNDEFINED}. This is added to the
129      * value of the current weight returned by {@link Typeface#getWeight()}.
130      */
getFontWeightAdjustment()131     public int getFontWeightAdjustment() {
132         return mFontWeightAdjustment;
133     }
134 
135     @Override
updateDrawState(TextPaint ds)136     public void updateDrawState(TextPaint ds) {
137         apply(ds, mStyle, mFontWeightAdjustment);
138     }
139 
140     @Override
updateMeasureState(TextPaint paint)141     public void updateMeasureState(TextPaint paint) {
142         apply(paint, mStyle, mFontWeightAdjustment);
143     }
144 
apply(Paint paint, int style, int fontWeightAdjustment)145     private static void apply(Paint paint, int style, int fontWeightAdjustment) {
146         int oldStyle;
147 
148         Typeface old = paint.getTypeface();
149         if (old == null) {
150             oldStyle = 0;
151         } else {
152             oldStyle = old.getStyle();
153         }
154 
155         int want = oldStyle | style;
156 
157         Typeface tf;
158         if (old == null) {
159             tf = Typeface.defaultFromStyle(want);
160         } else {
161             tf = Typeface.create(old, want);
162         }
163 
164         // Base typeface may already be bolded by auto bold. Bold further.
165         if ((style & Typeface.BOLD) != 0) {
166             if (fontWeightAdjustment != 0
167                     && fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
168                 int newWeight = Math.min(
169                         Math.max(tf.getWeight() + fontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
170                         FontStyle.FONT_WEIGHT_MAX);
171                 boolean italic = (want & Typeface.ITALIC) != 0;
172                 tf = Typeface.create(tf, newWeight, italic);
173             }
174         }
175 
176         int fake = want & ~tf.getStyle();
177 
178         if ((fake & Typeface.BOLD) != 0) {
179             paint.setFakeBoldText(true);
180         }
181 
182         if ((fake & Typeface.ITALIC) != 0) {
183             paint.setTextSkewX(-0.25f);
184         }
185 
186         paint.setTypeface(tf);
187     }
188 
189     @Override
toString()190     public String toString() {
191         return "StyleSpan{"
192                 + "style=" + getStyle()
193                 + ", fontWeightAdjustment=" + getFontWeightAdjustment()
194                 + '}';
195     }
196 }
197