1 /* 2 * Copyright (C) 2022 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; 18 19 import android.graphics.Paint; 20 import android.util.Pair; 21 22 import androidx.annotation.IntRange; 23 import androidx.annotation.NonNull; 24 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.List; 28 import java.util.Objects; 29 30 /** 31 * A class that represents of the highlight of the text. 32 */ 33 public class Highlights { 34 private final List<Pair<Paint, int[]>> mHighlights; 35 Highlights(List<Pair<Paint, int[]>> highlights)36 private Highlights(List<Pair<Paint, int[]>> highlights) { 37 mHighlights = highlights; 38 } 39 40 /** 41 * Returns the number of highlight. 42 * 43 * @return the number of highlight. 44 * 45 * @see Builder#addRange(Paint, int, int) 46 * @see Builder#addRanges(Paint, int...) 47 */ getSize()48 public @IntRange(from = 0) int getSize() { 49 return mHighlights.size(); 50 } 51 52 /** 53 * Returns a paint used for the i-th highlight. 54 * 55 * @param index an index of the highlight. Must be between 0 and {@link #getSize()} 56 * @return the paint object 57 * 58 * @see Builder#addRange(Paint, int, int) 59 * @see Builder#addRanges(Paint, int...) 60 */ getPaint(@ntRangefrom = 0) int index)61 public @NonNull Paint getPaint(@IntRange(from = 0) int index) { 62 return mHighlights.get(index).first; 63 } 64 65 /** 66 * Returns ranges of the i-th highlight. 67 * 68 * Ranges are represented of flattened inclusive start and exclusive end integers array. The 69 * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array. 70 * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the 71 * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array 72 * [1, 2, 3, 4]. 73 * 74 * @param index an index of the highlight. Must be between 0 and {@link #getSize()} 75 * @return the flattened ranges. 76 * 77 * @see Builder#addRange(Paint, int, int) 78 * @see Builder#addRanges(Paint, int...) 79 */ getRanges(int index)80 public @NonNull int[] getRanges(int index) { 81 return mHighlights.get(index).second; 82 } 83 84 /** 85 * A builder for the Highlights. 86 */ 87 public static final class Builder { 88 private final List<Pair<Paint, int[]>> mHighlights = new ArrayList<>(); 89 90 /** 91 * Add single range highlight. 92 * 93 * The {@link android.widget.TextView} and underlying {@link Layout} draws highlight in the 94 * order of the {@link #addRange} calls. 95 * 96 * For example, the following code draws (1, 2) with red and (2, 5) with blue. 97 * <code> 98 * val redPaint = Paint().apply { color = Color.RED } 99 * val bluePaint = Paint().apply { color = Color.BLUE } 100 * val highlight = Highlights.Builder() 101 * .addRange(redPaint, 1, 4) 102 * .addRange(bluePaint, 2, 5) 103 * .build() 104 * </code> 105 * 106 * 107 * @param paint a paint object used for drawing highlight path. 108 * @param start an inclusive offset of the text. 109 * @param end an exclusive offset of the text. 110 * @return this builder instance. 111 */ addRange(@onNull Paint paint, @IntRange(from = 0) int start, @IntRange(from = 0) int end)112 public @NonNull Builder addRange(@NonNull Paint paint, @IntRange(from = 0) int start, 113 @IntRange(from = 0) int end) { 114 if (start > end) { 115 throw new IllegalArgumentException("start must not be larger than end: " 116 + start + ", " + end); 117 } 118 Objects.requireNonNull(paint); 119 120 int[] range = new int[] {start, end}; 121 mHighlights.add(new Pair<>(paint, range)); 122 return this; 123 } 124 125 /** 126 * Add multiple ranges highlight. 127 * 128 * For example, the following code draws (1, 2) with red and (2, 5) with blue. 129 * <code> 130 * val redPaint = Paint().apply { color = Color.RED } 131 * val bluePaint = Paint().apply { color = Color.BLUE } 132 * val highlight = Highlights.Builder() 133 * .addRange(redPaint, 1, 4) 134 * .addRange(bluePaint, 2, 5) 135 * .build() 136 * </code> 137 * 138 * @param paint a paint object used for drawing highlight path. 139 * @param ranges a flatten ranges. The {@code 2 * i}-th element is an inclusive start offset 140 * of the {@code i}-th character. The {@code 2 * i + 1}-th element is an 141 * exclusive end offset of the {@code i}-th character. 142 * @return this builder instance. 143 */ addRanges(@onNull Paint paint, @NonNull int... ranges)144 public @NonNull Builder addRanges(@NonNull Paint paint, @NonNull int... ranges) { 145 if (ranges.length % 2 == 1) { 146 throw new IllegalArgumentException( 147 "Flatten ranges must have even numbered elements"); 148 } 149 for (int j = 0; j < ranges.length / 2; ++j) { 150 int start = ranges[j * 2]; 151 int end = ranges[j * 2 + 1]; 152 if (start > end) { 153 throw new IllegalArgumentException( 154 "Reverse range found in the flatten range: " + Arrays.toString( 155 ranges)); 156 } 157 } 158 Objects.requireNonNull(paint); 159 mHighlights.add(new Pair<>(paint, ranges)); 160 return this; 161 } 162 163 /** 164 * Build a new Highlights instance. 165 * 166 * @return a new Highlights instance. 167 */ build()168 public @NonNull Highlights build() { 169 return new Highlights(mHighlights); 170 } 171 } 172 } 173