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