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