package com.joanzapata.iconify.internal; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.support.annotation.CheckResult; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Size; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.util.TypedValue; import android.widget.TextView; import com.joanzapata.iconify.Icon; import java.util.List; public final class ParsingUtil { private static final String ANDROID_PACKAGE_NAME = "android"; // Prevents instantiation private ParsingUtil() {} @CheckResult @NonNull public static CharSequence parse( @NonNull TextView targetView, @NonNull @Size(min = 1) List<IconFontDescriptorWrapper> iconFontDescriptors, @NonNull CharSequence text) { final SpannableStringBuilder spannableBuilder = new SpannableStringBuilder(text); recursivePrepareSpannableIndexes(targetView, text.toString(), spannableBuilder, iconFontDescriptors, 0); return spannableBuilder; } // Analyse the text and replace {} blocks with the appropriate character // Retain all transformations in the accumulator private static void recursivePrepareSpannableIndexes( @NonNull TextView targetView, @NonNull String fullText, @NonNull SpannableStringBuilder text, @NonNull @Size(min = 1) List<IconFontDescriptorWrapper> iconFontDescriptors, @IntRange(from = 0) int start) { // Try to find a {...} in the string and extract expression from it String stringText = text.toString(); int startIndex = stringText.indexOf("{", start); if (startIndex == -1) return; int endIndex = stringText.indexOf("}", startIndex) + 1; String expression = stringText.substring(startIndex + 1, endIndex - 1); // Split the expression and retrieve the icon key String[] strokes = expression.split(" "); String key = strokes[0]; // Loop through the descriptors to find a key match IconFontDescriptorWrapper iconFontDescriptor = null; Icon icon = null; for (int i = 0; i < iconFontDescriptors.size(); i++) { iconFontDescriptor = iconFontDescriptors.get(i); icon = iconFontDescriptor.getIcon(key); if (icon != null) break; } // If no match, ignore and continue if (icon == null) { recursivePrepareSpannableIndexes(targetView, fullText, text, iconFontDescriptors, endIndex); return; } // See if any more stroke within {} should be applied Context context = targetView.getContext(); float iconSizePx = -1; int iconColor = Integer.MAX_VALUE; float iconSizeRatio = -1; Animation animation = Animation.NONE; boolean baselineAligned = false; for (int i = 1; i < strokes.length; i++) { String stroke = strokes[i]; // Look for "spin" if (stroke.equalsIgnoreCase("spin")) { animation = Animation.SPIN; } // Look for "pulse" else if (stroke.equalsIgnoreCase("pulse")) { animation = Animation.PULSE; } // Look for "baseline" else if (stroke.equalsIgnoreCase("baseline")) { baselineAligned = true; } // Look for an icon size else if (stroke.matches("([0-9]*(\\.[0-9]*)?)dp")) { iconSizePx = dpToPx(context, Float.valueOf(stroke.substring(0, stroke.length() - 2))); } else if (stroke.matches("([0-9]*(\\.[0-9]*)?)sp")) { iconSizePx = spToPx(context, Float.valueOf(stroke.substring(0, stroke.length() - 2))); } else if (stroke.matches("([0-9]*)px")) { iconSizePx = Integer.valueOf(stroke.substring(0, stroke.length() - 2)); } else if (stroke.matches("@dimen/(.*)")) { iconSizePx = getPxFromDimen(context, context.getPackageName(), stroke.substring(7)); if (iconSizePx < 0) throw new IllegalArgumentException("Unknown resource " + stroke + " in \"" + fullText + "\""); } else if (stroke.matches("@android:dimen/(.*)")) { iconSizePx = getPxFromDimen(context, ANDROID_PACKAGE_NAME, stroke.substring(15)); if (iconSizePx < 0) throw new IllegalArgumentException("Unknown resource " + stroke + " in \"" + fullText + "\""); } else if (stroke.matches("([0-9]*(\\.[0-9]*)?)%")) { iconSizeRatio = Float.valueOf(stroke.substring(0, stroke.length() - 1)) / 100f; } // Look for an icon color else if (stroke.matches("#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})")) { iconColor = Color.parseColor(stroke); } else if (stroke.matches("@color/(.*)")) { iconColor = getColorFromResource(context, context.getPackageName(), stroke.substring(7)); if (iconColor == Integer.MAX_VALUE) throw new IllegalArgumentException("Unknown resource " + stroke + " in \"" + fullText + "\""); } else if (stroke.matches("@android:color/(.*)")) { iconColor = getColorFromResource(context, ANDROID_PACKAGE_NAME, stroke.substring(15)); if (iconColor == Integer.MAX_VALUE) throw new IllegalArgumentException("Unknown resource " + stroke + " in \"" + fullText + "\""); } else { throw new IllegalArgumentException("Unknown expression " + stroke + " in \"" + fullText + "\""); } } // Replace the character and apply the typeface text = text.replace(startIndex, endIndex, "" + icon.character()); text.setSpan(new CustomTypefaceSpan(targetView, icon, iconFontDescriptor.getTypeface(context), iconSizePx, iconSizeRatio, iconColor, animation, baselineAligned), startIndex, startIndex + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); recursivePrepareSpannableIndexes(targetView, fullText, text, iconFontDescriptors, startIndex); } @CheckResult public static float getPxFromDimen(@NonNull Context context, @NonNull String packageName, @NonNull String resName) { Resources resources = context.getResources(); int resId = resources.getIdentifier( resName, "dimen", packageName); if (resId <= 0) return -1; return resources.getDimension(resId); } @CheckResult public static int getColorFromResource(@NonNull Context context, @NonNull String packageName, @NonNull String resName) { Resources resources = context.getResources(); int resId = resources.getIdentifier( resName, "color", packageName); if (resId <= 0) return Integer.MAX_VALUE; return resources.getColor(resId); } @CheckResult public static float dpToPx(@NonNull Context context, @IntRange(from = 0) float dp) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); } @CheckResult public static float spToPx(@NonNull Context context, @IntRange(from = 0) float sp) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics()); } }