1 /*
2 * Copyright (C) 2025 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 "path_object.h"
18
19 #include <stddef.h>
20 #include <stdint.h>
21
22 #include "fpdf_edit.h"
23 #include "logging.h"
24 #include "rect.h"
25
26 #define LOG_TAG "path_object"
27
28 namespace pdfClient {
29
PathObject()30 PathObject::PathObject() : PageObject(Type::Path) {}
31
CreateFPDFInstance(FPDF_DOCUMENT document,FPDF_PAGE page)32 ScopedFPDFPageObject PathObject::CreateFPDFInstance(FPDF_DOCUMENT document, FPDF_PAGE page) {
33 int segment_count = segments_.size();
34 if (segment_count == 0) {
35 return nullptr;
36 }
37 // Get Start Points
38 float x = segments_[0].x;
39 float y = segments_[0].y;
40
41 // Create a scoped PDFium path object.
42 ScopedFPDFPageObject scoped_path_object(FPDFPageObj_CreateNewPath(x, y));
43 if (!scoped_path_object) {
44 return nullptr;
45 }
46
47 // Insert all segments into PDFium Path Object
48 for (int i = 1; i < segment_count; ++i) {
49 // Get EndPoint for current segment.
50 x = segments_[i].x;
51 y = segments_[i].y;
52 switch (segments_[i].command) {
53 case Segment::Command::Move: {
54 FPDFPath_MoveTo(scoped_path_object.get(), x, y);
55 break;
56 }
57 case Segment::Command::Line: {
58 FPDFPath_LineTo(scoped_path_object.get(), x, y);
59 break;
60 }
61 default:
62 break;
63 }
64 if (segments_[i].is_closed) {
65 FPDFPath_Close(scoped_path_object.get());
66 }
67 }
68
69 // Update attributes of PDFium path object.
70 if (!UpdateFPDFInstance(scoped_path_object.get(), page)) {
71 return nullptr;
72 }
73
74 return scoped_path_object;
75 }
76
UpdateFPDFInstance(FPDF_PAGEOBJECT path_object,FPDF_PAGE page)77 bool PathObject::UpdateFPDFInstance(FPDF_PAGEOBJECT path_object, FPDF_PAGE page) {
78 if (!path_object) {
79 return false;
80 }
81
82 // Check for Type Correctness.
83 if (FPDFPageObj_GetType(path_object) != FPDF_PAGEOBJ_PATH) {
84 return false;
85 }
86
87 // Set the updated Draw Mode
88 int fill_mode = this->is_fill_ ? FPDF_FILLMODE_WINDING : FPDF_FILLMODE_NONE;
89 if (!FPDFPath_SetDrawMode(path_object, fill_mode, is_stroke_)) {
90 return false;
91 }
92
93 // Set the updated matrix.
94 if (!SetDeviceToPageMatrix(path_object, page)) {
95 return false;
96 }
97
98 // Set the updated Stroke Width
99 FPDFPageObj_SetStrokeWidth(path_object, stroke_width_);
100 // Set the updated Stroke Color
101 FPDFPageObj_SetStrokeColor(path_object, stroke_color_.r, stroke_color_.g, stroke_color_.b,
102 stroke_color_.a);
103 // Set the updated Fill Color
104 FPDFPageObj_SetFillColor(path_object, fill_color_.r, fill_color_.g, fill_color_.b,
105 fill_color_.a);
106
107 return true;
108 }
109
PopulateFromFPDFInstance(FPDF_PAGEOBJECT path_object,FPDF_PAGE page)110 bool PathObject::PopulateFromFPDFInstance(FPDF_PAGEOBJECT path_object, FPDF_PAGE page) {
111 // Count the number of segments in the Path
112 int segment_count = FPDFPath_CountSegments(path_object);
113 if (segment_count == 0) {
114 return false;
115 }
116
117 // Get Path segments
118 for (int index = 0; index < segment_count; ++index) {
119 FPDF_PATHSEGMENT path_segment = FPDFPath_GetPathSegment(path_object, index);
120
121 // Get Type for the current Path Segment
122 int type = FPDFPathSegment_GetType(path_segment);
123 if (type == FPDF_SEGMENT_UNKNOWN || type == FPDF_SEGMENT_BEZIERTO) {
124 // Control point extraction of bezier curve is not supported by Pdfium as of now.
125 return false;
126 }
127
128 // Get EndPoint for the current Path Segment
129 float x, y;
130 FPDFPathSegment_GetPoint(path_segment, &x, &y);
131
132 // Get Close for the current Path Segment
133 bool is_closed = FPDFPathSegment_GetClose(path_segment);
134
135 // Add Segment to PageObject Data
136 if (type == FPDF_SEGMENT_LINETO) {
137 segments_.emplace_back(Segment::Command::Line, x, y, is_closed);
138 } else {
139 segments_.emplace_back(Segment::Command::Move, x, y, is_closed);
140 }
141 }
142
143 // Get Draw Mode
144 int fill_mode;
145 FPDF_BOOL stroke;
146 if (!FPDFPath_GetDrawMode(path_object, &fill_mode, &stroke)) {
147 LOGE("Path GetDrawMode Failed!");
148 return false;
149 }
150 is_fill_ = fill_mode;
151 is_stroke_ = stroke;
152
153 // Get Matrix
154 if (!GetPageToDeviceMatrix(path_object, page)) {
155 return false;
156 }
157
158 // Get Fill Color
159 FPDFPageObj_GetFillColor(path_object, &fill_color_.r, &fill_color_.g, &fill_color_.b,
160 &fill_color_.a);
161 // Get Stroke Color
162 FPDFPageObj_GetStrokeColor(path_object, &stroke_color_.r, &stroke_color_.g, &stroke_color_.b,
163 &stroke_color_.a);
164 // Get Stroke Width
165 FPDFPageObj_GetStrokeWidth(path_object, &stroke_width_);
166
167 return true;
168 }
169
GetPageToDeviceMatrix(FPDF_PAGEOBJECT path_object,FPDF_PAGE page)170 bool PathObject::GetPageToDeviceMatrix(FPDF_PAGEOBJECT path_object, FPDF_PAGE page) {
171 Matrix page_matrix;
172 if (!FPDFPageObj_GetMatrix(path_object, reinterpret_cast<FS_MATRIX*>(&page_matrix))) {
173 LOGE("GetPageMatrix failed!");
174 return false;
175 }
176
177 float page_height = FPDF_GetPageHeightF(page);
178
179 // Page to device matrix.
180 device_matrix_.a = page_matrix.a;
181 device_matrix_.b = (page_matrix.b != 0) ? -page_matrix.b : 0;
182 device_matrix_.c = (page_matrix.c != 0) ? -page_matrix.c : 0;
183 device_matrix_.d = page_matrix.d;
184 device_matrix_.e = page_matrix.e + (page_height * page_matrix.c);
185 device_matrix_.f = page_height - page_matrix.f - (page_height * page_matrix.d);
186
187 return true;
188 }
189
SetDeviceToPageMatrix(FPDF_PAGEOBJECT path_object,FPDF_PAGE page)190 bool PathObject::SetDeviceToPageMatrix(FPDF_PAGEOBJECT path_object, FPDF_PAGE page) {
191 // Reset Previous Transformation.
192 Matrix identity = {1, 0, 0, 1, 0, 0};
193 if (!FPDFPageObj_SetMatrix(path_object, reinterpret_cast<FS_MATRIX*>(&identity))) {
194 LOGE("SetMatrix failed!");
195 return false;
196 }
197
198 float page_height = FPDF_GetPageHeightF(page);
199
200 FPDFPageObj_Transform(path_object, 1, 0, 0, 1, 0, -page_height);
201 FPDFPageObj_Transform(path_object, device_matrix_.a, -device_matrix_.b, -device_matrix_.c,
202 device_matrix_.d, device_matrix_.e, -device_matrix_.f);
203 FPDFPageObj_Transform(path_object, 1, 0, 0, 1, 0, page_height);
204
205 return true;
206 }
207
208 PathObject::~PathObject() = default;
209
210 } // namespace pdfClient