1 /* 2 * Copyright (C) 2024 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.graphics.pdf.models.jni; 18 19 import android.annotation.FlaggedApi; 20 import android.graphics.Rect; 21 import android.graphics.RectF; 22 import android.graphics.pdf.flags.Flags; 23 import android.graphics.pdf.models.PageMatchBounds; 24 import android.graphics.pdf.utils.Preconditions; 25 26 import androidx.annotation.NonNull; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 import java.util.stream.Collectors; 32 33 /** 34 * Represents the bounds of search matches as a {@code List<List<Rect>>}, where 35 * the first {@code List<Rect>} is all the rectangles needed to bound the 36 * first match, and so on. Most matches will be surrounded with a single Rect. 37 * <p> 38 * Internally, data is stored as 1-dimensional Lists, to avoid the overhead of 39 * a large amount of single-element lists. 40 * <p> 41 * Also contains data about the character index of each match - so {@link #get} 42 * returns the rectangles that bound the match, and {@link #getCharIndexes} 43 * returns the character index that the match starts at. 44 * 45 * @hide 46 */ 47 // TODO(b/324536951): Remove this class after updating the native code to directly use 48 // PageMatchBounds 49 public class MatchRects extends ListOfList<Rect> { 50 @NonNull 51 public static final MatchRects NO_MATCHES = new MatchRects(Collections.emptyList(), 52 Collections.emptyList(), Collections.emptyList()); 53 54 private final List<Rect> mRects; 55 private final List<Integer> mMatchToRect; 56 private final List<Integer> mCharIndexes; 57 MatchRects(@onNull List<Rect> rects, @NonNull List<Integer> matchToRect, @NonNull List<Integer> charIndexes)58 public MatchRects(@NonNull List<Rect> rects, @NonNull List<Integer> matchToRect, 59 @NonNull List<Integer> charIndexes) { 60 super(rects, matchToRect); 61 this.mRects = Preconditions.checkNotNull(rects, "rects cannot be null"); 62 this.mMatchToRect = Preconditions.checkNotNull(matchToRect, "matchToRect cannot be null"); 63 this.mCharIndexes = Preconditions.checkNotNull(charIndexes, "charIndexes cannot be null"); 64 } 65 getRects()66 public List<Rect> getRects() { 67 return mRects; 68 } 69 getMatchToRect()70 public List<Integer> getMatchToRect() { 71 return mMatchToRect; 72 } 73 getCharIndexes()74 public List<Integer> getCharIndexes() { 75 return mCharIndexes; 76 } 77 78 /** 79 * Un-flattens the list and converts to the public class. 80 * <p>As an example, in case there are 2 matches on the page of the document with the 1st match 81 * overflowing to the next line, the {@link LinkRects} would have the following values - 82 * <pre> 83 * MatchRects( 84 * mRects=[Rect(l1, t1, r1, b1), Rect(l2, t2, r2, b2), Rect(l3, t3, r3, b3)], 85 * mMatchToRect=[0,2,3], 86 * mCharIndexes=[1, 3] 87 * ) 88 * 89 * // In this case, the first match is represented by the first two {@link Rect}. The mapping to 90 * // these Rect is done through the {@code mMatchToRect} array. This is the flattened 91 * // representation of the matches and bounds. Using the method below, we can un-flatten this 92 * // to the following representation - 93 * List( 94 * PageMatchBounds( 95 * bounds = [Rect(l1, t1, r1, b1), Rect(l2, t2, r2, b2)], 96 * mTextStartIndex = 1 97 * ), 98 * PageMatchBounds( 99 * bounds = [Rect(l3, t3, r3, b3)], 100 * mTextStartIndex = 3 101 * ), 102 * ) 103 * </pre> 104 */ 105 @FlaggedApi(Flags.FLAG_ENABLE_PDF_VIEWER) unflattenToList()106 public List<PageMatchBounds> unflattenToList() { 107 List<PageMatchBounds> matches = new ArrayList<>(); 108 for (int index = 0; index < mMatchToRect.size(); index++) { 109 List<Rect> bounds = new ArrayList<>(); 110 int boundsForCurrentMatch = (index + 1 < mMatchToRect.size()) ? mMatchToRect.get( 111 index + 1) : mRects.size(); 112 for (int boundIndex = mMatchToRect.get(index); boundIndex < boundsForCurrentMatch; 113 boundIndex++) { 114 bounds.add(mRects.get(boundIndex)); 115 } 116 matches.add(new PageMatchBounds( 117 bounds.stream().map(RectF::new).collect(Collectors.toList()), 118 mCharIndexes.get(index))); 119 } 120 121 return matches; 122 } 123 } 124