• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <android/bitmap.h>
20 #include <string.h>
21 
22 #include "image_object.h"
23 #include "logging.h"
24 #include "rect.h"
25 #include "text_object.h"
26 
27 using pdfClient::Annotation;
28 using pdfClient::BitmapFormat;
29 using pdfClient::Color;
30 using pdfClient::Document;
31 using pdfClient::Font;
32 using pdfClient::font_names;
33 using pdfClient::FreeTextAnnotation;
34 using pdfClient::HighlightAnnotation;
35 using pdfClient::ICoordinateConverter;
36 using pdfClient::ImageObject;
37 using pdfClient::LinuxFileOps;
38 using pdfClient::Matrix;
39 using pdfClient::PageObject;
40 using pdfClient::PathObject;
41 using pdfClient::Point_f;
42 using pdfClient::Rectangle_f;
43 using pdfClient::Rectangle_i;
44 using pdfClient::SelectionBoundary;
45 using pdfClient::StampAnnotation;
46 using pdfClient::TextObject;
47 using std::string;
48 using std::vector;
49 
50 #define LOG_TAG "jni_conversion"
51 
52 namespace convert {
53 
54 namespace {
55 static const char* kDimensions = "android/graphics/pdf/models/Dimensions";
56 static const char* kPdfDocument = "android/graphics/pdf/PdfDocumentProxy";
57 static const char* kLoadPdfResult = "android/graphics/pdf/models/jni/LoadPdfResult";
58 static const char* kLinkRects = "android/graphics/pdf/models/jni/LinkRects";
59 static const char* kMatchRects = "android/graphics/pdf/models/jni/MatchRects";
60 static const char* kSelection = "android/graphics/pdf/models/jni/PageSelection";
61 static const char* kBoundary = "android/graphics/pdf/models/jni/SelectionBoundary";
62 static const char* kFormWidgetInfo = "android/graphics/pdf/models/FormWidgetInfo";
63 static const char* kChoiceOption = "android/graphics/pdf/models/ListItem";
64 static const char* kGotoLinkDestination =
65         "android/graphics/pdf/content/PdfPageGotoLinkContent$Destination";
66 static const char* kGotoLink = "android/graphics/pdf/content/PdfPageGotoLinkContent";
67 static const char* kPageObject = "android/graphics/pdf/component/PdfPageObject";
68 static const char* kTextFont = "android/graphics/pdf/component/PdfPageTextObjectFont";
69 static const char* kTextObject = "android/graphics/pdf/component/PdfPageTextObject";
70 static const char* kPathObject = "android/graphics/pdf/component/PdfPagePathObject";
71 static const char* kImageObject = "android/graphics/pdf/component/PdfPageImageObject";
72 static const char* kStampAnnotation = "android/graphics/pdf/component/StampAnnotation";
73 static const char* kPdfAnnotation = "android/graphics/pdf/component/PdfAnnotation";
74 static const char* kHighlightAnnotation = "android/graphics/pdf/component/HighlightAnnotation";
75 static const char* kFreeTextAnnotation = "android/graphics/pdf/component/FreeTextAnnotation";
76 
77 static const char* kBitmap = "android/graphics/Bitmap";
78 static const char* kBitmapConfig = "android/graphics/Bitmap$Config";
79 static const char* kColor = "android/graphics/Color";
80 static const char* kMatrix = "android/graphics/Matrix";
81 static const char* kPath = "android/graphics/Path";
82 static const char* kRect = "android/graphics/Rect";
83 static const char* kRectF = "android/graphics/RectF";
84 static const char* kInteger = "java/lang/Integer";
85 static const char* kString = "java/lang/String";
86 static const char* kObject = "java/lang/Object";
87 static const char* kArrayList = "java/util/ArrayList";
88 static const char* kList = "java/util/List";
89 static const char* kSet = "java/util/Set";
90 static const char* kIterator = "java/util/Iterator";
91 static const char* kFloat = "java/lang/Float";
92 
93 // Helper methods to build up type signatures like "Ljava/lang/Object;" and
94 // function signatures like "(I)Ljava/lang/Integer;":
sig(const char * raw)95 string sig(const char* raw) {
96     if (strlen(raw) == 1)
97         return raw;
98     else {
99         string res = "L";
100         res += raw;
101         res += ";";
102         return res;
103     }
104 }
105 
106 // Function to build up type signatures like "Ljava/lang/Object;" and
107 // function signatures like "(I)Ljava/lang/Integer;":
108 template <typename... Args>
funcsig(const char * return_type,const Args...params)109 string funcsig(const char* return_type, const Args... params) {
110     vector<const char*> vec = {params...};
111     string res = "(";
112     for (const char* param : vec) {
113         res += sig(param);
114     }
115     res += ")";
116     res += sig(return_type);
117     return res;
118 }
119 
120 // Classes can move around - if we want a long-lived pointer to one, we have
121 // get a global reference to it which will be updated if the class moves.
GetPermClassRef(JNIEnv * env,const std::string & classname)122 inline jclass GetPermClassRef(JNIEnv* env, const std::string& classname) {
123     // NOTE: These references are held for the duration of the process.
124     return (jclass)env->NewGlobalRef(env->FindClass(classname.c_str()));
125 }
126 
127 // Convert an int to a java.lang.Integer.
ToJavaInteger(JNIEnv * env,const int & i)128 jobject ToJavaInteger(JNIEnv* env, const int& i) {
129     static jclass integer_class = GetPermClassRef(env, kInteger);
130     static jmethodID value_of =
131             env->GetStaticMethodID(integer_class, "valueOf", funcsig(kInteger, "I").c_str());
132     return env->CallStaticObjectMethod(integer_class, value_of, i);
133 }
134 
ToJavaString(JNIEnv * env,const std::string & s)135 jobject ToJavaString(JNIEnv* env, const std::string& s) {
136     return env->NewStringUTF(s.c_str());
137 }
138 
ToJavaString(JNIEnv * env,const std::wstring & ws)139 jobject ToJavaString(JNIEnv* env, const std::wstring& ws) {
140     jsize len = ws.length();
141     jchar* jchars = new jchar[len + 1];  // Null Termination
142 
143     for (size_t i = 0; i < len; ++i) {
144         jchars[i] = static_cast<jchar>(ws[i]);
145     }
146     jchars[len] = 0;
147 
148     jstring result = env->NewString(jchars, len);
149 
150     delete[] jchars;
151     return result;
152 }
153 
ToNativeWideString(JNIEnv * env,jstring java_string)154 std::wstring ToNativeWideString(JNIEnv* env, jstring java_string) {
155     std::wstring value;
156 
157     const jchar* raw = env->GetStringChars(java_string, 0);
158     jsize len = env->GetStringLength(java_string);
159 
160     value.assign(raw, raw + len);
161 
162     env->ReleaseStringChars(java_string, raw);
163     return value;
164 }
165 
166 // Copy a C++ vector to a java ArrayList, using the given function to convert.
167 template <class T>
ToJavaList(JNIEnv * env,const vector<T> & input,jobject (* ToJavaObject)(JNIEnv * env,const T &))168 jobject ToJavaList(JNIEnv* env, const vector<T>& input,
169                    jobject (*ToJavaObject)(JNIEnv* env, const T&)) {
170     static jclass arraylist_class = GetPermClassRef(env, kArrayList);
171     static jmethodID init = env->GetMethodID(arraylist_class, "<init>", "(I)V");
172     static jmethodID add = env->GetMethodID(arraylist_class, "add",
173                                                              funcsig("Z", kObject).c_str());
174 
175     jobject java_list = env->NewObject(arraylist_class, init, input.size());
176     for (size_t i = 0; i < input.size(); i++) {
177         jobject java_object = ToJavaObject(env, input[i]);
178         env->CallBooleanMethod(java_list, add, java_object);
179         env->DeleteLocalRef(java_object);
180     }
181     return java_list;
182 }
183 
184 template <class T>
ToJavaList(JNIEnv * env,const vector<T> & input,ICoordinateConverter * converter,jobject (* ToJavaObject)(JNIEnv * env,const T &,ICoordinateConverter * converter))185 jobject ToJavaList(JNIEnv* env, const vector<T>& input, ICoordinateConverter* converter,
186                    jobject (*ToJavaObject)(JNIEnv* env, const T&, ICoordinateConverter* converter)) {
187     static jclass arraylist_class = GetPermClassRef(env, kArrayList);
188     static jmethodID init = env->GetMethodID(arraylist_class, "<init>", "(I)V");
189     static jmethodID add = env->GetMethodID(arraylist_class, "add", funcsig("Z", kObject).c_str());
190 
191     jobject java_list = env->NewObject(arraylist_class, init, input.size());
192     for (size_t i = 0; i < input.size(); i++) {
193         jobject java_object = ToJavaObject(env, input[i], converter);
194         env->CallBooleanMethod(java_list, add, java_object);
195         env->DeleteLocalRef(java_object);
196     }
197     return java_list;
198 }
199 
200 // Copy a C++ vector to a java ArrayList, using the given function to convert.
201 template <class T>
ToJavaList(JNIEnv * env,const vector<T * > & input,ICoordinateConverter * converter,jobject (* ToJavaObject)(JNIEnv * env,const T *,ICoordinateConverter * converter))202 jobject ToJavaList(JNIEnv* env, const vector<T*>& input, ICoordinateConverter* converter,
203                    jobject (*ToJavaObject)(JNIEnv* env, const T*,
204                                            ICoordinateConverter* converter)) {
205     static jclass arraylist_class = GetPermClassRef(env, kArrayList);
206     static jmethodID init = env->GetMethodID(arraylist_class, "<init>", "(I)V");
207     static jmethodID add = env->GetMethodID(arraylist_class, "add", funcsig("Z", kObject).c_str());
208 
209     jobject java_list = env->NewObject(arraylist_class, init, input.size());
210     for (size_t i = 0; i < input.size(); i++) {
211         jobject java_object = ToJavaObject(env, input[i], converter);
212         env->CallBooleanMethod(java_list, add, java_object);
213         env->DeleteLocalRef(java_object);
214     }
215     return java_list;
216 }
217 
218 }  // namespace
219 
ToJavaPdfDocument(JNIEnv * env,std::unique_ptr<Document> doc)220 jobject ToJavaPdfDocument(JNIEnv* env, std::unique_ptr<Document> doc) {
221     static jclass pdf_doc_class = GetPermClassRef(env, kPdfDocument);
222     static jmethodID init = env->GetMethodID(pdf_doc_class, "<init>", "(JI)V");
223 
224     int numPages = doc->NumPages();
225     // Transfer ownership of |doc| to the Java object by releasing it.
226     return env->NewObject(pdf_doc_class, init, (jlong)doc.release(), numPages);
227 }
228 
ToJavaLoadPdfResult(JNIEnv * env,const Status status,std::unique_ptr<Document> doc,size_t pdfSizeInByte)229 jobject ToJavaLoadPdfResult(JNIEnv* env, const Status status, std::unique_ptr<Document> doc,
230                             size_t pdfSizeInByte) {
231     static jclass result_class = GetPermClassRef(env, kLoadPdfResult);
232     static jmethodID init =
233             env->GetMethodID(result_class, "<init>", funcsig("V", "I", kPdfDocument, "F").c_str());
234 
235     jobject jPdfDocument = (!doc) ? nullptr : ToJavaPdfDocument(env, std::move(doc));
236     jfloat pdfSizeInKb = pdfSizeInByte / 1024.0f;
237     return env->NewObject(result_class, init, (jint)status, jPdfDocument, pdfSizeInKb);
238 }
239 
GetPdfDocPtr(JNIEnv * env,jobject jPdfDocument)240 Document* GetPdfDocPtr(JNIEnv* env, jobject jPdfDocument) {
241     static jfieldID pdp_field =
242             env->GetFieldID(GetPermClassRef(env, kPdfDocument), "mPdfDocPtr", "J");
243     jlong pdf_doc_ptr = env->GetLongField(jPdfDocument, pdp_field);
244     return reinterpret_cast<Document*>(pdf_doc_ptr);
245 }
246 
ToNativeBoundary(JNIEnv * env,jobject jBoundary)247 SelectionBoundary ToNativeBoundary(JNIEnv* env, jobject jBoundary) {
248     static jclass boundary_class = GetPermClassRef(env, kBoundary);
249     static jfieldID index_field = env->GetFieldID(boundary_class, "mIndex", "I");
250     static jfieldID x_field = env->GetFieldID(boundary_class, "mX", "I");
251     static jfieldID y_field = env->GetFieldID(boundary_class, "mY", "I");
252     static jfieldID rtl_field = env->GetFieldID(boundary_class, "mIsRtl", "Z");
253 
254     return SelectionBoundary(
255             env->GetIntField(jBoundary, index_field), env->GetIntField(jBoundary, x_field),
256             env->GetIntField(jBoundary, y_field), env->GetBooleanField(jBoundary, rtl_field));
257 }
258 
ToNativeInteger(JNIEnv * env,jobject jInteger)259 int ToNativeInteger(JNIEnv* env, jobject jInteger) {
260     static jclass integer_class = GetPermClassRef(env, kInteger);
261     static jmethodID get_int_value = env->GetMethodID(integer_class, "intValue", "()I");
262     return env->CallIntMethod(jInteger, get_int_value);
263 }
264 
ToNativeIntegerVector(JNIEnv * env,jintArray jintArray)265 vector<int> ToNativeIntegerVector(JNIEnv* env, jintArray jintArray) {
266     jsize size = env->GetArrayLength(jintArray);
267     vector<int> output(size);
268     env->GetIntArrayRegion(jintArray, jsize{0}, size, &output[0]);
269     return output;
270 }
271 
ToNativeIntegerUnorderedSet(JNIEnv * env,jintArray jintArray)272 std::unordered_set<int> ToNativeIntegerUnorderedSet(JNIEnv* env, jintArray jintArray) {
273     jsize size = env->GetArrayLength(jintArray);
274     vector<int> intermediate(size);
275     env->GetIntArrayRegion(jintArray, jsize{0}, size, &intermediate[0]);
276     return std::unordered_set<int>(std::begin(intermediate), std::end(intermediate));
277 }
278 
ToJavaRect(JNIEnv * env,const Rectangle_i & r)279 jobject ToJavaRect(JNIEnv* env, const Rectangle_i& r) {
280     static jclass rect_class = GetPermClassRef(env, kRect);
281     static jmethodID init = env->GetMethodID(rect_class, "<init>", "(IIII)V");
282     return env->NewObject(rect_class, init, r.left, r.top, r.right, r.bottom);
283 }
284 
ToJavaRectF(JNIEnv * env,const Rectangle_i & r)285 jobject ToJavaRectF(JNIEnv* env, const Rectangle_i& r) {
286     static jclass rectF_class = GetPermClassRef(env, kRectF);
287     static jmethodID init = env->GetMethodID(rectF_class, "<init>", "(FFFF)V");
288     return env->NewObject(rectF_class, init, float(r.left), float(r.top), float(r.right),
289                           float(r.bottom));
290 }
291 
ToJavaRectF(JNIEnv * env,const Rectangle_f & r,ICoordinateConverter * converter)292 jobject ToJavaRectF(JNIEnv* env, const Rectangle_f& r, ICoordinateConverter* converter) {
293     static jclass rectF_class = GetPermClassRef(env, kRectF);
294     static jmethodID init = env->GetMethodID(rectF_class, "<init>", "(FFFF)V");
295 
296     Point_f top_left_corner = converter->PageToDevice({r.left, r.top});
297     Point_f bottom_down_corner = converter->PageToDevice({r.right, r.bottom});
298     return env->NewObject(rectF_class, init, top_left_corner.x, top_left_corner.y,
299                           bottom_down_corner.x, bottom_down_corner.y);
300 }
301 
ToNativeRectF(JNIEnv * env,jobject java_rectF,ICoordinateConverter * converter)302 Rectangle_f ToNativeRectF(JNIEnv* env, jobject java_rectF, ICoordinateConverter* converter) {
303     static jclass rectF_class = GetPermClassRef(env, kRectF);
304     static jfieldID left_field = env->GetFieldID(rectF_class, "left", "F");
305     static jfieldID top_field = env->GetFieldID(rectF_class, "top", "F");
306     static jfieldID right_field = env->GetFieldID(rectF_class, "right", "F");
307     static jfieldID bottom_field = env->GetFieldID(rectF_class, "bottom", "F");
308 
309     float left = env->GetFloatField(java_rectF, left_field);
310     float top = env->GetFloatField(java_rectF, top_field);
311     float right = env->GetFloatField(java_rectF, right_field);
312     float bottom = env->GetFloatField(java_rectF, bottom_field);
313 
314     Point_f top_left_corner = converter->DeviceToPage({left, top});
315     Point_f bottom_down_corner = converter->DeviceToPage({right, bottom});
316 
317     return Rectangle_f{top_left_corner.x, top_left_corner.y, bottom_down_corner.x,
318                        bottom_down_corner.y};
319 }
320 
ToJavaRects(JNIEnv * env,const vector<Rectangle_i> & rects)321 jobject ToJavaRects(JNIEnv* env, const vector<Rectangle_i>& rects) {
322     return ToJavaList(env, rects, &ToJavaRect);
323 }
324 
ToJavaDimensions(JNIEnv * env,const Rectangle_i & r)325 jobject ToJavaDimensions(JNIEnv* env, const Rectangle_i& r) {
326     static jclass dim_class = GetPermClassRef(env, kDimensions);
327     static jmethodID init = env->GetMethodID(dim_class, "<init>", "(II)V");
328     return env->NewObject(dim_class, init, r.Width(), r.Height());
329 }
330 
ToJavaStrings(JNIEnv * env,const vector<std::string> & strings)331 jobject ToJavaStrings(JNIEnv* env, const vector<std::string>& strings) {
332     return ToJavaList(env, strings, &ToJavaString);
333 }
334 
ToJavaMatchRects(JNIEnv * env,const vector<Rectangle_i> & rects,const vector<int> & match_to_rect,const vector<int> & char_indexes)335 jobject ToJavaMatchRects(JNIEnv* env, const vector<Rectangle_i>& rects,
336                          const vector<int>& match_to_rect, const vector<int>& char_indexes) {
337     static jclass match_rects_class = GetPermClassRef(env, kMatchRects);
338     static jmethodID init = env->GetMethodID(match_rects_class, "<init>",
339                                              funcsig("V", kList, kList, kList).c_str());
340     static jfieldID no_matches_field =
341             env->GetStaticFieldID(match_rects_class, "NO_MATCHES", sig(kMatchRects).c_str());
342     static jobject no_matches =
343             env->NewGlobalRef(env->GetStaticObjectField(match_rects_class, no_matches_field));
344 
345     if (rects.empty()) {
346         return no_matches;
347     }
348     jobject java_rects = ToJavaList(env, rects, &ToJavaRect);
349     jobject java_m2r = ToJavaList(env, match_to_rect, &ToJavaInteger);
350     jobject java_cidx = ToJavaList(env, char_indexes, &ToJavaInteger);
351     return env->NewObject(match_rects_class, init, java_rects, java_m2r, java_cidx);
352 }
353 
ToJavaBoundary(JNIEnv * env,const SelectionBoundary & boundary)354 jobject ToJavaBoundary(JNIEnv* env, const SelectionBoundary& boundary) {
355     static jclass boundary_class = GetPermClassRef(env, kBoundary);
356     static jmethodID init = env->GetMethodID(boundary_class, "<init>", "(IIIZ)V");
357     return env->NewObject(boundary_class, init, boundary.index, boundary.point.x, boundary.point.y,
358                           boundary.is_rtl);
359 }
360 
ToJavaSelection(JNIEnv * env,const int page,const SelectionBoundary & start,const SelectionBoundary & stop,const vector<Rectangle_i> & rects,const std::string & text)361 jobject ToJavaSelection(JNIEnv* env, const int page, const SelectionBoundary& start,
362                         const SelectionBoundary& stop, const vector<Rectangle_i>& rects,
363                         const std::string& text) {
364     static jclass selection_class = GetPermClassRef(env, kSelection);
365     static jmethodID init =
366             env->GetMethodID(selection_class, "<init>",
367                              funcsig("V", "I", kBoundary, kBoundary, kList, kString).c_str());
368 
369     // If rects is empty then it means that the text is empty as well.
370     if (rects.empty()) {
371         return nullptr;
372     }
373 
374     jobject java_rects = ToJavaList(env, rects, &ToJavaRect);
375     return env->NewObject(selection_class, init, page, ToJavaBoundary(env, start),
376                           ToJavaBoundary(env, stop), java_rects, env->NewStringUTF(text.c_str()));
377 }
378 
ToJavaLinkRects(JNIEnv * env,const vector<Rectangle_i> & rects,const vector<int> & link_to_rect,const vector<std::string> & urls)379 jobject ToJavaLinkRects(JNIEnv* env, const vector<Rectangle_i>& rects,
380                         const vector<int>& link_to_rect, const vector<std::string>& urls) {
381     static jclass link_rects_class = GetPermClassRef(env, kLinkRects);
382     static jmethodID init =
383             env->GetMethodID(link_rects_class, "<init>", funcsig("V", kList, kList, kList).c_str());
384     static jfieldID no_links_field =
385             env->GetStaticFieldID(link_rects_class, "NO_LINKS", sig(kLinkRects).c_str());
386     static jobject no_links =
387             env->NewGlobalRef(env->GetStaticObjectField(link_rects_class, no_links_field));
388 
389     if (rects.empty()) {
390         return no_links;
391     }
392     jobject java_rects = ToJavaList(env, rects, &ToJavaRect);
393     jobject java_l2r = ToJavaList(env, link_to_rect, &ToJavaInteger);
394     jobject java_urls = ToJavaList(env, urls, &ToJavaString);
395     return env->NewObject(link_rects_class, init, java_rects, java_l2r, java_urls);
396 }
397 
ToJavaChoiceOption(JNIEnv * env,const Option & option)398 jobject ToJavaChoiceOption(JNIEnv* env, const Option& option) {
399     static jclass choice_option_class = GetPermClassRef(env, kChoiceOption);
400     static jmethodID init =
401             env->GetMethodID(choice_option_class, "<init>", funcsig("V", kString, "Z").c_str());
402     jobject java_label = ToJavaString(env, option.label);
403     return env->NewObject(choice_option_class, init, java_label, option.selected);
404 }
405 
ToJavaFormWidgetInfo(JNIEnv * env,const FormWidgetInfo & form_action_result)406 jobject ToJavaFormWidgetInfo(JNIEnv* env, const FormWidgetInfo& form_action_result) {
407     static jclass click_result_class = GetPermClassRef(env, kFormWidgetInfo);
408 
409     static jmethodID init = env->GetMethodID(
410             click_result_class, "<init>",
411             funcsig("V", "I", "I", kRect, "Z", kString, kString, "Z", "Z", "Z", "I", "F", kList)
412                     .c_str());
413 
414     jobject java_widget_rect = ToJavaRect(env, form_action_result.widget_rect());
415     jobject java_text_value = ToJavaString(env, form_action_result.text_value());
416     jobject java_accessibility_label = ToJavaString(env, form_action_result.accessibility_label());
417     jobject java_choice_options = ToJavaList(env, form_action_result.options(), &ToJavaChoiceOption);
418 
419     return env->NewObject(click_result_class, init, form_action_result.widget_type(),
420                           form_action_result.widget_index(), java_widget_rect,
421                           form_action_result.read_only(), java_text_value, java_accessibility_label,
422                           form_action_result.editable_text(), form_action_result.multiselect(),
423                           form_action_result.multi_line_text(), form_action_result.max_length(),
424                           form_action_result.font_size(), java_choice_options);
425 }
426 
ToJavaFormWidgetInfos(JNIEnv * env,const std::vector<FormWidgetInfo> & widget_infos)427 jobject ToJavaFormWidgetInfos(JNIEnv* env, const std::vector<FormWidgetInfo>& widget_infos) {
428     return ToJavaList(env, widget_infos, &ToJavaFormWidgetInfo);
429 }
430 
ToJavaDestination(JNIEnv * env,const GotoLinkDest dest)431 jobject ToJavaDestination(JNIEnv* env, const GotoLinkDest dest) {
432     static jclass goto_link_dest_class = GetPermClassRef(env, kGotoLinkDestination);
433     static jmethodID init = env->GetMethodID(goto_link_dest_class, "<init>",
434                                              funcsig("V", "I", "F", "F", "F").c_str());
435 
436     return env->NewObject(goto_link_dest_class, init, dest.page_number, dest.x, dest.y, dest.zoom);
437 }
438 
ToJavaGotoLink(JNIEnv * env,const GotoLink & link)439 jobject ToJavaGotoLink(JNIEnv* env, const GotoLink& link) {
440     static jclass goto_link_class = GetPermClassRef(env, kGotoLink);
441     static jmethodID init = env->GetMethodID(goto_link_class, "<init>",
442                                              funcsig("V", kList, kGotoLinkDestination).c_str());
443 
444     jobject java_rects = ToJavaList(env, link.rect, &ToJavaRectF);
445     jobject goto_link_dest = ToJavaDestination(env, link.dest);
446 
447     return env->NewObject(goto_link_class, init, java_rects, goto_link_dest);
448 }
449 
ToJavaGotoLinks(JNIEnv * env,const vector<GotoLink> & links)450 jobject ToJavaGotoLinks(JNIEnv* env, const vector<GotoLink>& links) {
451     return ToJavaList(env, links, &ToJavaGotoLink);
452 }
453 
ConvertBgrToRgba(uint32_t * rgba_pixel_array,uint8_t * bgr_pixel_array,size_t rgba_stride,size_t bgr_stride,size_t width,size_t height)454 void ConvertBgrToRgba(uint32_t* rgba_pixel_array, uint8_t* bgr_pixel_array, size_t rgba_stride,
455                       size_t bgr_stride, size_t width, size_t height) {
456     for (size_t y = 0; y < height; y++) {
457         uint32_t* rgba_row_ptr = rgba_pixel_array + y * (rgba_stride / 4);
458         uint8_t* bgr_row_ptr = bgr_pixel_array + y * (bgr_stride);
459         for (size_t x = 0; x < width; x++) {
460             // Extract BGR components stored.
461             uint8_t blue = bgr_row_ptr[x * 3];
462             uint8_t green = bgr_row_ptr[x * 3 + 1];
463             uint8_t red = bgr_row_ptr[x * 3 + 2];
464             // Storing java bitmap components RGBA in little-endian.
465             rgba_row_ptr[x] = (0xFF << 24) | (blue << 16) | (green << 8) | red;
466         }
467     }
468 }
469 
ConvertBgraToRgba(uint32_t * rgba_pixel_array,uint8_t * bgra_pixel_array,size_t rgba_stride,size_t bgra_stride,size_t width,size_t height,bool ignore_alpha)470 void ConvertBgraToRgba(uint32_t* rgba_pixel_array, uint8_t* bgra_pixel_array, size_t rgba_stride,
471                        size_t bgra_stride, size_t width, size_t height, bool ignore_alpha) {
472     for (size_t y = 0; y < height; y++) {
473         uint32_t* rgba_row_ptr = rgba_pixel_array + y * (rgba_stride / 4);
474         uint8_t* bgra_row_ptr = bgra_pixel_array + y * (bgra_stride);
475         for (size_t x = 0; x < width; x++) {
476             // Extract BGR components and determine alpha based on ignore_alpha flag.
477             uint8_t blue = bgra_row_ptr[x * 4];
478             uint8_t green = bgra_row_ptr[x * 4 + 1];
479             uint8_t red = bgra_row_ptr[x * 4 + 2];
480             uint8_t alpha = ignore_alpha ? 0xFF : bgra_row_ptr[x * 4 + 3];
481             // Storing java bitmap components RGBA in little-endian.
482             rgba_row_ptr[x] = (alpha << 24) | (blue << 16) | (green << 8) | red;
483         }
484     }
485 }
486 
ToJavaBitmap(JNIEnv * env,void * buffer,BitmapFormat bitmap_format,size_t width,size_t height,size_t native_stride)487 jobject ToJavaBitmap(JNIEnv* env, void* buffer, BitmapFormat bitmap_format, size_t width,
488                      size_t height, size_t native_stride) {
489     // Find Java Bitmap class
490     static jclass bitmap_class = GetPermClassRef(env, kBitmap);
491 
492     // Get createBitmap method ID
493     static jmethodID create_bitmap = env->GetStaticMethodID(
494             bitmap_class, "createBitmap", funcsig(kBitmap, "I", "I", kBitmapConfig).c_str());
495 
496     // Get Bitmap.Config.ARGB_8888 field ID
497     static jclass bitmap_config_class = GetPermClassRef(env, kBitmapConfig);
498     static jfieldID argb8888_field =
499             env->GetStaticFieldID(bitmap_config_class, "ARGB_8888", sig(kBitmapConfig).c_str());
500     static jobject argb8888 =
501             env->NewGlobalRef(env->GetStaticObjectField(bitmap_config_class, argb8888_field));
502 
503     // Create a Java Bitmap object
504     jobject java_bitmap =
505             env->CallStaticObjectMethod(bitmap_class, create_bitmap, width, height, argb8888);
506 
507     // Copy the buffer data into java bitmap.
508     AndroidBitmapInfo bitmap_info;
509     AndroidBitmap_getInfo(env, java_bitmap, &bitmap_info);
510     size_t java_stride = bitmap_info.stride;
511 
512     void* bitmap_pixels;
513     if (AndroidBitmap_lockPixels(env, java_bitmap, &bitmap_pixels) < 0) {
514         return NULL;
515     }
516 
517     uint32_t* java_pixel_array = static_cast<uint32_t*>(bitmap_pixels);
518     uint8_t* native_pixel_array = static_cast<uint8_t*>(buffer);
519     switch (bitmap_format) {
520         case BitmapFormat::BGR: {
521             ConvertBgrToRgba(java_pixel_array, native_pixel_array, java_stride, native_stride,
522                              width, height);
523             break;
524         }
525         case BitmapFormat::BGRA: {
526             ConvertBgraToRgba(java_pixel_array, native_pixel_array, java_stride, native_stride,
527                               width, height, false);
528             break;
529         }
530         case BitmapFormat::BGRx: {
531             ConvertBgraToRgba(java_pixel_array, native_pixel_array, java_stride, native_stride,
532                               width, height, true);
533             break;
534         }
535         default: {
536             LOGE("Bitmap format unknown!");
537             AndroidBitmap_unlockPixels(env, java_bitmap);
538             return NULL;
539         }
540     }
541 
542     AndroidBitmap_unlockPixels(env, java_bitmap);
543 
544     return java_bitmap;
545 }
546 
ToJavaColorInt(Color color)547 int ToJavaColorInt(Color color) {
548     // Get ARGB values from Native Color
549     uint A = color.a;
550     uint R = color.r;
551     uint G = color.g;
552     uint B = color.b;
553 
554     // Make ARGB  java color int
555     int java_color_int = (A & 0xFF) << 24 | (R & 0xFF) << 16 | (G & 0xFF) << 8 | (B & 0xFF);
556 
557     return java_color_int;
558 }
559 
ToJavaColor(JNIEnv * env,Color color)560 jobject ToJavaColor(JNIEnv* env, Color color) {
561     // Find Java Color class
562     static jclass color_class = GetPermClassRef(env, kColor);
563 
564     // Get valueOf method ID
565     static jmethodID value_of =
566             env->GetStaticMethodID(color_class, "valueOf", funcsig(kColor, "I").c_str());
567 
568     // Make ARGB  java color int
569     int java_color_int = ToJavaColorInt(color);
570 
571     // Create a Java Color Object.
572     jobject java_color = env->CallStaticObjectMethod(color_class, value_of, java_color_int);
573 
574     return java_color;
575 }
576 
ToJavaFloatArray(JNIEnv * env,const float arr[],size_t length)577 jfloatArray ToJavaFloatArray(JNIEnv* env, const float arr[], size_t length) {
578     // Create Java float Array.
579     jfloatArray java_float_array = env->NewFloatArray(length);
580 
581     // Copy data from the C++ float Array to the Java float Array
582     env->SetFloatArrayRegion(java_float_array, 0, length, arr);
583 
584     return java_float_array;
585 }
586 
ToJavaMatrix(JNIEnv * env,const Matrix matrix)587 jobject ToJavaMatrix(JNIEnv* env, const Matrix matrix) {
588     // Find Java Matrix class
589     static jclass matrix_class = GetPermClassRef(env, kMatrix);
590     // Get the constructor method ID
591     static jmethodID init = env->GetMethodID(matrix_class, "<init>", funcsig("V").c_str());
592 
593     // Create Java Matrix object.
594     jobject java_matrix = env->NewObject(matrix_class, init);
595 
596     // Create Transform Array.
597     float transform[9] = {matrix.a, matrix.c, matrix.e, matrix.b, matrix.d, matrix.f, 0, 0, 1};
598 
599     // Convert to Java floatArray.
600     jfloatArray java_float_array =
601             ToJavaFloatArray(env, transform, sizeof(transform) / sizeof(transform[0]));
602 
603     // Matrix setValues.
604     static jmethodID set_values = env->GetMethodID(matrix_class, "setValues", "([F)V");
605     env->CallVoidMethod(java_matrix, set_values, java_float_array);
606 
607     return java_matrix;
608 }
609 
ToJavaPath(JNIEnv * env,const std::vector<PathObject::Segment> & segments,ICoordinateConverter * converter)610 jobject ToJavaPath(JNIEnv* env, const std::vector<PathObject::Segment>& segments,
611                    ICoordinateConverter* converter) {
612     // Find Java Path class.
613     static jclass path_class = GetPermClassRef(env, kPath);
614     // Get the constructor methodID.
615     static jmethodID init = env->GetMethodID(path_class, "<init>", funcsig("V").c_str());
616 
617     // Create Java Path object.
618     jobject java_path = env->NewObject(path_class, init);
619 
620     // Set Path Segments in Java.
621     for (auto& segment : segments) {
622         // Get PageToDevice Coordinates
623         Point_f output = converter->PageToDevice({segment.x, segment.y});
624         switch (segment.command) {
625             case PathObject::Segment::Command::Move: {
626                 static jmethodID move_to =
627                         env->GetMethodID(path_class, "moveTo", funcsig("V", "F", "F").c_str());
628                 env->CallVoidMethod(java_path, move_to, output.x, output.y);
629                 break;
630             }
631             case PathObject::Segment::Command::Line: {
632                 static jmethodID line_to =
633                         env->GetMethodID(path_class, "lineTo", funcsig("V", "F", "F").c_str());
634                 env->CallVoidMethod(java_path, line_to, output.x, output.y);
635                 break;
636             }
637             default:
638                 break;
639         }
640         // Check if segment isClosed.
641         if (segment.is_closed) {
642             static jmethodID close = env->GetMethodID(path_class, "close", funcsig("V").c_str());
643             env->CallVoidMethod(java_path, close);
644         }
645     }
646 
647     return java_path;
648 }
649 
ToJavaPdfTextObject(JNIEnv * env,const TextObject * text_object)650 jobject ToJavaPdfTextObject(JNIEnv* env, const TextObject* text_object) {
651     // Find Java PdfTextObject Class.
652     static jclass text_object_class = GetPermClassRef(env, kTextObject);
653 
654     // Create Java Text String from TextObject Data String.
655     jobject java_string = ToJavaString(env, text_object->text_);
656 
657     // Get Native Font Object Data.
658     int font_family = static_cast<int>(text_object->font_.GetFamily());
659     bool bold = text_object->font_.IsBold();
660     bool italic = text_object->font_.IsItalic();
661 
662     // Create Java TextObjectFont Instance.
663     static jclass text_font_class = GetPermClassRef(env, kTextFont);
664     static jmethodID init_text_font =
665             env->GetMethodID(text_font_class, "<init>", funcsig("V", "I", "Z", "Z").c_str());
666     jobject java_font = env->NewObject(text_font_class, init_text_font, font_family, bold, italic);
667 
668     // Create Java PdfTextObject Instance.
669     static jmethodID init_text_object = env->GetMethodID(
670             text_object_class, "<init>", funcsig("V", kString, kTextFont, "F").c_str());
671     float font_size = text_object->font_size_;
672     jobject java_text_object =
673             env->NewObject(text_object_class, init_text_object, java_string, java_font, font_size);
674 
675     // Set Java PdfTextObject Render Mode.
676     int render_mode = static_cast<int>(text_object->render_mode_);
677     static jmethodID set_render_mode = env->GetMethodID(text_object_class, "setRenderMode", "(I)V");
678     env->CallVoidMethod(java_text_object, set_render_mode, render_mode);
679 
680     // Set Java PdfTextObject Fill Color.
681     static jmethodID set_fill_color = env->GetMethodID(text_object_class, "setFillColor", "(I)V");
682     env->CallVoidMethod(java_text_object, set_fill_color, ToJavaColorInt(text_object->fill_color_));
683 
684     // Set Java PdfTextObject Stroke Color.
685     static jmethodID set_stroke_color =
686             env->GetMethodID(text_object_class, "setStrokeColor", "(I)V");
687     env->CallVoidMethod(java_text_object, set_stroke_color,
688                         ToJavaColorInt(text_object->stroke_color_));
689 
690     // Set Java PdfTextObject Stroke Width.
691     static jmethodID set_stroke_width =
692             env->GetMethodID(text_object_class, "setStrokeWidth", "(F)V");
693     env->CallVoidMethod(java_text_object, set_stroke_width, text_object->stroke_width_);
694 
695     return java_text_object;
696 }
697 
ToJavaPdfPathObject(JNIEnv * env,const PathObject * path_object,ICoordinateConverter * converter)698 jobject ToJavaPdfPathObject(JNIEnv* env, const PathObject* path_object,
699                             ICoordinateConverter* converter) {
700     // Find Java PdfPathObject Class.
701     static jclass path_object_class = GetPermClassRef(env, kPathObject);
702     // Get Constructor Id.
703     static jmethodID init_path =
704             env->GetMethodID(path_object_class, "<init>", funcsig("V", kPath).c_str());
705 
706     // Create Java Path from Native PathSegments.
707     jobject java_path = ToJavaPath(env, path_object->segments_, converter);
708 
709     // Create Java PdfPathObject Instance.
710     jobject java_path_object = env->NewObject(path_object_class, init_path, java_path);
711 
712     // Set Java PdfPathObject FillColor.
713     if (path_object->is_fill_) {
714         static jmethodID set_fill_color =
715                 env->GetMethodID(path_object_class, "setFillColor", funcsig("V", "I").c_str());
716 
717         env->CallVoidMethod(java_path_object, set_fill_color,
718                             ToJavaColorInt(path_object->fill_color_));
719     }
720 
721     // Set Java PdfPathObject StrokeColor.
722     if (path_object->is_stroke_) {
723         static jmethodID set_stroke_color =
724                 env->GetMethodID(path_object_class, "setStrokeColor", funcsig("V", "I").c_str());
725 
726         env->CallVoidMethod(java_path_object, set_stroke_color,
727                             ToJavaColorInt(path_object->stroke_color_));
728     }
729 
730     // Set Java Stroke Width.
731     static jmethodID set_stroke_width =
732             env->GetMethodID(path_object_class, "setStrokeWidth", "(F)V");
733     env->CallVoidMethod(java_path_object, set_stroke_width, path_object->stroke_width_);
734 
735     return java_path_object;
736 }
737 
ToJavaPdfImageObject(JNIEnv * env,const ImageObject * image_object)738 jobject ToJavaPdfImageObject(JNIEnv* env, const ImageObject* image_object) {
739     // Find Java ImageObject Class.
740     static jclass image_object_class = GetPermClassRef(env, kImageObject);
741     // Get Constructor Id.
742     static jmethodID init_image =
743             env->GetMethodID(image_object_class, "<init>", funcsig("V", kBitmap).c_str());
744 
745     // Create Java Bitmap from Native Bitmap Buffer.
746     void* buffer = image_object->GetBitmapBuffer();
747     BitmapFormat bitmap_format = image_object->bitmap_format_;
748     size_t width = image_object->width_;
749     size_t height = image_object->height_;
750     int stride = FPDFBitmap_GetStride(image_object->bitmap_.get());
751     jobject java_bitmap = ToJavaBitmap(env, buffer, bitmap_format, width, height, stride);
752     if (java_bitmap == NULL) {
753         LOGE("To java bitmap conversion failed!");
754         return NULL;
755     }
756 
757     // Create Java PdfImageObject Instance.
758     jobject java_image_object = env->NewObject(image_object_class, init_image, java_bitmap);
759 
760     return java_image_object;
761 }
762 
ToJavaPdfPageObject(JNIEnv * env,const PageObject * page_object,ICoordinateConverter * converter)763 jobject ToJavaPdfPageObject(JNIEnv* env, const PageObject* page_object,
764                             ICoordinateConverter* converter) {
765     // Check for Native Supported Object.
766     if (!page_object) {
767         return NULL;
768     }
769 
770     jobject java_page_object = NULL;
771 
772     switch (page_object->GetType()) {
773         case PageObject::Type::Path: {
774             const PathObject* path_object = static_cast<const PathObject*>(page_object);
775             java_page_object = ToJavaPdfPathObject(env, path_object, converter);
776             break;
777         }
778         case PageObject::Type::Image: {
779             const ImageObject* image_object = static_cast<const ImageObject*>(page_object);
780             java_page_object = ToJavaPdfImageObject(env, image_object);
781             break;
782         }
783         default:
784             break;
785     }
786 
787     // If no PageObject was created, return null
788     if (java_page_object == NULL) {
789         return NULL;
790     }
791 
792     // Find Java PageObject Class.
793     static jclass page_object_class = GetPermClassRef(env, kPageObject);
794 
795     // Set Java PdfPageObject Matrix.
796     static jmethodID set_matrix =
797             env->GetMethodID(page_object_class, "setMatrix", funcsig("V", kMatrix).c_str());
798     env->CallVoidMethod(java_page_object, set_matrix,
799                         ToJavaMatrix(env, page_object->device_matrix_));
800 
801     return java_page_object;
802 }
803 
ToJavaPdfPageObjects(JNIEnv * env,const vector<PageObject * > & page_objects,ICoordinateConverter * converter)804 jobject ToJavaPdfPageObjects(JNIEnv* env, const vector<PageObject*>& page_objects,
805                              ICoordinateConverter* converter) {
806     return ToJavaList(env, page_objects, converter, &ToJavaPdfPageObject);
807 }
808 
ToNativeColor(jint java_color_int)809 Color ToNativeColor(jint java_color_int) {
810     // Decoding RGBA components
811     unsigned int red = (java_color_int >> 16) & 0xFF;
812     unsigned int green = (java_color_int >> 8) & 0xFF;
813     unsigned int blue = java_color_int & 0xFF;
814     unsigned int alpha = (java_color_int >> 24) & 0xFF;
815 
816     return Color(red, green, blue, alpha);
817 }
818 
ToNativeColor(JNIEnv * env,jobject java_color)819 Color ToNativeColor(JNIEnv* env, jobject java_color) {
820     // Find Java Color class
821     static jclass color_class = GetPermClassRef(env, kColor);
822 
823     // Get the color as an ARGB integer
824     jmethodID get_color_int = env->GetMethodID(color_class, "toArgb", funcsig("I").c_str());
825     jint java_color_int = env->CallIntMethod(java_color, get_color_int);
826 
827     return ToNativeColor(java_color_int);
828 }
829 
ToNativeTextObject(JNIEnv * env,jobject java_text_object)830 std::unique_ptr<TextObject> ToNativeTextObject(JNIEnv* env, jobject java_text_object) {
831     // Create TextObject Data Instance.
832     auto text_object = std::make_unique<TextObject>();
833 
834     // Get Ref to Java PdfTextObject Class.
835     static jclass text_object_class = GetPermClassRef(env, kTextObject);
836 
837     // Get Java PdfTextObject Font.
838     static jmethodID get_text_font =
839             env->GetMethodID(text_object_class, "getFont", funcsig(kTextFont).c_str());
840     jobject java_text_font = env->CallObjectMethod(java_text_object, get_text_font);
841 
842     // Find Java PdfTextObjectFont Class.
843     static jclass text_font_class = GetPermClassRef(env, kTextFont);
844 
845     // Get the Font Family for the PdfTextObjectFont.
846     static jmethodID get_font_family = env->GetMethodID(text_font_class, "getFontFamily", "()I");
847     jint font_family = env->CallIntMethod(java_text_font, get_font_family);
848 
849     // Is PdfTextObjectFont Bold.
850     static jmethodID is_bold = env->GetMethodID(text_font_class, "isBold", "()Z");
851     jboolean bold = env->CallBooleanMethod(java_text_font, is_bold);
852 
853     // Is PdfTextObjectFont Italic.
854     static jmethodID is_italic = env->GetMethodID(text_font_class, "isItalic", "()Z");
855     jboolean italic = env->CallBooleanMethod(java_text_font, is_italic);
856 
857     // Set TextObject Data Font.
858     if (font_family < 0 || font_family >= font_names.size()) {
859         return nullptr;
860     }
861     text_object->font_ =
862             Font(font_names[font_family], static_cast<Font::Family>(font_family), bold, italic);
863 
864     // Get Java PdfTextObject font size.
865     static jmethodID get_font_size = env->GetMethodID(text_object_class, "getFontSize", "()F");
866     jfloat font_size = env->CallFloatMethod(java_text_object, get_font_size);
867 
868     // Set TextObject Data font size.
869     text_object->font_size_ = font_size;
870 
871     // Get Java PdfTextObject Text.
872     static jmethodID get_text =
873             env->GetMethodID(text_object_class, "getText", funcsig(kString).c_str());
874     jstring java_text = static_cast<jstring>(env->CallObjectMethod(java_text_object, get_text));
875 
876     // Set TextObject Data Text.
877     text_object->text_ = ToNativeWideString(env, java_text);
878 
879     // Get Java PdfTextObject RenderMode.
880     static jmethodID get_render_mode = env->GetMethodID(text_object_class, "getRenderMode", "()I");
881     jint render_mode = env->CallIntMethod(java_text_object, get_render_mode);
882 
883     // Set TextObject Data RenderMode.
884     switch (static_cast<TextObject::RenderMode>(render_mode)) {
885         case TextObject::RenderMode::Fill: {
886             text_object->render_mode_ = TextObject::RenderMode::Fill;
887             break;
888         }
889         case TextObject::RenderMode::Stroke: {
890             text_object->render_mode_ = TextObject::RenderMode::Stroke;
891             break;
892         }
893         case TextObject::RenderMode::FillStroke: {
894             text_object->render_mode_ = TextObject::RenderMode::FillStroke;
895             break;
896         }
897         default: {
898             text_object->render_mode_ = TextObject::RenderMode::Unknown;
899             break;
900         }
901     }
902 
903     // Get Java PdfTextObject Fill Color.
904     static jmethodID get_fill_color = env->GetMethodID(text_object_class, "getFillColor", "()I");
905     jint java_fill_color = env->CallIntMethod(java_text_object, get_fill_color);
906 
907     // Set TextObject Data Fill Color
908     text_object->fill_color_ = ToNativeColor(java_fill_color);
909 
910     // Get Java PdfTextObject Stroke Color.
911     static jmethodID get_stroke_color =
912             env->GetMethodID(text_object_class, "getStrokeColor", "()I");
913     jint java_stroke_color = env->CallIntMethod(java_text_object, get_stroke_color);
914 
915     // Set TextObject Data Stroke Color.
916     text_object->stroke_color_ = ToNativeColor(java_stroke_color);
917 
918     // Get Java PdfTextObject Stroke Width.
919     static jmethodID get_stroke_width =
920             env->GetMethodID(text_object_class, "getStrokeWidth", "()F");
921     jfloat stroke_width = env->CallFloatMethod(java_text_object, get_stroke_width);
922 
923     // Set TextObject Data Stroke Width.
924     text_object->stroke_width_ = stroke_width;
925 
926     return text_object;
927 }
928 
ToNativePathObject(JNIEnv * env,jobject java_path_object,ICoordinateConverter * converter)929 std::unique_ptr<PathObject> ToNativePathObject(JNIEnv* env, jobject java_path_object,
930                                                ICoordinateConverter* converter) {
931     // Create PathObject Data Instance.
932     auto path_object = std::make_unique<PathObject>();
933 
934     // Get Ref to Java PathObject Class.
935     static jclass path_object_class = GetPermClassRef(env, kPathObject);
936 
937     // Get Path from Java PathObject.
938     static jmethodID to_path =
939             env->GetMethodID(path_object_class, "toPath", funcsig(kPath).c_str());
940     jobject java_path = env->CallObjectMethod(java_path_object, to_path);
941 
942     // Find Java Path Class.
943     static jclass path_class = GetPermClassRef(env, kPath);
944 
945     // Get the Approximate Array for the Path.
946     static jmethodID approximate = env->GetMethodID(path_class, "approximate", "(F)[F");
947     // The acceptable error while approximating a Path Curve with a line.
948     static const float acceptable_error = 0.5f;
949     jfloatArray java_approximate =
950             (jfloatArray)env->CallObjectMethod(java_path, approximate, acceptable_error);
951     const jsize size = env->GetArrayLength(java_approximate);
952 
953     // Copy Java Array to Native Array
954     float path_approximate[size];
955     env->GetFloatArrayRegion(java_approximate, 0, size, path_approximate);
956 
957     // Set PathObject Data PathSegments.
958     auto& segments = path_object->segments_;
959     for (int i = 0; i < size; i += 3) {
960         // Get DeviceToPage Coordinates
961         Point_f output =
962                 converter->DeviceToPage({path_approximate[i + 1], path_approximate[i + 2]});
963         if (i == 0 || path_approximate[i] == path_approximate[i - 3]) {
964             segments.emplace_back(PathObject::Segment::Command::Move, output.x, output.y);
965         } else {
966             segments.emplace_back(PathObject::Segment::Command::Line, output.x, output.y);
967         }
968     }
969 
970     // Get Java PathObject Fill Color.
971     static jmethodID get_fill_color =
972             env->GetMethodID(path_object_class, "getFillColor", funcsig("I").c_str());
973     jint java_fill_color = env->CallIntMethod(java_path_object, get_fill_color);
974 
975     // Set PathObject Data Fill Mode and Fill Color
976     path_object->is_fill_ = (java_fill_color != 0);
977     if (path_object->is_fill_) {
978         path_object->fill_color_ = ToNativeColor(java_fill_color);
979     }
980 
981     // Get Java PathObject Stroke Color.
982     static jmethodID get_stroke_color =
983             env->GetMethodID(path_object_class, "getStrokeColor", funcsig("I").c_str());
984     jint java_stroke_color = env->CallIntMethod(java_path_object, get_stroke_color);
985 
986     // Set PathObject Data Stroke Mode and Stroke Color.
987     path_object->is_stroke_ = (java_stroke_color != 0);
988     if (path_object->is_stroke_) {
989         path_object->stroke_color_ = ToNativeColor(java_stroke_color);
990     }
991 
992     // Get Java PathObject Stroke Width.
993     static jmethodID get_stroke_width =
994             env->GetMethodID(path_object_class, "getStrokeWidth", funcsig("F").c_str());
995     jfloat stroke_width = env->CallFloatMethod(java_path_object, get_stroke_width);
996 
997     // Set PathObject Data Stroke Width.
998     path_object->stroke_width_ = stroke_width;
999 
1000     return path_object;
1001 }
1002 
CopyRgbaToBgra(uint8_t * rgba_pixel_array,size_t rgba_stride,uint32_t * bgra_pixel_array,size_t bgra_stride,size_t width,size_t height)1003 void CopyRgbaToBgra(uint8_t* rgba_pixel_array, size_t rgba_stride, uint32_t* bgra_pixel_array,
1004                     size_t bgra_stride, size_t width, size_t height) {
1005     for (size_t y = 0; y < height; y++) {
1006         uint8_t* rgba_row_ptr = rgba_pixel_array + y * rgba_stride;
1007         uint32_t* bgra_row_ptr = bgra_pixel_array + y * (bgra_stride / 4);
1008         for (size_t x = 0; x < width; x++) {
1009             // Extract RGBA components stored.
1010             uint8_t red = rgba_row_ptr[x * 4];
1011             uint8_t green = rgba_row_ptr[x * 4 + 1];
1012             uint8_t blue = rgba_row_ptr[x * 4 + 2];
1013             uint8_t alpha = rgba_row_ptr[x * 4 + 3];
1014             // Storing native bitmap components BGRA in little-endian.
1015             bgra_row_ptr[x] = (alpha << 24) | (red << 16) | (green << 8) | blue;
1016         }
1017     }
1018 }
1019 
ToNativeImageObject(JNIEnv * env,jobject java_image_object)1020 std::unique_ptr<ImageObject> ToNativeImageObject(JNIEnv* env, jobject java_image_object) {
1021     // Create ImageObject Data Instance.
1022     auto image_object = std::make_unique<ImageObject>();
1023 
1024     // Get Ref to Java ImageObject Class.
1025     static jclass image_object_class = GetPermClassRef(env, kImageObject);
1026 
1027     // Get the bitmap from the Java ImageObject.
1028     static jmethodID get_bitmap =
1029             env->GetMethodID(image_object_class, "getBitmap", funcsig(kBitmap).c_str());
1030     jobject java_bitmap = env->CallObjectMethod(java_image_object, get_bitmap);
1031 
1032     // Get android bitmap info.
1033     AndroidBitmapInfo bitmap_info;
1034     AndroidBitmap_getInfo(env, java_bitmap, &bitmap_info);
1035     if (bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
1036         LOGE("Android bitmap is not in RGBA_8888 format");
1037         return nullptr;
1038     }
1039     size_t bitmap_width = bitmap_info.width;
1040     size_t bitmap_height = bitmap_info.height;
1041     size_t java_stride = bitmap_info.stride;
1042 
1043     // Create ImageObject data bitmap.
1044     image_object->bitmap_ = ScopedFPDFBitmap(FPDFBitmap_Create(bitmap_width, bitmap_height, 1));
1045     size_t native_stride = FPDFBitmap_GetStride(image_object->bitmap_.get());
1046 
1047     // Copy pixels from android bitmap.
1048     void* bitmap_pixels;
1049     if (AndroidBitmap_lockPixels(env, java_bitmap, &bitmap_pixels) < 0) {
1050         LOGE("Android bitmap lock pixels failed!");
1051         return nullptr;
1052     }
1053 
1054     uint8_t* java_pixel_array = static_cast<uint8_t*>(bitmap_pixels);
1055     uint32_t* native_pixel_array = static_cast<uint32_t*>(image_object->GetBitmapBuffer());
1056 
1057     CopyRgbaToBgra(java_pixel_array, java_stride, native_pixel_array, native_stride, bitmap_width,
1058                    bitmap_height);
1059 
1060     AndroidBitmap_unlockPixels(env, java_bitmap);
1061 
1062     return image_object;
1063 }
1064 
ToNativePageObject(JNIEnv * env,jobject java_page_object,ICoordinateConverter * converter)1065 std::unique_ptr<PageObject> ToNativePageObject(JNIEnv* env, jobject java_page_object,
1066                                                ICoordinateConverter* converter) {
1067     // Find Java PageObject class and GetType
1068     static jclass page_object_class = GetPermClassRef(env, kPageObject);
1069     static jmethodID get_type = env->GetMethodID(page_object_class, "getPdfObjectType", "()I");
1070     jint page_object_type = env->CallIntMethod(java_page_object, get_type);
1071 
1072     // Pointer to PageObject
1073     std::unique_ptr<PageObject> page_object = nullptr;
1074 
1075     switch (static_cast<PageObject::Type>(page_object_type)) {
1076         case PageObject::Type::Path: {
1077             page_object = ToNativePathObject(env, java_page_object, converter);
1078             break;
1079         }
1080         case PageObject::Type::Image: {
1081             page_object = ToNativeImageObject(env, java_page_object);
1082             break;
1083         }
1084         default:
1085             break;
1086     }
1087 
1088     if (!page_object) {
1089         return nullptr;
1090     }
1091 
1092     // Get Matrix from Java PageObject.
1093     static jmethodID get_matrix = env->GetMethodID(page_object_class, "getMatrix", "()[F");
1094     jfloatArray java_matrix_array =
1095                              (jfloatArray)env->CallObjectMethod(java_page_object, get_matrix);
1096 
1097     // Copy Java Array to Native Array
1098     float transform[9];
1099     env->GetFloatArrayRegion(java_matrix_array, 0, 9, transform);
1100 
1101     // Set PageObject Data Matrix.
1102     page_object->device_matrix_ = {transform[0 /*kMScaleX*/], transform[3 /*kMSkewY*/],
1103                                    transform[1 /*kMSkewX*/],  transform[4 /*kMScaleY*/],
1104                                    transform[2 /*kMTransX*/], transform[5 /*kMTransY*/]};
1105 
1106     return page_object;
1107 }
1108 
ToJavaPageAnnotations(JNIEnv * env,const vector<Annotation * > & annotations,ICoordinateConverter * converter)1109 jobject ToJavaPageAnnotations(JNIEnv* env, const vector<Annotation*>& annotations,
1110                               ICoordinateConverter* converter) {
1111     return ToJavaList(env, annotations, converter, &ToJavaPageAnnotation);
1112 }
1113 
ToJavaStampAnnotation(JNIEnv * env,const Annotation * annotation,ICoordinateConverter * converter)1114 jobject ToJavaStampAnnotation(JNIEnv* env, const Annotation* annotation,
1115                               ICoordinateConverter* converter) {
1116     // Cast to StampAnnotation
1117     const StampAnnotation* stamp_annotation = static_cast<const StampAnnotation*>(annotation);
1118     jobject java_bounds = ToJavaRectF(env, stamp_annotation->GetBounds(), converter);
1119 
1120     // Find Java StampAnnotation Class.
1121     static jclass stamp_annotation_class = GetPermClassRef(env, kStampAnnotation);
1122     // Get Constructor Id.
1123     static jmethodID init =
1124             env->GetMethodID(stamp_annotation_class, "<init>", funcsig("V", kRectF).c_str());
1125 
1126     // Create Java StampAnnotation Instance.
1127     jobject java_annotation = env->NewObject(stamp_annotation_class, init, java_bounds);
1128 
1129     // Add page objects to stamp annotation
1130 
1131     // Get methodId for addObject
1132     static jmethodID add_object = env->GetMethodID(stamp_annotation_class, "addObject",
1133                                                    funcsig("V", kPageObject).c_str());
1134 
1135     std::vector<PageObject*> page_objects = stamp_annotation->GetObjects();
1136 
1137     for (const auto& page_object : page_objects) {
1138         jobject java_page_object = ToJavaPdfPageObject(env, page_object, converter);
1139         env->CallVoidMethod(java_annotation, add_object, java_page_object);
1140     }
1141     return java_annotation;
1142 }
1143 
ToJavaHighlightAnnotation(JNIEnv * env,const Annotation * annotation,ICoordinateConverter * converter)1144 jobject ToJavaHighlightAnnotation(JNIEnv* env, const Annotation* annotation,
1145                                   ICoordinateConverter* converter) {
1146     // Cast to HighlightAnnotation
1147     const HighlightAnnotation* highlight_annotation =
1148             static_cast<const HighlightAnnotation*>(annotation);
1149     jobject java_bounds =
1150             ToJavaList(env, highlight_annotation->GetBounds(), converter, &ToJavaRectF);
1151 
1152     // Find Java HighlightAnnotation Class.
1153     static jclass highlight_annotation_class = GetPermClassRef(env, kHighlightAnnotation);
1154     // Get Constructor Id.
1155     static jmethodID init =
1156             env->GetMethodID(highlight_annotation_class, "<init>", funcsig("V", kList).c_str());
1157 
1158     // Create Java HighlightAnnotation Instance.
1159     jobject java_annotation = env->NewObject(highlight_annotation_class, init, java_bounds);
1160 
1161     // Get and set highlight color
1162     // Get method Id for setColor.
1163     static jmethodID set_color =
1164             env->GetMethodID(highlight_annotation_class, "setColor", funcsig("V", "I").c_str());
1165     // call setColor
1166     env->CallVoidMethod(java_annotation, set_color,
1167                         ToJavaColorInt(highlight_annotation->GetColor()));
1168 
1169     return java_annotation;
1170 }
1171 
ToJavaFreeTextAnnotation(JNIEnv * env,const Annotation * annotation,ICoordinateConverter * converter)1172 jobject ToJavaFreeTextAnnotation(JNIEnv* env, const Annotation* annotation,
1173                                  ICoordinateConverter* converter) {
1174     // Cast to FreeText Annotation
1175     const FreeTextAnnotation* freetext_annotation =
1176             static_cast<const FreeTextAnnotation*>(annotation);
1177 
1178     jobject java_bounds = ToJavaRectF(env, freetext_annotation->GetBounds(), converter);
1179     // Find Java FreeTextAnnotation class.
1180     static jclass freetext_annotation_class = GetPermClassRef(env, kFreeTextAnnotation);
1181     // Get Constructor Id.
1182     static jmethodID init = env->GetMethodID(freetext_annotation_class, "<init>",
1183                                              funcsig("V", kRectF, kString).c_str());
1184 
1185     // Get Java String for text content.
1186     jobject java_string = ToJavaString(env, freetext_annotation->GetTextContent());
1187     // Create Java FreeTextAnnotation Object.
1188     jobject java_freetext_annotation =
1189             env->NewObject(freetext_annotation_class, init, java_bounds, java_string);
1190 
1191     // Set Text color.
1192     static jmethodID set_text_color =
1193             env->GetMethodID(freetext_annotation_class, "setTextColor", funcsig("V", "I").c_str());
1194     // call setTextColor
1195     env->CallVoidMethod(java_freetext_annotation, set_text_color,
1196                         ToJavaColorInt(freetext_annotation->GetTextColor()));
1197 
1198     // Set Background color.
1199     static jmethodID set_background_color = env->GetMethodID(
1200             freetext_annotation_class, "setBackgroundColor", funcsig("V", "I").c_str());
1201     // call setBackgroundColor
1202     env->CallVoidMethod(java_freetext_annotation, set_background_color,
1203                         ToJavaColorInt(freetext_annotation->GetBackgroundColor()));
1204 
1205     return java_freetext_annotation;
1206 }
1207 
ToJavaPageAnnotation(JNIEnv * env,const Annotation * annotation,ICoordinateConverter * converter)1208 jobject ToJavaPageAnnotation(JNIEnv* env, const Annotation* annotation,
1209                              ICoordinateConverter* converter) {
1210     if (!annotation) {
1211         return NULL;
1212     }
1213 
1214     jobject java_annotation = nullptr;
1215 
1216     switch (annotation->GetType()) {
1217         case Annotation::Type::Stamp: {
1218             java_annotation = ToJavaStampAnnotation(env, annotation, converter);
1219             break;
1220         }
1221         case Annotation::Type::Highlight: {
1222             java_annotation = ToJavaHighlightAnnotation(env, annotation, converter);
1223             break;
1224         }
1225         case Annotation::Type::FreeText: {
1226             java_annotation = ToJavaFreeTextAnnotation(env, annotation, converter);
1227             break;
1228         }
1229         default:
1230             break;
1231     }
1232 
1233     return java_annotation;
1234 }
1235 
ToNativeStampAnnotation(JNIEnv * env,jobject java_annotation,ICoordinateConverter * converter)1236 std::unique_ptr<Annotation> ToNativeStampAnnotation(JNIEnv* env, jobject java_annotation,
1237                                                     ICoordinateConverter* converter) {
1238     // Get Ref to Java StampAnnotation Class.
1239     static jclass stamp_annotation_class = GetPermClassRef(env, kStampAnnotation);
1240 
1241     jmethodID get_bounds =
1242             env->GetMethodID(stamp_annotation_class, "getBounds", funcsig(kRectF).c_str());
1243     jobject java_bounds = env->CallObjectMethod(java_annotation, get_bounds);
1244     Rectangle_f native_bounds = ToNativeRectF(env, java_bounds, converter);
1245 
1246     // Create StampAnnotation Instance.
1247     auto stamp_annotation = std::make_unique<StampAnnotation>(native_bounds);
1248 
1249     // Get PdfPageObjects from stamp annotation
1250     static jmethodID get_objects =
1251             env->GetMethodID(stamp_annotation_class, "getObjects", funcsig(kList).c_str());
1252     jobject java_page_objects = env->CallObjectMethod(java_annotation, get_objects);
1253 
1254     jclass list_class = env->FindClass(kList);
1255     jmethodID size_method = env->GetMethodID(list_class, "size", funcsig("I").c_str());
1256     jmethodID get_method = env->GetMethodID(list_class, "get", funcsig(kObject, "I").c_str());
1257 
1258     jint listSize = env->CallIntMethod(java_page_objects, size_method);
1259     for (int i = 0; i < listSize; i++) {
1260         jobject java_page_object = env->CallObjectMethod(java_page_objects, get_method, i);
1261         std::unique_ptr<PageObject> native_page_object =
1262                 ToNativePageObject(env, java_page_object, converter);
1263         stamp_annotation->AddObject(std::move(native_page_object));
1264     }
1265     return stamp_annotation;
1266 }
1267 
ToNativeHighlightAnnotation(JNIEnv * env,jobject java_annotation,ICoordinateConverter * converter)1268 std::unique_ptr<Annotation> ToNativeHighlightAnnotation(JNIEnv* env, jobject java_annotation,
1269                                                         ICoordinateConverter* converter) {
1270     // Get Ref to Java HighlightAnnotation Class.
1271     static jclass highlight_annotation_class = GetPermClassRef(env, kHighlightAnnotation);
1272 
1273     jmethodID get_bounds =
1274             env->GetMethodID(highlight_annotation_class, "getBounds", funcsig(kList).c_str());
1275     jobject java_bounds = env->CallObjectMethod(java_annotation, get_bounds);
1276 
1277     vector<Rectangle_f> native_bounds;
1278 
1279     jclass list_class = env->FindClass(kList);
1280     jmethodID size_method = env->GetMethodID(list_class, "size", funcsig("I").c_str());
1281     jmethodID get_method = env->GetMethodID(list_class, "get", funcsig(kObject, "I").c_str());
1282 
1283     jint listSize = env->CallIntMethod(java_bounds, size_method);
1284     for (int i = 0; i < listSize; i++) {
1285         jobject java_bound = env->CallObjectMethod(java_bounds, get_method, i);
1286         Rectangle_f native_bound = ToNativeRectF(env, java_bound, converter);
1287         native_bounds.push_back(native_bound);
1288     }
1289 
1290     // Create HighlightAnnotation Instance.
1291     auto highlight_annotation = std::make_unique<HighlightAnnotation>(native_bounds);
1292 
1293     // Get and set highlight color
1294 
1295     // Get methodId for getColor
1296     static jmethodID get_color =
1297             env->GetMethodID(highlight_annotation_class, "getColor", funcsig("I").c_str());
1298     jint java_color_int = env->CallIntMethod(java_annotation, get_color);
1299 
1300     highlight_annotation->SetColor(ToNativeColor(java_color_int));
1301 
1302     return highlight_annotation;
1303 }
1304 
ToNativeFreeTextAnnotation(JNIEnv * env,jobject java_annotation,ICoordinateConverter * converter)1305 std::unique_ptr<Annotation> ToNativeFreeTextAnnotation(JNIEnv* env, jobject java_annotation,
1306                                                        ICoordinateConverter* converter) {
1307     // Get Ref to Java FreeTextAnnotation Class.
1308     static jclass freetext_annotation_class = GetPermClassRef(env, kFreeTextAnnotation);
1309 
1310     jmethodID get_bounds =
1311             env->GetMethodID(freetext_annotation_class, "getBounds", funcsig(kRectF).c_str());
1312     jobject java_bounds = env->CallObjectMethod(java_annotation, get_bounds);
1313     Rectangle_f native_bounds = ToNativeRectF(env, java_bounds, converter);
1314 
1315     // Create FreeTextAnnotation Instance.
1316     auto freetext_annotation = std::make_unique<FreeTextAnnotation>(native_bounds);
1317 
1318     // Get the TextContent from Java layer.
1319     static jmethodID get_text_content =
1320             env->GetMethodID(freetext_annotation_class, "getTextContent", funcsig(kString).c_str());
1321     auto java_text_content =
1322             static_cast<jstring>(env->CallObjectMethod(java_annotation, get_text_content));
1323 
1324     // Set the TextContent
1325     std::wstring native_text_content = ToNativeWideString(env, java_text_content);
1326     freetext_annotation->SetTextContent(native_text_content);
1327 
1328     // Get the text color
1329     static jmethodID get_text_color =
1330             env->GetMethodID(freetext_annotation_class, "getTextColor", funcsig("I").c_str());
1331     jint java_text_color_int = env->CallIntMethod(java_annotation, get_text_color);
1332 
1333     freetext_annotation->SetTextColor(ToNativeColor(java_text_color_int));
1334 
1335     // Get the background color
1336     static jmethodID get_background_color =
1337             env->GetMethodID(freetext_annotation_class, "getBackgroundColor", funcsig("I").c_str());
1338     jint java_background_color_int = env->CallIntMethod(java_annotation, get_background_color);
1339 
1340     freetext_annotation->SetBackgroundColor(ToNativeColor(java_background_color_int));
1341 
1342     return freetext_annotation;
1343 }
1344 
ToNativePageAnnotation(JNIEnv * env,jobject java_annotation,ICoordinateConverter * converter)1345 std::unique_ptr<Annotation> ToNativePageAnnotation(JNIEnv* env, jobject java_annotation,
1346                                                    ICoordinateConverter* converter) {
1347     // Find Java PdfAnnotation class and GetType
1348     static jclass annotation_class = GetPermClassRef(env, kPdfAnnotation);
1349     static jmethodID get_type =
1350             env->GetMethodID(annotation_class, "getPdfAnnotationType", funcsig("I").c_str());
1351     jint annotation_type = env->CallIntMethod(java_annotation, get_type);
1352 
1353     std::unique_ptr<Annotation> annotation = nullptr;
1354 
1355     switch (static_cast<Annotation::Type>(annotation_type)) {
1356         case Annotation::Type::Stamp: {
1357             annotation = ToNativeStampAnnotation(env, java_annotation, converter);
1358             break;
1359         }
1360         case Annotation::Type::Highlight: {
1361             annotation = ToNativeHighlightAnnotation(env, java_annotation, converter);
1362             break;
1363         }
1364         case Annotation::Type::FreeText: {
1365             annotation = ToNativeFreeTextAnnotation(env, java_annotation, converter);
1366             break;
1367         }
1368         default:
1369             break;
1370     }
1371 
1372     return annotation;
1373 }
1374 
1375 }  // namespace convert