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.annotation.NonNull;
21 import android.graphics.Rect;
22 import android.graphics.RectF;
23 import android.graphics.pdf.content.PdfPageLinkContent;
24 import android.graphics.pdf.flags.Flags;
25 import android.graphics.pdf.utils.Preconditions;
26 import android.net.Uri;
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 links as a {@code List<List<Rect>>}, where
35  * the first {@code List<Rect>} is all of the rectangles needed to bound the
36  * first link, and so on. Most links 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 the URL index of each link - so {@link #get} returns the
42  * rectangles that bound the link, and {@link #getUrls} returns the URLs that is
43  * linked to.
44  *
45  * @hide
46  */
47 // TODO(b/324536951): Remove this class after updating the native code to directly use
48 //  PdfPageLinkContent
49 public class LinkRects extends ListOfList<Rect> {
50     /** Required at the JNI layer to detect no links and return an empty {@link LinkRects} */
51     public static final LinkRects NO_LINKS = new LinkRects(Collections.<Rect>emptyList(),
52             Collections.<Integer>emptyList(), Collections.<String>emptyList());
53     private final List<Rect> mRects;
54     private final List<Integer> mLinkToRect;
55     private final List<String> mUrls;
56 
LinkRects(@onNull List<Rect> rects, @NonNull List<Integer> linkToRect, @NonNull List<String> urls)57     public LinkRects(@NonNull List<Rect> rects, @NonNull List<Integer> linkToRect,
58             @NonNull List<String> urls) {
59         super(rects, linkToRect);
60         this.mRects = Preconditions.checkNotNull(rects, "rects cannot be null");
61         this.mLinkToRect = Preconditions.checkNotNull(linkToRect, "linkToRect cannot be null");
62         this.mUrls = Preconditions.checkNotNull(urls, "urls cannot be null");
63     }
64 
65     /** Returns the list of bounds for the embedded weblinks. */
getRects()66     public List<Rect> getRects() {
67         return mRects;
68     }
69 
70     /**
71      * Returns the mapping list of bounds to the consecutive link.
72      *
73      * @see #unflattenToList()
74      */
getLinkToRect()75     public List<Integer> getLinkToRect() {
76         return mLinkToRect;
77     }
78 
79     /** Returns the list of embedded links on the page of the document. */
getUrls()80     public List<String> getUrls() {
81         return mUrls;
82     }
83 
84     /**
85      * Un-flattens the list and converts to the public class.
86      * <p>As an example, in case there are 2 weblinks on the page of the document with the 1st link
87      * overflowing to the next line, the {@link LinkRects} would have the following values -
88      * <pre>
89      * LinkRects(
90      *      mRects=[Rect(l1, t1, r1, b1), Rect(l2, t2, r2, b2), Rect(l3, t3, r3, b3)],
91      *      mLinkToRect=[0,2,3],
92      *      mUrls=[url1, url2]
93      * )
94      *
95      * // In this case, the first link is represented by the first two {@link Rect}. The mapping to
96      * // these Rect is done through the {@code mLinkToRect} array. This is the flattened
97      * // representation of the links and bounds. Using the method below, we can un-flatten this
98      * // to the following representation -
99      * List(
100      *      PdfPageLinkContent(
101      *          bounds = [Rect(l1, t1, r1, b1), Rect(l2, t2, r2, b2)],
102      *          url = url1
103      *      ),
104      *      PdfPageLinkContent(
105      *          bounds = [Rect(l3, t3, r3, b3)],
106      *          url = url2
107      *      ),
108      * )
109      * </pre>
110      */
111     @FlaggedApi(Flags.FLAG_ENABLE_PDF_VIEWER)
unflattenToList()112     public List<PdfPageLinkContent> unflattenToList() {
113         List<PdfPageLinkContent> boundedLinks = new ArrayList<>();
114         for (int index = 0; index < mLinkToRect.size(); index++) {
115             List<Rect> bounds = new ArrayList<>();
116             int boundsForCurrentLink = (index + 1 < mLinkToRect.size()) ? mLinkToRect.get(index + 1)
117                     : mRects.size();
118             for (int boundIndex = mLinkToRect.get(index); boundIndex < boundsForCurrentLink;
119                     boundIndex++) {
120                 bounds.add(mRects.get(boundIndex));
121             }
122             boundedLinks.add(new PdfPageLinkContent(
123                     bounds.stream().map(RectF::new).collect(Collectors.toList()),
124                     Uri.parse(mUrls.get(index))));
125         }
126 
127         return boundedLinks;
128     }
129 }
130