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 #include "jni_conversion.h"
18 
19 #include <string.h>
20 
21 #include "rect.h"
22 
23 using pdfClient::Document;
24 using pdfClient::LinuxFileOps;
25 using pdfClient::Rectangle_i;
26 using pdfClient::SelectionBoundary;
27 using std::string;
28 using std::vector;
29 
30 namespace convert {
31 
32 namespace {
33 static const char* kDimensions = "android/graphics/pdf/models/Dimensions";
34 static const char* kPdfDocument = "android/graphics/pdf/PdfDocumentProxy";
35 static const char* kLoadPdfResult = "android/graphics/pdf/models/jni/LoadPdfResult";
36 static const char* kLinkRects = "android/graphics/pdf/models/jni/LinkRects";
37 static const char* kMatchRects = "android/graphics/pdf/models/jni/MatchRects";
38 static const char* kSelection = "android/graphics/pdf/models/jni/PageSelection";
39 static const char* kBoundary = "android/graphics/pdf/models/jni/SelectionBoundary";
40 static const char* kFormWidgetInfo = "android/graphics/pdf/models/FormWidgetInfo";
41 static const char* kChoiceOption = "android/graphics/pdf/models/ListItem";
42 static const char* kGotoLinkDestination =
43         "android/graphics/pdf/content/PdfPageGotoLinkContent$Destination";
44 static const char* kGotoLink = "android/graphics/pdf/content/PdfPageGotoLinkContent";
45 
46 static const char* kRect = "android/graphics/Rect";
47 static const char* kRectF = "android/graphics/RectF";
48 static const char* kInteger = "java/lang/Integer";
49 static const char* kString = "java/lang/String";
50 static const char* kObject = "java/lang/Object";
51 static const char* kArrayList = "java/util/ArrayList";
52 static const char* kList = "java/util/List";
53 static const char* kSet = "java/util/Set";
54 static const char* kIterator = "java/util/Iterator";
55 static const char* kFloat = "java/lang/Float";
56 
57 // Helper methods to build up type signatures like "Ljava/lang/Object;" and
58 // function signatures like "(I)Ljava/lang/Integer;":
sig(const char * raw)59 string sig(const char* raw) {
60     if (strlen(raw) == 1)
61         return raw;
62     else {
63         string res = "L";
64         res += raw;
65         res += ";";
66         return res;
67     }
68 }
69 
70 // Function to build up type signatures like "Ljava/lang/Object;" and
71 // function signatures like "(I)Ljava/lang/Integer;":
72 template <typename... Args>
funcsig(const char * return_type,const Args...params)73 string funcsig(const char* return_type, const Args... params) {
74     vector<const char*> vec = {params...};
75     string res = "(";
76     for (const char* param : vec) {
77         res += sig(param);
78     }
79     res += ")";
80     res += sig(return_type);
81     return res;
82 }
83 
84 // Classes can move around - if we want a long-lived pointer to one, we have
85 // get a global reference to it which will be updated if the class moves.
GetPermClassRef(JNIEnv * env,const std::string & classname)86 inline jclass GetPermClassRef(JNIEnv* env, const std::string& classname) {
87     // NOTE: These references are held for the duration of the process.
88     return (jclass)env->NewGlobalRef(env->FindClass(classname.c_str()));
89 }
90 
91 // Convert an int to a java.lang.Integer.
ToJavaInteger(JNIEnv * env,const int & i)92 jobject ToJavaInteger(JNIEnv* env, const int& i) {
93     static jclass integer_class = GetPermClassRef(env, kInteger);
94     static jmethodID value_of =
95             env->GetStaticMethodID(integer_class, "valueOf", funcsig(kInteger, "I").c_str());
96     return env->CallStaticObjectMethod(integer_class, value_of, i);
97 }
98 
ToJavaString(JNIEnv * env,const std::string & s)99 jobject ToJavaString(JNIEnv* env, const std::string& s) {
100     return env->NewStringUTF(s.c_str());
101 }
102 
103 // Copy a C++ vector to a java ArrayList, using the given function to convert.
104 template <class T>
ToJavaList(JNIEnv * env,const vector<T> & input,jobject (* ToJavaObject)(JNIEnv * env,const T &))105 jobject ToJavaList(JNIEnv* env, const vector<T>& input,
106                    jobject (*ToJavaObject)(JNIEnv* env, const T&)) {
107     static jclass arraylist_class = GetPermClassRef(env, kArrayList);
108     static jmethodID init = env->GetMethodID(arraylist_class, "<init>", "(I)V");
109     static jmethodID add = env->GetMethodID(arraylist_class, "add", funcsig("Z", kObject).c_str());
110 
111     jobject java_list = env->NewObject(arraylist_class, init, input.size());
112     for (size_t i = 0; i < input.size(); i++) {
113         jobject java_object = ToJavaObject(env, input[i]);
114         env->CallBooleanMethod(java_list, add, java_object);
115         env->DeleteLocalRef(java_object);
116     }
117     return java_list;
118 }
119 
120 }  // namespace
121 
ToJavaPdfDocument(JNIEnv * env,std::unique_ptr<Document> doc)122 jobject ToJavaPdfDocument(JNIEnv* env, std::unique_ptr<Document> doc) {
123     static jclass pdf_doc_class = GetPermClassRef(env, kPdfDocument);
124     static jmethodID init = env->GetMethodID(pdf_doc_class, "<init>", "(JI)V");
125 
126     int numPages = doc->NumPages();
127     // Transfer ownership of |doc| to the Java object by releasing it.
128     return env->NewObject(pdf_doc_class, init, (jlong)doc.release(), numPages);
129 }
130 
ToJavaLoadPdfResult(JNIEnv * env,const Status status,std::unique_ptr<Document> doc,size_t pdfSizeInByte)131 jobject ToJavaLoadPdfResult(JNIEnv* env, const Status status, std::unique_ptr<Document> doc,
132                             size_t pdfSizeInByte) {
133     static jclass result_class = GetPermClassRef(env, kLoadPdfResult);
134     static jmethodID init =
135             env->GetMethodID(result_class, "<init>", funcsig("V", "I", kPdfDocument, "F").c_str());
136 
137     jobject jPdfDocument = (!doc) ? nullptr : ToJavaPdfDocument(env, std::move(doc));
138     jfloat pdfSizeInKb = pdfSizeInByte / 1024.0f;
139     return env->NewObject(result_class, init, (jint)status, jPdfDocument, pdfSizeInKb);
140 }
141 
GetPdfDocPtr(JNIEnv * env,jobject jPdfDocument)142 Document* GetPdfDocPtr(JNIEnv* env, jobject jPdfDocument) {
143     static jfieldID pdp_field =
144             env->GetFieldID(GetPermClassRef(env, kPdfDocument), "mPdfDocPtr", "J");
145     jlong pdf_doc_ptr = env->GetLongField(jPdfDocument, pdp_field);
146     return reinterpret_cast<Document*>(pdf_doc_ptr);
147 }
148 
ToNativeBoundary(JNIEnv * env,jobject jBoundary)149 SelectionBoundary ToNativeBoundary(JNIEnv* env, jobject jBoundary) {
150     static jclass boundary_class = GetPermClassRef(env, kBoundary);
151     static jfieldID index_field = env->GetFieldID(boundary_class, "mIndex", "I");
152     static jfieldID x_field = env->GetFieldID(boundary_class, "mX", "I");
153     static jfieldID y_field = env->GetFieldID(boundary_class, "mY", "I");
154     static jfieldID rtl_field = env->GetFieldID(boundary_class, "mIsRtl", "Z");
155 
156     return SelectionBoundary(
157             env->GetIntField(jBoundary, index_field), env->GetIntField(jBoundary, x_field),
158             env->GetIntField(jBoundary, y_field), env->GetBooleanField(jBoundary, rtl_field));
159 }
160 
ToNativeInteger(JNIEnv * env,jobject jInteger)161 int ToNativeInteger(JNIEnv* env, jobject jInteger) {
162     static jclass integer_class = GetPermClassRef(env, kInteger);
163     static jmethodID get_int_value = env->GetMethodID(integer_class, "intValue", "()I");
164     return env->CallIntMethod(jInteger, get_int_value);
165 }
166 
ToNativeIntegerVector(JNIEnv * env,jintArray jintArray)167 vector<int> ToNativeIntegerVector(JNIEnv* env, jintArray jintArray) {
168     jsize size = env->GetArrayLength(jintArray);
169     vector<int> output(size);
170     env->GetIntArrayRegion(jintArray, jsize{0}, size, &output[0]);
171     return output;
172 }
173 
ToNativeIntegerUnorderedSet(JNIEnv * env,jintArray jintArray)174 std::unordered_set<int> ToNativeIntegerUnorderedSet(JNIEnv* env, jintArray jintArray) {
175     jsize size = env->GetArrayLength(jintArray);
176     vector<int> intermediate(size);
177     env->GetIntArrayRegion(jintArray, jsize{0}, size, &intermediate[0]);
178     return std::unordered_set<int>(std::begin(intermediate), std::end(intermediate));
179 }
180 
ToJavaRect(JNIEnv * env,const Rectangle_i & r)181 jobject ToJavaRect(JNIEnv* env, const Rectangle_i& r) {
182     static jclass rect_class = GetPermClassRef(env, kRect);
183     static jmethodID init = env->GetMethodID(rect_class, "<init>", "(IIII)V");
184     return env->NewObject(rect_class, init, r.left, r.top, r.right, r.bottom);
185 }
186 
ToJavaRectF(JNIEnv * env,const Rectangle_i & r)187 jobject ToJavaRectF(JNIEnv* env, const Rectangle_i& r) {
188     static jclass rectF_class = GetPermClassRef(env, kRectF);
189     static jmethodID init = env->GetMethodID(rectF_class, "<init>", "(FFFF)V");
190     return env->NewObject(rectF_class, init, float(r.left), float(r.top), float(r.right),
191                           float(r.bottom));
192 }
193 
ToJavaRects(JNIEnv * env,const vector<Rectangle_i> & rects)194 jobject ToJavaRects(JNIEnv* env, const vector<Rectangle_i>& rects) {
195     return ToJavaList(env, rects, &ToJavaRect);
196 }
197 
ToJavaDimensions(JNIEnv * env,const Rectangle_i & r)198 jobject ToJavaDimensions(JNIEnv* env, const Rectangle_i& r) {
199     static jclass dim_class = GetPermClassRef(env, kDimensions);
200     static jmethodID init = env->GetMethodID(dim_class, "<init>", "(II)V");
201     return env->NewObject(dim_class, init, r.Width(), r.Height());
202 }
203 
ToJavaStrings(JNIEnv * env,const vector<std::string> & strings)204 jobject ToJavaStrings(JNIEnv* env, const vector<std::string>& strings) {
205     return ToJavaList(env, strings, &ToJavaString);
206 }
207 
ToJavaMatchRects(JNIEnv * env,const vector<Rectangle_i> & rects,const vector<int> & match_to_rect,const vector<int> & char_indexes)208 jobject ToJavaMatchRects(JNIEnv* env, const vector<Rectangle_i>& rects,
209                          const vector<int>& match_to_rect, const vector<int>& char_indexes) {
210     static jclass match_rects_class = GetPermClassRef(env, kMatchRects);
211     static jmethodID init = env->GetMethodID(match_rects_class, "<init>",
212                                              funcsig("V", kList, kList, kList).c_str());
213     static jfieldID no_matches_field =
214             env->GetStaticFieldID(match_rects_class, "NO_MATCHES", sig(kMatchRects).c_str());
215     static jobject no_matches =
216             env->NewGlobalRef(env->GetStaticObjectField(match_rects_class, no_matches_field));
217 
218     if (rects.empty()) {
219         return no_matches;
220     }
221     jobject java_rects = ToJavaList(env, rects, &ToJavaRect);
222     jobject java_m2r = ToJavaList(env, match_to_rect, &ToJavaInteger);
223     jobject java_cidx = ToJavaList(env, char_indexes, &ToJavaInteger);
224     return env->NewObject(match_rects_class, init, java_rects, java_m2r, java_cidx);
225 }
226 
ToJavaBoundary(JNIEnv * env,const SelectionBoundary & boundary)227 jobject ToJavaBoundary(JNIEnv* env, const SelectionBoundary& boundary) {
228     static jclass boundary_class = GetPermClassRef(env, kBoundary);
229     static jmethodID init = env->GetMethodID(boundary_class, "<init>", "(IIIZ)V");
230     return env->NewObject(boundary_class, init, boundary.index, boundary.point.x, boundary.point.y,
231                           boundary.is_rtl);
232 }
233 
ToJavaSelection(JNIEnv * env,const int page,const SelectionBoundary & start,const SelectionBoundary & stop,const vector<Rectangle_i> & rects,const std::string & text)234 jobject ToJavaSelection(JNIEnv* env, const int page, const SelectionBoundary& start,
235                         const SelectionBoundary& stop, const vector<Rectangle_i>& rects,
236                         const std::string& text) {
237     static jclass selection_class = GetPermClassRef(env, kSelection);
238     static jmethodID init =
239             env->GetMethodID(selection_class, "<init>",
240                              funcsig("V", "I", kBoundary, kBoundary, kList, kString).c_str());
241 
242     // If rects is empty then it means that the text is empty as well.
243     if (rects.empty()) {
244         return nullptr;
245     }
246 
247     jobject java_rects = ToJavaList(env, rects, &ToJavaRect);
248     return env->NewObject(selection_class, init, page, ToJavaBoundary(env, start),
249                           ToJavaBoundary(env, stop), java_rects, env->NewStringUTF(text.c_str()));
250 }
251 
ToJavaLinkRects(JNIEnv * env,const vector<Rectangle_i> & rects,const vector<int> & link_to_rect,const vector<std::string> & urls)252 jobject ToJavaLinkRects(JNIEnv* env, const vector<Rectangle_i>& rects,
253                         const vector<int>& link_to_rect, const vector<std::string>& urls) {
254     static jclass link_rects_class = GetPermClassRef(env, kLinkRects);
255     static jmethodID init =
256             env->GetMethodID(link_rects_class, "<init>", funcsig("V", kList, kList, kList).c_str());
257     static jfieldID no_links_field =
258             env->GetStaticFieldID(link_rects_class, "NO_LINKS", sig(kLinkRects).c_str());
259     static jobject no_links =
260             env->NewGlobalRef(env->GetStaticObjectField(link_rects_class, no_links_field));
261 
262     if (rects.empty()) {
263         return no_links;
264     }
265     jobject java_rects = ToJavaList(env, rects, &ToJavaRect);
266     jobject java_l2r = ToJavaList(env, link_to_rect, &ToJavaInteger);
267     jobject java_urls = ToJavaList(env, urls, &ToJavaString);
268     return env->NewObject(link_rects_class, init, java_rects, java_l2r, java_urls);
269 }
270 
ToJavaChoiceOption(JNIEnv * env,const Option & option)271 jobject ToJavaChoiceOption(JNIEnv* env, const Option& option) {
272     static jclass choice_option_class = GetPermClassRef(env, kChoiceOption);
273     static jmethodID init =
274             env->GetMethodID(choice_option_class, "<init>", funcsig("V", kString, "Z").c_str());
275     jobject java_label = ToJavaString(env, option.label);
276     return env->NewObject(choice_option_class, init, java_label, option.selected);
277 }
278 
ToJavaFormWidgetInfo(JNIEnv * env,const FormWidgetInfo & form_action_result)279 jobject ToJavaFormWidgetInfo(JNIEnv* env, const FormWidgetInfo& form_action_result) {
280     static jclass click_result_class = GetPermClassRef(env, kFormWidgetInfo);
281 
282     static jmethodID init = env->GetMethodID(
283             click_result_class, "<init>",
284             funcsig("V", "I", "I", kRect, "Z", kString, kString, "Z", "Z", "Z", "I", "F", kList)
285                     .c_str());
286 
287     jobject java_widget_rect = ToJavaRect(env, form_action_result.widget_rect());
288     jobject java_text_value = ToJavaString(env, form_action_result.text_value());
289     jobject java_accessibility_label = ToJavaString(env, form_action_result.accessibility_label());
290     jobject java_choice_options = ToJavaList(env, form_action_result.options(), &ToJavaChoiceOption);
291 
292     return env->NewObject(click_result_class, init, form_action_result.widget_type(),
293                           form_action_result.widget_index(), java_widget_rect,
294                           form_action_result.read_only(), java_text_value, java_accessibility_label,
295                           form_action_result.editable_text(), form_action_result.multiselect(),
296                           form_action_result.multi_line_text(), form_action_result.max_length(),
297                           form_action_result.font_size(), java_choice_options);
298 }
299 
ToJavaFormWidgetInfos(JNIEnv * env,const std::vector<FormWidgetInfo> & widget_infos)300 jobject ToJavaFormWidgetInfos(JNIEnv* env, const std::vector<FormWidgetInfo>& widget_infos) {
301     return ToJavaList(env, widget_infos, &ToJavaFormWidgetInfo);
302 }
303 
ToJavaDestination(JNIEnv * env,const GotoLinkDest dest)304 jobject ToJavaDestination(JNIEnv* env, const GotoLinkDest dest) {
305     static jclass goto_link_dest_class = GetPermClassRef(env, kGotoLinkDestination);
306     static jmethodID init = env->GetMethodID(goto_link_dest_class, "<init>",
307                                              funcsig("V", "I", "F", "F", "F").c_str());
308 
309     return env->NewObject(goto_link_dest_class, init, dest.page_number, dest.x, dest.y, dest.zoom);
310 }
311 
ToJavaGotoLink(JNIEnv * env,const GotoLink & link)312 jobject ToJavaGotoLink(JNIEnv* env, const GotoLink& link) {
313     static jclass goto_link_class = GetPermClassRef(env, kGotoLink);
314     static jmethodID init = env->GetMethodID(goto_link_class, "<init>",
315                                              funcsig("V", kList, kGotoLinkDestination).c_str());
316 
317     jobject java_rects = ToJavaList(env, link.rect, &ToJavaRectF);
318     jobject goto_link_dest = ToJavaDestination(env, link.dest);
319 
320     return env->NewObject(goto_link_class, init, java_rects, goto_link_dest);
321 }
322 
ToJavaGotoLinks(JNIEnv * env,const vector<GotoLink> & links)323 jobject ToJavaGotoLinks(JNIEnv* env, const vector<GotoLink>& links) {
324     return ToJavaList(env, links, &ToJavaGotoLink);
325 }
326 
327 }  // namespace convert