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