Index: chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/AnswerTextBuilder.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/AnswerTextBuilder.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/AnswerTextBuilder.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1c657b50dd6640485931303ed816362cc75bfc82 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/AnswerTextBuilder.java |
@@ -0,0 +1,297 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.omnibox; |
+ |
+import android.graphics.Color; |
+import android.graphics.Paint; |
+import android.text.Html; |
+import android.text.Spannable; |
+import android.text.SpannableStringBuilder; |
+import android.text.TextPaint; |
+import android.text.style.AbsoluteSizeSpan; |
+import android.text.style.ForegroundColorSpan; |
+import android.text.style.MetricAffectingSpan; |
+import android.util.Log; |
+ |
+import java.util.List; |
+ |
+/** |
+ * Helper class that builds Spannables to represent the styled text in answers from Answers in |
+ * Suggest. |
+ */ |
+class AnswerTextBuilder { |
+ private static final String TAG = "AnswerTextBuilder"; |
+ |
+ // Types, sizes and colors specified at http://goto.google.com/ais_api. |
+ private static final int ANSWERS_ANSWER_TEXT_TYPE = 1; |
+ private static final int ANSWERS_HEADLINE_TEXT_TYPE = 2; |
+ private static final int ANSWERS_TOP_ALIGNED_TEXT_TYPE = 3; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_TYPE = 4; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_NEGATIVE_TYPE = 5; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_POSITIVE_TYPE = 6; |
+ private static final int ANSWERS_MORE_INFO_TEXT_TYPE = 7; |
+ private static final int ANSWERS_SUGGESTION_TEXT_TYPE = 8; |
+ private static final int ANSWERS_SUGGESTION_TEXT_POSITIVE_TYPE = 9; |
+ private static final int ANSWERS_SUGGESTION_TEXT_NEGATIVE_TYPE = 10; |
+ private static final int ANSWERS_SUGGESTION_LINK_COLOR_TYPE = 11; |
+ private static final int ANSWERS_STATUS_TEXT_TYPE = 12; |
+ private static final int ANSWERS_PERSONALIZED_SUGGESTION_TEXT_TYPE = 13; |
+ |
+ private static final int ANSWERS_ANSWER_TEXT_SIZE_SP = 28; |
+ private static final int ANSWERS_HEADLINE_TEXT_SIZE_SP = 24; |
+ private static final int ANSWERS_TOP_ALIGNED_TEXT_SIZE_SP = 13; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_SIZE_SP = 15; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_NEGATIVE_SIZE_SP = 16; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_POSITIVE_SIZE_SP = 16; |
+ private static final int ANSWERS_MORE_INFO_TEXT_SIZE_SP = 12; |
+ private static final int ANSWERS_SUGGESTION_TEXT_SIZE_SP = 15; |
+ private static final int ANSWERS_SUGGESTION_TEXT_POSITIVE_SIZE_SP = 15; |
+ private static final int ANSWERS_SUGGESTION_TEXT_NEGATIVE_SIZE_SP = 15; |
+ private static final int ANSWERS_SUGGESTION_LINK_COLOR_SIZE_SP = 15; |
+ private static final int ANSWERS_STATUS_TEXT_SIZE_SP = 13; |
+ private static final int ANSWERS_PERSONALIZED_SUGGESTION_TEXT_SIZE_SP = 15; |
+ |
+ private static final int ANSWERS_ANSWER_TEXT_COLOR = Color.BLACK; |
+ private static final int ANSWERS_HEADLINE_TEXT_COLOR = Color.BLACK; |
+ private static final int ANSWERS_TOP_ALIGNED_TEXT_COLOR = Color.GRAY; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_COLOR = Color.BLACK; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_NEGATIVE_COLOR = 0xFFC53929; |
+ private static final int ANSWERS_DESCRIPTION_TEXT_POSITIVE_COLOR = 0xFF0B8043; |
+ private static final int ANSWERS_MORE_INFO_TEXT_COLOR = Color.BLACK; |
+ private static final int ANSWERS_SUGGESTION_TEXT_COLOR = Color.BLACK; |
+ private static final int ANSWERS_SUGGESTION_TEXT_POSITIVE_COLOR = Color.GREEN; |
+ private static final int ANSWERS_SUGGESTION_TEXT_NEGATIVE_COLOR = Color.RED; |
+ // TODO(jdonnelly): Links should be purple if visited. |
+ private static final int ANSWERS_SUGGESTION_LINK_COLOR_COLOR = Color.BLUE; |
+ private static final int ANSWERS_STATUS_TEXT_COLOR = Color.GRAY; |
+ private static final int ANSWERS_PERSONALIZED_SUGGESTION_TEXT_COLOR = Color.BLACK; |
+ |
+ /** |
+ * Builds a Spannable containing all of the styled text in the supplied ImageLine. |
+ * |
+ * @param line All text fields within this line will be added to the returned Spannable. |
+ * types. |
+ * @param metrics Font metrics which will be used to properly size and layout images and top- |
+ * aligned text. |
+ * @param density Screen density which will be used to properly size and layout images and top- |
+ * aligned text. |
+ */ |
+ static Spannable buildSpannable( |
+ SuggestionAnswer.ImageLine line, Paint.FontMetrics metrics, float density) { |
+ SpannableStringBuilder builder = new SpannableStringBuilder(); |
+ |
+ // Determine the height of the largest text element in the line. This |
+ // will be used to top-align text and scale images. |
+ int maxTextHeightSp = getMaxTextHeightSp(line); |
+ |
+ List<SuggestionAnswer.TextField> textFields = line.getTextFields(); |
+ for (int i = 0; i < textFields.size(); i++) { |
+ appendAndStyleText(builder, textFields.get(i), maxTextHeightSp, metrics, density); |
+ } |
+ if (line.hasAdditionalText()) { |
+ builder.append(" "); |
+ SuggestionAnswer.TextField additionalText = line.getAdditionalText(); |
+ appendAndStyleText(builder, additionalText, maxTextHeightSp, metrics, density); |
+ } |
+ if (line.hasStatusText()) { |
+ builder.append(" "); |
+ SuggestionAnswer.TextField statusText = line.getStatusText(); |
+ appendAndStyleText(builder, statusText, maxTextHeightSp, metrics, density); |
+ } |
+ |
+ return builder; |
+ } |
+ |
+ /** |
+ * Determine the height of the largest text field in the entire line. |
+ * |
+ * @param line An ImageLine containing the text fields. |
+ * @return The height in SP. |
+ */ |
+ static int getMaxTextHeightSp(SuggestionAnswer.ImageLine line) { |
+ int maxHeightSp = 0; |
+ |
+ List<SuggestionAnswer.TextField> textFields = line.getTextFields(); |
+ for (int i = 0; i < textFields.size(); i++) { |
+ int height = getAnswerTextSizeSp(textFields.get(i).getType()); |
+ if (height > maxHeightSp) { |
+ maxHeightSp = height; |
+ } |
+ } |
+ if (line.hasAdditionalText()) { |
+ int height = getAnswerTextSizeSp(line.getAdditionalText().getType()); |
+ if (height > maxHeightSp) { |
+ maxHeightSp = height; |
+ } |
+ } |
+ if (line.hasStatusText()) { |
+ int height = getAnswerTextSizeSp(line.getStatusText().getType()); |
+ if (height > maxHeightSp) { |
+ maxHeightSp = height; |
+ } |
+ } |
+ |
+ return maxHeightSp; |
+ } |
+ |
+ /** |
+ * Append the styled text in textField to the supplied builder. |
+ * |
+ * @param builder The builder to append the text to. |
+ * @param textField The text field (with text and type) to append. |
+ * @param maxTextHeightSp The height in SP of the largest text field in the entire line. Used to |
+ * top-align text when specified. |
+ * @param metrics Font metrics which will be used to properly size and layout images and top- |
+ * aligned text. |
+ * @param density Screen density which will be used to properly size and layout images and top- |
+ * aligned text. |
+ */ |
+ private static void appendAndStyleText( |
+ SpannableStringBuilder builder, SuggestionAnswer.TextField textField, |
+ int maxTextHeightSp, Paint.FontMetrics metrics, float density) { |
+ String text = textField.getText(); |
+ int type = textField.getType(); |
+ |
+ // Unescape HTML entities (e.g. """, ">"). |
+ text = Html.fromHtml(text).toString(); |
+ |
+ // Append as HTML (answer responses contain simple markup). |
+ int start = builder.length(); |
+ builder.append(Html.fromHtml(text)); |
+ int end = builder.length(); |
+ |
+ // Apply styles according to the type. |
+ AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(getAnswerTextSizeSp(type), true); |
+ builder.setSpan(sizeSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
+ |
+ ForegroundColorSpan colorSpan = new ForegroundColorSpan(getAnswerTextColor(type)); |
+ builder.setSpan(colorSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
+ |
+ if (type == ANSWERS_TOP_ALIGNED_TEXT_TYPE) { |
+ TopAlignedSpan topAlignedSpan = |
+ new TopAlignedSpan( |
+ ANSWERS_TOP_ALIGNED_TEXT_SIZE_SP, maxTextHeightSp, metrics, density); |
+ builder.setSpan(topAlignedSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
+ } |
+ } |
+ |
+ /** |
+ * Return the SP text height for the specified answer text type. |
+ * |
+ * @param type The answer type as specified at http://goto.google.com/ais_api. |
+ */ |
+ private static int getAnswerTextSizeSp(int type) { |
+ switch (type) { |
+ case ANSWERS_ANSWER_TEXT_TYPE: |
+ return ANSWERS_ANSWER_TEXT_SIZE_SP; |
+ case ANSWERS_HEADLINE_TEXT_TYPE: |
+ return ANSWERS_HEADLINE_TEXT_SIZE_SP; |
+ case ANSWERS_TOP_ALIGNED_TEXT_TYPE: |
+ return ANSWERS_TOP_ALIGNED_TEXT_SIZE_SP; |
+ case ANSWERS_DESCRIPTION_TEXT_TYPE: |
+ return ANSWERS_DESCRIPTION_TEXT_SIZE_SP; |
+ case ANSWERS_DESCRIPTION_TEXT_NEGATIVE_TYPE: |
+ return ANSWERS_DESCRIPTION_TEXT_NEGATIVE_SIZE_SP; |
+ case ANSWERS_DESCRIPTION_TEXT_POSITIVE_TYPE: |
+ return ANSWERS_DESCRIPTION_TEXT_POSITIVE_SIZE_SP; |
+ case ANSWERS_MORE_INFO_TEXT_TYPE: |
+ return ANSWERS_MORE_INFO_TEXT_SIZE_SP; |
+ case ANSWERS_SUGGESTION_TEXT_TYPE: |
+ return ANSWERS_SUGGESTION_TEXT_SIZE_SP; |
+ case ANSWERS_SUGGESTION_TEXT_POSITIVE_TYPE: |
+ return ANSWERS_SUGGESTION_TEXT_POSITIVE_SIZE_SP; |
+ case ANSWERS_SUGGESTION_TEXT_NEGATIVE_TYPE: |
+ return ANSWERS_SUGGESTION_TEXT_NEGATIVE_SIZE_SP; |
+ case ANSWERS_SUGGESTION_LINK_COLOR_TYPE: |
+ return ANSWERS_SUGGESTION_LINK_COLOR_SIZE_SP; |
+ case ANSWERS_STATUS_TEXT_TYPE: |
+ return ANSWERS_STATUS_TEXT_SIZE_SP; |
+ case ANSWERS_PERSONALIZED_SUGGESTION_TEXT_TYPE: |
+ return ANSWERS_PERSONALIZED_SUGGESTION_TEXT_SIZE_SP; |
+ default: |
+ Log.w(TAG, "Unknown answer type: " + type); |
+ return ANSWERS_SUGGESTION_TEXT_SIZE_SP; |
+ } |
+ } |
+ |
+ /** |
+ * Return the color code for the specified answer text type. |
+ * |
+ * @param type The answer type as specified at http://goto.google.com/ais_api. |
+ */ |
+ private static int getAnswerTextColor(int type) { |
+ switch (type) { |
+ case ANSWERS_ANSWER_TEXT_TYPE: |
+ return ANSWERS_ANSWER_TEXT_COLOR; |
+ case ANSWERS_HEADLINE_TEXT_TYPE: |
+ return ANSWERS_HEADLINE_TEXT_COLOR; |
+ case ANSWERS_TOP_ALIGNED_TEXT_TYPE: |
+ return ANSWERS_TOP_ALIGNED_TEXT_COLOR; |
+ case ANSWERS_DESCRIPTION_TEXT_TYPE: |
+ return ANSWERS_DESCRIPTION_TEXT_COLOR; |
+ case ANSWERS_DESCRIPTION_TEXT_NEGATIVE_TYPE: |
+ return ANSWERS_DESCRIPTION_TEXT_NEGATIVE_COLOR; |
+ case ANSWERS_DESCRIPTION_TEXT_POSITIVE_TYPE: |
+ return ANSWERS_DESCRIPTION_TEXT_POSITIVE_COLOR; |
+ case ANSWERS_MORE_INFO_TEXT_TYPE: |
+ return ANSWERS_MORE_INFO_TEXT_COLOR; |
+ case ANSWERS_SUGGESTION_TEXT_TYPE: |
+ return ANSWERS_SUGGESTION_TEXT_COLOR; |
+ case ANSWERS_SUGGESTION_TEXT_POSITIVE_TYPE: |
+ return ANSWERS_SUGGESTION_TEXT_POSITIVE_COLOR; |
+ case ANSWERS_SUGGESTION_TEXT_NEGATIVE_TYPE: |
+ return ANSWERS_SUGGESTION_TEXT_NEGATIVE_COLOR; |
+ case ANSWERS_SUGGESTION_LINK_COLOR_TYPE: |
+ return ANSWERS_SUGGESTION_LINK_COLOR_COLOR; |
+ case ANSWERS_STATUS_TEXT_TYPE: |
+ return ANSWERS_STATUS_TEXT_COLOR; |
+ case ANSWERS_PERSONALIZED_SUGGESTION_TEXT_TYPE: |
+ return ANSWERS_PERSONALIZED_SUGGESTION_TEXT_COLOR; |
+ default: |
+ Log.w(TAG, "Unknown answer type: " + type); |
+ return ANSWERS_SUGGESTION_TEXT_COLOR; |
+ } |
+ } |
+ |
+ /** |
+ * Aligns the top of the spanned text with the top of some other specified text height. This is |
+ * done by calculating the ascent of both text heights and shifting the baseline of the spanned |
+ * text by the difference. As a result, "top aligned" means the top of the ascents are |
+ * aligned, which looks as expected in most cases (some glyphs in some fonts are drawn above |
+ * the top of the ascent). |
+ */ |
+ private static class TopAlignedSpan extends MetricAffectingSpan { |
+ private int mBaselineShift; |
+ |
+ /** |
+ * Constructor for TopAlignedSpan. |
+ * |
+ * @param textHeightSp The total height in SP of the text covered by this span. |
+ * @param maxTextHeightSp The total height in SP of the text we wish to top-align with. |
+ * @param metrics The font metrics used to determine what proportion of the font height is |
+ * the ascent. |
+ * @param density The display density. |
+ */ |
+ public TopAlignedSpan( |
+ int textHeightSp, int maxTextHeightSp, Paint.FontMetrics metrics, float density) { |
+ float ascentProportion = metrics.ascent / (metrics.top - metrics.bottom); |
+ |
+ int textAscentPx = (int) (textHeightSp * ascentProportion * density); |
+ int maxTextAscentPx = (int) (maxTextHeightSp * ascentProportion * density); |
+ |
+ this.mBaselineShift = -(maxTextAscentPx - textAscentPx); // Up is -y. |
+ } |
+ |
+ @Override |
+ public void updateDrawState(TextPaint tp) { |
+ tp.baselineShift += mBaselineShift; |
+ } |
+ |
+ @Override |
+ public void updateMeasureState(TextPaint tp) { |
+ tp.baselineShift += mBaselineShift; |
+ } |
+ } |
+} |