568 lines
19 KiB
Java
568 lines
19 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2008 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.text.style;
|
||
|
|
||
|
import android.annotation.Nullable;
|
||
|
import android.content.Context;
|
||
|
import android.content.res.ColorStateList;
|
||
|
import android.content.res.TypedArray;
|
||
|
import android.graphics.LeakyTypefaceStorage;
|
||
|
import android.graphics.Typeface;
|
||
|
import android.graphics.fonts.FontStyle;
|
||
|
import android.os.LocaleList;
|
||
|
import android.os.Parcel;
|
||
|
import android.text.ParcelableSpan;
|
||
|
import android.text.TextPaint;
|
||
|
import android.text.TextUtils;
|
||
|
|
||
|
/**
|
||
|
* Sets the text appearance using the given
|
||
|
* {@link android.R.styleable#TextAppearance TextAppearance} attributes.
|
||
|
* By default {@link TextAppearanceSpan} only changes the specified attributes in XML.
|
||
|
* {@link android.R.styleable#TextAppearance_textColorHighlight textColorHighlight},
|
||
|
* {@link android.R.styleable#TextAppearance_textColorHint textColorHint},
|
||
|
* {@link android.R.styleable#TextAppearance_textAllCaps textAllCaps} and
|
||
|
* {@link android.R.styleable#TextAppearance_fallbackLineSpacing fallbackLineSpacing}
|
||
|
* are not supported by {@link TextAppearanceSpan}.
|
||
|
*
|
||
|
* {@see android.widget.TextView#setTextAppearance(int)}
|
||
|
*
|
||
|
* @attr ref android.R.styleable#TextAppearance_fontFamily
|
||
|
* @attr ref android.R.styleable#TextAppearance_textColor
|
||
|
* @attr ref android.R.styleable#TextAppearance_textColorLink
|
||
|
* @attr ref android.R.styleable#TextAppearance_textFontWeight
|
||
|
* @attr ref android.R.styleable#TextAppearance_textSize
|
||
|
* @attr ref android.R.styleable#TextAppearance_textStyle
|
||
|
* @attr ref android.R.styleable#TextAppearance_typeface
|
||
|
* @attr ref android.R.styleable#TextAppearance_shadowColor
|
||
|
* @attr ref android.R.styleable#TextAppearance_shadowDx
|
||
|
* @attr ref android.R.styleable#TextAppearance_shadowDy
|
||
|
* @attr ref android.R.styleable#TextAppearance_shadowRadius
|
||
|
* @attr ref android.R.styleable#TextAppearance_elegantTextHeight
|
||
|
* @attr ref android.R.styleable#TextAppearance_letterSpacing
|
||
|
* @attr ref android.R.styleable#TextAppearance_fontFeatureSettings
|
||
|
* @attr ref android.R.styleable#TextAppearance_fontVariationSettings
|
||
|
*
|
||
|
*/
|
||
|
public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
|
||
|
private final String mFamilyName;
|
||
|
private final int mStyle;
|
||
|
private final int mTextSize;
|
||
|
private final ColorStateList mTextColor;
|
||
|
private final ColorStateList mTextColorLink;
|
||
|
private final Typeface mTypeface;
|
||
|
|
||
|
private final int mTextFontWeight;
|
||
|
private final LocaleList mTextLocales;
|
||
|
|
||
|
private final float mShadowRadius;
|
||
|
private final float mShadowDx;
|
||
|
private final float mShadowDy;
|
||
|
private final int mShadowColor;
|
||
|
|
||
|
private final boolean mHasElegantTextHeight;
|
||
|
private final boolean mElegantTextHeight;
|
||
|
private final boolean mHasLetterSpacing;
|
||
|
private final float mLetterSpacing;
|
||
|
|
||
|
private final String mFontFeatureSettings;
|
||
|
private final String mFontVariationSettings;
|
||
|
|
||
|
/**
|
||
|
* Uses the specified TextAppearance resource to determine the
|
||
|
* text appearance. The <code>appearance</code> should be, for example,
|
||
|
* <code>android.R.style.TextAppearance_Small</code>.
|
||
|
*/
|
||
|
public TextAppearanceSpan(Context context, int appearance) {
|
||
|
this(context, appearance, -1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Uses the specified TextAppearance resource to determine the
|
||
|
* text appearance, and the specified text color resource
|
||
|
* to determine the color. The <code>appearance</code> should be,
|
||
|
* for example, <code>android.R.style.TextAppearance_Small</code>,
|
||
|
* and the <code>colorList</code> should be, for example,
|
||
|
* <code>android.R.styleable.Theme_textColorPrimary</code>.
|
||
|
*/
|
||
|
public TextAppearanceSpan(Context context, int appearance, int colorList) {
|
||
|
ColorStateList textColor;
|
||
|
|
||
|
TypedArray a =
|
||
|
context.obtainStyledAttributes(appearance,
|
||
|
com.android.internal.R.styleable.TextAppearance);
|
||
|
|
||
|
textColor = a.getColorStateList(com.android.internal.R.styleable.
|
||
|
TextAppearance_textColor);
|
||
|
mTextColorLink = a.getColorStateList(com.android.internal.R.styleable.
|
||
|
TextAppearance_textColorLink);
|
||
|
mTextSize = a.getDimensionPixelSize(com.android.internal.R.styleable.
|
||
|
TextAppearance_textSize, -1);
|
||
|
|
||
|
mStyle = a.getInt(com.android.internal.R.styleable.TextAppearance_textStyle, 0);
|
||
|
if (!context.isRestricted() && context.canLoadUnsafeResources()) {
|
||
|
mTypeface = a.getFont(com.android.internal.R.styleable.TextAppearance_fontFamily);
|
||
|
} else {
|
||
|
mTypeface = null;
|
||
|
}
|
||
|
if (mTypeface != null) {
|
||
|
mFamilyName = null;
|
||
|
} else {
|
||
|
String family = a.getString(com.android.internal.R.styleable.TextAppearance_fontFamily);
|
||
|
if (family != null) {
|
||
|
mFamilyName = family;
|
||
|
} else {
|
||
|
int tf = a.getInt(com.android.internal.R.styleable.TextAppearance_typeface, 0);
|
||
|
|
||
|
switch (tf) {
|
||
|
case 1:
|
||
|
mFamilyName = "sans";
|
||
|
break;
|
||
|
|
||
|
case 2:
|
||
|
mFamilyName = "serif";
|
||
|
break;
|
||
|
|
||
|
case 3:
|
||
|
mFamilyName = "monospace";
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
mFamilyName = null;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mTextFontWeight = a.getInt(com.android.internal.R.styleable
|
||
|
.TextAppearance_textFontWeight, /*defValue*/ FontStyle.FONT_WEIGHT_UNSPECIFIED);
|
||
|
|
||
|
final String localeString = a.getString(com.android.internal.R.styleable
|
||
|
.TextAppearance_textLocale);
|
||
|
if (localeString != null) {
|
||
|
LocaleList localeList = LocaleList.forLanguageTags(localeString);
|
||
|
if (!localeList.isEmpty()) {
|
||
|
mTextLocales = localeList;
|
||
|
} else {
|
||
|
mTextLocales = null;
|
||
|
}
|
||
|
} else {
|
||
|
mTextLocales = null;
|
||
|
}
|
||
|
|
||
|
mShadowRadius = a.getFloat(com.android.internal.R.styleable
|
||
|
.TextAppearance_shadowRadius, 0.0f);
|
||
|
mShadowDx = a.getFloat(com.android.internal.R.styleable
|
||
|
.TextAppearance_shadowDx, 0.0f);
|
||
|
mShadowDy = a.getFloat(com.android.internal.R.styleable
|
||
|
.TextAppearance_shadowDy, 0.0f);
|
||
|
mShadowColor = a.getInt(com.android.internal.R.styleable
|
||
|
.TextAppearance_shadowColor, 0);
|
||
|
|
||
|
mHasElegantTextHeight = a.hasValue(com.android.internal.R.styleable
|
||
|
.TextAppearance_elegantTextHeight);
|
||
|
mElegantTextHeight = a.getBoolean(com.android.internal.R.styleable
|
||
|
.TextAppearance_elegantTextHeight, false);
|
||
|
|
||
|
mHasLetterSpacing = a.hasValue(com.android.internal.R.styleable
|
||
|
.TextAppearance_letterSpacing);
|
||
|
mLetterSpacing = a.getFloat(com.android.internal.R.styleable
|
||
|
.TextAppearance_letterSpacing, 0.0f);
|
||
|
|
||
|
mFontFeatureSettings = a.getString(com.android.internal.R.styleable
|
||
|
.TextAppearance_fontFeatureSettings);
|
||
|
|
||
|
mFontVariationSettings = a.getString(com.android.internal.R.styleable
|
||
|
.TextAppearance_fontVariationSettings);
|
||
|
|
||
|
a.recycle();
|
||
|
|
||
|
if (colorList >= 0) {
|
||
|
a = context.obtainStyledAttributes(com.android.internal.R.style.Theme,
|
||
|
com.android.internal.R.styleable.Theme);
|
||
|
|
||
|
textColor = a.getColorStateList(colorList);
|
||
|
a.recycle();
|
||
|
}
|
||
|
|
||
|
mTextColor = textColor;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Makes text be drawn with the specified typeface, size, style,
|
||
|
* and colors.
|
||
|
*/
|
||
|
public TextAppearanceSpan(String family, int style, int size,
|
||
|
ColorStateList color, ColorStateList linkColor) {
|
||
|
mFamilyName = family;
|
||
|
mStyle = style;
|
||
|
mTextSize = size;
|
||
|
mTextColor = color;
|
||
|
mTextColorLink = linkColor;
|
||
|
mTypeface = null;
|
||
|
|
||
|
mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
|
||
|
mTextLocales = null;
|
||
|
|
||
|
mShadowRadius = 0.0f;
|
||
|
mShadowDx = 0.0f;
|
||
|
mShadowDy = 0.0f;
|
||
|
mShadowColor = 0;
|
||
|
|
||
|
mHasElegantTextHeight = false;
|
||
|
mElegantTextHeight = false;
|
||
|
mHasLetterSpacing = false;
|
||
|
mLetterSpacing = 0.0f;
|
||
|
|
||
|
mFontFeatureSettings = null;
|
||
|
mFontVariationSettings = null;
|
||
|
}
|
||
|
|
||
|
public TextAppearanceSpan(Parcel src) {
|
||
|
mFamilyName = src.readString();
|
||
|
mStyle = src.readInt();
|
||
|
mTextSize = src.readInt();
|
||
|
if (src.readInt() != 0) {
|
||
|
mTextColor = ColorStateList.CREATOR.createFromParcel(src);
|
||
|
} else {
|
||
|
mTextColor = null;
|
||
|
}
|
||
|
if (src.readInt() != 0) {
|
||
|
mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
|
||
|
} else {
|
||
|
mTextColorLink = null;
|
||
|
}
|
||
|
mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
|
||
|
|
||
|
mTextFontWeight = src.readInt();
|
||
|
mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class);
|
||
|
|
||
|
mShadowRadius = src.readFloat();
|
||
|
mShadowDx = src.readFloat();
|
||
|
mShadowDy = src.readFloat();
|
||
|
mShadowColor = src.readInt();
|
||
|
|
||
|
mHasElegantTextHeight = src.readBoolean();
|
||
|
mElegantTextHeight = src.readBoolean();
|
||
|
mHasLetterSpacing = src.readBoolean();
|
||
|
mLetterSpacing = src.readFloat();
|
||
|
|
||
|
mFontFeatureSettings = src.readString();
|
||
|
mFontVariationSettings = src.readString();
|
||
|
}
|
||
|
|
||
|
public int getSpanTypeId() {
|
||
|
return getSpanTypeIdInternal();
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public int getSpanTypeIdInternal() {
|
||
|
return TextUtils.TEXT_APPEARANCE_SPAN;
|
||
|
}
|
||
|
|
||
|
public int describeContents() {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
public void writeToParcel(Parcel dest, int flags) {
|
||
|
writeToParcelInternal(dest, flags);
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public void writeToParcelInternal(Parcel dest, int flags) {
|
||
|
dest.writeString(mFamilyName);
|
||
|
dest.writeInt(mStyle);
|
||
|
dest.writeInt(mTextSize);
|
||
|
if (mTextColor != null) {
|
||
|
dest.writeInt(1);
|
||
|
mTextColor.writeToParcel(dest, flags);
|
||
|
} else {
|
||
|
dest.writeInt(0);
|
||
|
}
|
||
|
if (mTextColorLink != null) {
|
||
|
dest.writeInt(1);
|
||
|
mTextColorLink.writeToParcel(dest, flags);
|
||
|
} else {
|
||
|
dest.writeInt(0);
|
||
|
}
|
||
|
LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest);
|
||
|
|
||
|
dest.writeInt(mTextFontWeight);
|
||
|
dest.writeParcelable(mTextLocales, flags);
|
||
|
|
||
|
dest.writeFloat(mShadowRadius);
|
||
|
dest.writeFloat(mShadowDx);
|
||
|
dest.writeFloat(mShadowDy);
|
||
|
dest.writeInt(mShadowColor);
|
||
|
|
||
|
dest.writeBoolean(mHasElegantTextHeight);
|
||
|
dest.writeBoolean(mElegantTextHeight);
|
||
|
dest.writeBoolean(mHasLetterSpacing);
|
||
|
dest.writeFloat(mLetterSpacing);
|
||
|
|
||
|
dest.writeString(mFontFeatureSettings);
|
||
|
dest.writeString(mFontVariationSettings);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the typeface family specified by this span, or <code>null</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public String getFamily() {
|
||
|
return mFamilyName;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the text color specified by this span, or <code>null</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public ColorStateList getTextColor() {
|
||
|
return mTextColor;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the link color specified by this span, or <code>null</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public ColorStateList getLinkTextColor() {
|
||
|
return mTextColorLink;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the text size specified by this span, or <code>-1</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public int getTextSize() {
|
||
|
return mTextSize;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the text style specified by this span, or <code>0</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public int getTextStyle() {
|
||
|
return mStyle;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the text font weight specified by this span, or
|
||
|
* <code>FontStyle.FONT_WEIGHT_UNSPECIFIED</code> if it does not specify one.
|
||
|
*/
|
||
|
public int getTextFontWeight() {
|
||
|
return mTextFontWeight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the {@link android.os.LocaleList} specified by this span, or <code>null</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public LocaleList getTextLocales() {
|
||
|
return mTextLocales;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the typeface specified by this span, or <code>null</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public Typeface getTypeface() {
|
||
|
return mTypeface;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the color of the text shadow specified by this span, or <code>0</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public int getShadowColor() {
|
||
|
return mShadowColor;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the horizontal offset of the text shadow specified by this span, or <code>0.0f</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public float getShadowDx() {
|
||
|
return mShadowDx;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the vertical offset of the text shadow specified by this span, or <code>0.0f</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public float getShadowDy() {
|
||
|
return mShadowDy;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the blur radius of the text shadow specified by this span, or <code>0.0f</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
public float getShadowRadius() {
|
||
|
return mShadowRadius;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the font feature settings specified by this span, or <code>null</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public String getFontFeatureSettings() {
|
||
|
return mFontFeatureSettings;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the font variation settings specified by this span, or <code>null</code>
|
||
|
* if it does not specify one.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public String getFontVariationSettings() {
|
||
|
return mFontVariationSettings;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value of elegant height metrics flag specified by this span,
|
||
|
* or <code>false</code> if it does not specify one.
|
||
|
*/
|
||
|
public boolean isElegantTextHeight() {
|
||
|
return mElegantTextHeight;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value of letter spacing to be added in em unit.
|
||
|
* @return a letter spacing amount
|
||
|
*/
|
||
|
public float getLetterSpacing() {
|
||
|
return mLetterSpacing;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void updateDrawState(TextPaint ds) {
|
||
|
updateMeasureState(ds);
|
||
|
|
||
|
if (mTextColor != null) {
|
||
|
ds.setColor(mTextColor.getColorForState(ds.drawableState, 0));
|
||
|
}
|
||
|
|
||
|
if (mTextColorLink != null) {
|
||
|
ds.linkColor = mTextColorLink.getColorForState(ds.drawableState, 0);
|
||
|
}
|
||
|
|
||
|
if (mShadowColor != 0) {
|
||
|
ds.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void updateMeasureState(TextPaint ds) {
|
||
|
final Typeface styledTypeface;
|
||
|
int style = 0;
|
||
|
|
||
|
if (mTypeface != null) {
|
||
|
style = mStyle;
|
||
|
styledTypeface = Typeface.create(mTypeface, style);
|
||
|
} else if (mFamilyName != null || mStyle != 0) {
|
||
|
Typeface tf = ds.getTypeface();
|
||
|
|
||
|
if (tf != null) {
|
||
|
style = tf.getStyle();
|
||
|
}
|
||
|
|
||
|
style |= mStyle;
|
||
|
|
||
|
if (mFamilyName != null) {
|
||
|
styledTypeface = Typeface.create(mFamilyName, style);
|
||
|
} else if (tf == null) {
|
||
|
styledTypeface = Typeface.defaultFromStyle(style);
|
||
|
} else {
|
||
|
styledTypeface = Typeface.create(tf, style);
|
||
|
}
|
||
|
} else {
|
||
|
styledTypeface = null;
|
||
|
}
|
||
|
|
||
|
if (styledTypeface != null) {
|
||
|
final Typeface readyTypeface;
|
||
|
if (mTextFontWeight >= 0) {
|
||
|
final int weight = Math.min(FontStyle.FONT_WEIGHT_MAX, mTextFontWeight);
|
||
|
final boolean italic = (style & Typeface.ITALIC) != 0;
|
||
|
readyTypeface = ds.setTypeface(Typeface.create(styledTypeface, weight, italic));
|
||
|
} else {
|
||
|
readyTypeface = styledTypeface;
|
||
|
}
|
||
|
|
||
|
int fake = style & ~readyTypeface.getStyle();
|
||
|
|
||
|
if ((fake & Typeface.BOLD) != 0) {
|
||
|
ds.setFakeBoldText(true);
|
||
|
}
|
||
|
|
||
|
if ((fake & Typeface.ITALIC) != 0) {
|
||
|
ds.setTextSkewX(-0.25f);
|
||
|
}
|
||
|
|
||
|
ds.setTypeface(readyTypeface);
|
||
|
}
|
||
|
|
||
|
if (mTextSize > 0) {
|
||
|
ds.setTextSize(mTextSize);
|
||
|
}
|
||
|
|
||
|
if (mTextLocales != null) {
|
||
|
ds.setTextLocales(mTextLocales);
|
||
|
}
|
||
|
|
||
|
if (mHasElegantTextHeight) {
|
||
|
ds.setElegantTextHeight(mElegantTextHeight);
|
||
|
}
|
||
|
|
||
|
if (mHasLetterSpacing) {
|
||
|
ds.setLetterSpacing(mLetterSpacing);
|
||
|
}
|
||
|
|
||
|
if (mFontFeatureSettings != null) {
|
||
|
ds.setFontFeatureSettings(mFontFeatureSettings);
|
||
|
}
|
||
|
|
||
|
if (mFontVariationSettings != null) {
|
||
|
ds.setFontVariationSettings(mFontVariationSettings);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "TextAppearanceSpan{"
|
||
|
+ "familyName='" + getFamily() + '\''
|
||
|
+ ", style=" + getTextStyle()
|
||
|
+ ", textSize=" + getTextSize()
|
||
|
+ ", textColor=" + getTextColor()
|
||
|
+ ", textColorLink=" + getLinkTextColor()
|
||
|
+ ", typeface=" + getTypeface()
|
||
|
+ ", textFontWeight=" + getTextFontWeight()
|
||
|
+ ", textLocales=" + getTextLocales()
|
||
|
+ ", shadowRadius=" + getShadowRadius()
|
||
|
+ ", shadowDx=" + getShadowDx()
|
||
|
+ ", shadowDy=" + getShadowDy()
|
||
|
+ ", shadowColor=" + String.format("#%08X", getShadowColor())
|
||
|
+ ", elegantTextHeight=" + isElegantTextHeight()
|
||
|
+ ", letterSpacing=" + getLetterSpacing()
|
||
|
+ ", fontFeatureSettings='" + getFontFeatureSettings() + '\''
|
||
|
+ ", fontVariationSettings='" + getFontVariationSettings() + '\''
|
||
|
+ '}';
|
||
|
}
|
||
|
}
|