/******************************************************************************* * Copyright 2012-present Pixate, Inc. * * 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 com.pixate.freestyle.styling; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.annotation.SuppressLint; import android.graphics.Matrix; import android.graphics.Paint.Align; import android.net.Uri; import android.text.TextUtils; import android.util.DisplayMetrics; import com.pixate.freestyle.PXEngineConfiguration.PXUpdateStylesType; import com.pixate.freestyle.cg.math.PXDimension; import com.pixate.freestyle.cg.math.PXOffsets; import com.pixate.freestyle.cg.paints.PXPaint; import com.pixate.freestyle.cg.parsing.PXTransformParser; import com.pixate.freestyle.cg.shadow.PXShadowPaint; import com.pixate.freestyle.styling.infos.PXAnimationInfo; import com.pixate.freestyle.styling.infos.PXBorderInfo; import com.pixate.freestyle.styling.infos.PXAnimationInfo.PXAnimationDirection; import com.pixate.freestyle.styling.infos.PXAnimationInfo.PXAnimationFillMode; import com.pixate.freestyle.styling.infos.PXAnimationInfo.PXAnimationPlayState; import com.pixate.freestyle.styling.infos.PXAnimationInfo.PXAnimationTimingFunction; import com.pixate.freestyle.styling.infos.PXBorderInfo.PXBorderStyle; import com.pixate.freestyle.styling.infos.PXBorderInfo.PXTextBorderStyle; import com.pixate.freestyle.styling.infos.PXLineBreakInfo.PXLineBreakMode; import com.pixate.freestyle.styling.parsing.PXStylesheetLexeme; import com.pixate.freestyle.styling.parsing.PXStylesheetTokenType; import com.pixate.freestyle.styling.parsing.PXValueParser; import com.pixate.freestyle.styling.stylers.PXStylerContext.GridStyle.PXColumnStretchMode; import com.pixate.freestyle.util.CollectionUtil; import com.pixate.freestyle.util.ObjectUtil; import com.pixate.freestyle.util.Size; import com.pixate.freestyle.util.StringUtil; @SuppressLint("DefaultLocale") public class PXDeclaration { private static final PXValueParser parser = new PXValueParser(); private String name = "<unknown>"; private String filename = null; private String source = null; private List<PXStylesheetLexeme> lexemes = null; private boolean important = false; /* STATIC */ private static final Pattern ESCAPE_SEQUENCES = Pattern.compile("\\\\.", Pattern.DOTALL); private static final Map<String, String> ESCAPE_SEQUENCE_MAP; private static Map<String, Align> TEXT_ALIGN_MAP; static { ESCAPE_SEQUENCE_MAP = new HashMap<String, String>(); ESCAPE_SEQUENCE_MAP.put("\\t", "\t"); ESCAPE_SEQUENCE_MAP.put("\\r", "\r"); ESCAPE_SEQUENCE_MAP.put("\\n", "\n"); ESCAPE_SEQUENCE_MAP.put("\\f", "\f"); } /* CONSTRUCTORS */ public PXDeclaration() { } public PXDeclaration(String name) { this.name = name; } public PXDeclaration(String name, String value) { this(name); setSource(value, /* filename */null); } /* PUBLIC */ public void setSource(String source, String filename, List<PXStylesheetLexeme> lexemes) { this.source = source; this.filename = filename; this.lexemes = lexemes; } public void setSource(String source, String filename) { setSource(source, filename, PXValueParser.lexemesForSource(source)); } public String getName() { return name; } public Matrix getAffineTransformValue() { return (new PXTransformParser()).parse(getStringValue()); } public List<PXAnimationInfo> getAnimationInfoList() { return parser.parseAnimationInfos(lexemes); } public Align getTextAlignmentValue() { synchronized (PXDeclaration.class) { if (TEXT_ALIGN_MAP == null) { // one-time init TEXT_ALIGN_MAP = new HashMap<String, Align>(3); TEXT_ALIGN_MAP.put("left", Align.LEFT); TEXT_ALIGN_MAP.put("center", Align.CENTER); TEXT_ALIGN_MAP.put("right", Align.RIGHT); } } Align alignment = Align.CENTER; Align value = TEXT_ALIGN_MAP.get(getFirstWord()); if (value != null) { alignment = value; } return alignment; } public PXTextBorderStyle getTextBorderStyleValue() { PXTextBorderStyle style = PXTextBorderStyle.NONE; PXTextBorderStyle value = PXTextBorderStyle.ofCssValue(getFirstWord()); if (value != null) { style = value; } return style; } public List<PXAnimationInfo> getTransitionInfoList() { return parser.parseTransitionInfos(lexemes); } public Uri getURLValue() { return parser.parseURL(lexemes); } public List<PXAnimationDirection> getAnimationDirectionList() { return parser.parseAnimationDirectionList(lexemes); } public List<PXAnimationFillMode> getAnimationFillModeList() { return parser.parseAnimationFillModeList(lexemes); } public List<PXAnimationPlayState> getAnimationPlayStateList() { return parser.parseAnimationPlayStateList(lexemes); } public List<PXAnimationTimingFunction> getAnimationTimingFunctionList() { return parser.parseAnimationTimingFunctionList(lexemes); } public boolean getBooleanValue() { return StringUtil.toBoolean(getFirstWord()); } public PXBorderInfo getBorderValue(DisplayMetrics displayMetrics) { return parser.parseBorder(this.lexemes, displayMetrics); } public List<Size> getBorderRadiiList(DisplayMetrics displayMetrics) { return parser.parseBorderRadiusList(this.lexemes, displayMetrics); } public PXBorderStyle getBorderStyleValue() { return parser.parseBorderStyle(this.lexemes); } public List<PXBorderStyle> getBorderStyleList() { return parser.parseBorderStyleList(this.lexemes); } public Integer getColorValue() { return parser.parseColor(this.lexemes); } public int getColumnWidth(DisplayMetrics displayMetrics) { return (int) Math.ceil(parser.parseColumnWidth(this.lexemes, displayMetrics)); } public float getFloatValue(DisplayMetrics displayMetrics) { return parser.parseFloat(this.lexemes, displayMetrics); } public List<Float> getFloatListValue() { return parser.parseFloatList(this.lexemes); } public int getColumnCount() { return parser.parseColumnCount(this.lexemes); } public int getColumnGap(DisplayMetrics displayMetrics) { return parser.parseColumnGap(this.lexemes, displayMetrics); } public int getRowGap(DisplayMetrics displayMetrics) { return parser.parseRowGap(this.lexemes, displayMetrics); } public PXOffsets getInsetsValue(DisplayMetrics displayMetrics) { return parser.parseInsets(this.lexemes, displayMetrics); } public PXDimension getLengthValue() { PXDimension result = null; if (lexemes.size() > 0) { PXStylesheetLexeme lexeme = lexemes.get(0); if (lexeme.getType() == PXStylesheetTokenType.LENGTH) { result = (PXDimension) lexeme.getValue(); } else if (lexeme.getType() == PXStylesheetTokenType.NUMBER) { Number number = (Number) lexeme.getValue(); result = new PXDimension(number.floatValue(), "px"); } } return result; } public void setImportant(boolean important) { this.important = important; } public boolean isImportant() { return important; } // TODO discuss NSLineBreakMode versus Android. public PXLineBreakMode getLineBreakModeValue() { PXLineBreakMode mode = PXLineBreakMode.ofCssValue(getFirstWord()); return mode == null ? PXLineBreakMode.TRUNCATE_MIDDLE : mode; } public List<String> getNameListValue() { return parser.parseNameList(this.lexemes); } public PXOffsets getOffsetsValue(DisplayMetrics displayMetrics) { return parser.parseOffsets(this.lexemes, displayMetrics); } public List<PXPaint> getPaintList() { return parser.parsePaints(this.lexemes); } public PXPaint getPaintValue() { return parser.parsePaint(this.lexemes); } public float getSecondsValue() { return parser.parseSeconds(this.lexemes); } public PXShadowPaint getShadowValue(DisplayMetrics displayMetrics) { return parser.parseShadow(this.lexemes, displayMetrics); } public List<Float> getSecondsListValue() { return parser.parseSecondsList(this.lexemes); } // @formatter:off /* WAITING FOR PXShadowPaint public PXShadowPaint getShadowValue() { return parser.parseShadow(this.lexemes); } */ // @formatter:on public Size getSizeValue(DisplayMetrics displayMetrics) { return parser.parseSize(this.lexemes, displayMetrics); } public PXColumnStretchMode getColumnStretchMode() { return parser.parseColumnStretchMode(this.lexemes); } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int hash = 1; hash = hash * 17 + (source != null ? source.hashCode() : 0); hash = hash * 31 + (filename != null ? filename.hashCode() : 0); hash = hash * 13 + (lexemes != null ? lexemes.hashCode() : 0); return hash; } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object o) { if (o instanceof PXDeclaration) { PXDeclaration other = (PXDeclaration) o; return ObjectUtil.areEqual(filename, other.filename) && ObjectUtil.areEqual(source, other.source) && ObjectUtil.areEqual(lexemes, other.lexemes); } return false; } // TODO test like hell public String getStringValue() { List<Object> parts = new ArrayList<Object>(lexemes.size()); for (PXStylesheetLexeme lexeme : lexemes) { if (lexeme.getType() == PXStylesheetTokenType.STRING) { // grab raw value String value = (String) lexeme.getValue(); // trim quotes String content = value.substring(1, value.length() - 1); StringBuffer sb = new StringBuffer(content.length()); // replace escape sequences Matcher matcher = ESCAPE_SEQUENCES.matcher(content); while (matcher.find()) { String match = matcher.group(); String replacement = ESCAPE_SEQUENCE_MAP.get(match); if (replacement == null) { replacement = match.substring(1); } matcher.appendReplacement(sb, replacement); } matcher.appendTail(sb); parts.add(sb.toString()); } else { parts.add(lexeme.getValue()); } } return TextUtils.join(" ", parts.toArray()); } public PXUpdateStylesType getUpdateStylesTypeValue() { PXUpdateStylesType type = PXUpdateStylesType.AUTO; if ("manual".equals(getFirstWord())) { type = PXUpdateStylesType.MANUAL; } return type; } public String transformString(String value) { String text = getFirstWord(); if ("uppercase".equals(text)) { return value.toUpperCase(); } else if ("lowercase".equals(text)) { return value.toLowerCase(); } else if ("capitalize".equals(text)) { // TODO find capitalize function // could lift Apache commons-lang WordUtils.capitalizeFully // since it's (obviously) apache-licensed return value; } else { return value; } } @Override public String toString() { if (this.important) { return String.format("%s: %s !important;", this.name, this.getStringValue()); } else { return String.format("%s: %s;", this.name, this.getStringValue()); } } /* PRIVATE */ private String getFirstWord() { String word = null; if (!CollectionUtil.isEmpty(lexemes)) { Object value = lexemes.get(0).getValue(); if (value instanceof String) { word = ((String) value).toLowerCase(); } } return word; } }