1 | //======================================================================== |
2 | // |
3 | // pdf-inspector.cc |
4 | // |
5 | // Copyright 2005 Jonathan Blandford <jrb@redhat.com> |
6 | // Copyright 2018 Adam Reichold <adam.reichold@t-online.de> |
7 | // Copyright 2019, 2022 Albert Astals Cid <aacid@kde.org> |
8 | // |
9 | //======================================================================== |
10 | |
11 | #include <config.h> |
12 | |
13 | #include <goo/gmem.h> |
14 | #include <goo/GooTimer.h> |
15 | #include <splash/SplashTypes.h> |
16 | #include <splash/SplashBitmap.h> |
17 | #include "Object.h" |
18 | #include "ProfileData.h" |
19 | #include "GfxState.h" |
20 | |
21 | #include <gdk/gdk.h> |
22 | #include "CairoOutputDev.h" |
23 | |
24 | #include "PDFDoc.h" |
25 | #include "GlobalParams.h" |
26 | #include "ErrorCodes.h" |
27 | #include <gtk/gtk.h> |
28 | |
29 | // Mapping |
30 | #include "pdf-operators.c" |
31 | |
32 | enum |
33 | { |
34 | OP_STRING, |
35 | OP_COUNT, |
36 | OP_TOTAL, |
37 | OP_MIN, |
38 | OP_MAX, |
39 | N_COLUMNS |
40 | }; |
41 | |
42 | class PdfInspector |
43 | { |
44 | public: |
45 | PdfInspector(); |
46 | |
47 | void set_file_name(const char *file_name); |
48 | void load(const char *file_name); |
49 | void run(); |
50 | void error_dialog(const char *error_message); |
51 | void analyze_page(int page); |
52 | |
53 | private: |
54 | static void on_file_activated(GtkWidget *widget, PdfInspector *inspector); |
55 | static void on_selection_changed(GtkTreeSelection *selection, PdfInspector *inspector); |
56 | static void on_analyze_clicked(GtkWidget *widget, PdfInspector *inspector); |
57 | |
58 | GtkBuilder *builder; |
59 | GtkTreeModel *model; |
60 | PDFDoc *doc; |
61 | CairoOutputDev *output; |
62 | }; |
63 | |
64 | PdfInspector::PdfInspector() |
65 | { |
66 | GtkWidget *widget; |
67 | GError *error = nullptr; |
68 | |
69 | builder = gtk_builder_new(); |
70 | |
71 | if (!gtk_builder_add_from_file(builder, SRC_DIR "/pdf-inspector.ui" , error: &error)) { |
72 | g_warning("Couldn't load builder file: %s" , error->message); |
73 | g_error_free(error); |
74 | } |
75 | |
76 | widget = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_file_chooser_button" )); |
77 | g_signal_connect(widget, "selection-changed" , G_CALLBACK(on_file_activated), this); |
78 | |
79 | widget = GTK_WIDGET(gtk_builder_get_object(builder, "analyze_button" )); |
80 | g_signal_connect(widget, "clicked" , G_CALLBACK(on_analyze_clicked), this); |
81 | |
82 | // setup the TreeView |
83 | widget = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_tree_view" )); |
84 | g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), "changed" , G_CALLBACK(on_selection_changed), this); |
85 | model = (GtkTreeModel *)gtk_list_store_new(n_columns: N_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
86 | gtk_tree_view_set_model(GTK_TREE_VIEW(widget), model); |
87 | gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), position: 0, title: "Operation" , cell: gtk_cell_renderer_text_new(), "text" , OP_STRING, NULL); |
88 | |
89 | gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), position: 1, title: "Count" , cell: gtk_cell_renderer_text_new(), "text" , OP_COUNT, NULL); |
90 | |
91 | gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), position: 2, title: "Elapsed" , cell: gtk_cell_renderer_text_new(), "text" , OP_TOTAL, NULL); |
92 | |
93 | gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), position: 3, title: "Min" , cell: gtk_cell_renderer_text_new(), "text" , OP_MIN, NULL); |
94 | |
95 | gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(widget), position: 4, title: "Max" , cell: gtk_cell_renderer_text_new(), "text" , OP_MAX, NULL); |
96 | |
97 | for (int i = 0; i < N_COLUMNS; i++) { |
98 | GtkTreeViewColumn *column; |
99 | |
100 | column = gtk_tree_view_get_column(GTK_TREE_VIEW(widget), n: i); |
101 | gtk_tree_view_column_set_sort_column_id(tree_column: column, sort_column_id: i); |
102 | } |
103 | doc = nullptr; |
104 | output = new CairoOutputDev(); |
105 | output->setPrinting(false); |
106 | |
107 | // set up initial widgets |
108 | load(file_name: nullptr); |
109 | } |
110 | |
111 | void PdfInspector::set_file_name(const char *file_name) |
112 | { |
113 | GtkWidget *widget; |
114 | |
115 | widget = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_file_chooser_button" )); |
116 | gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(widget), filename: file_name); |
117 | } |
118 | |
119 | void PdfInspector::on_file_activated(GtkWidget *widget, PdfInspector *inspector) |
120 | { |
121 | gchar *file_name; |
122 | |
123 | file_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)); |
124 | if (file_name) { |
125 | inspector->load(file_name); |
126 | } |
127 | |
128 | g_free(mem: file_name); |
129 | } |
130 | |
131 | void PdfInspector::on_selection_changed(GtkTreeSelection *selection, PdfInspector *inspector) |
132 | { |
133 | GtkWidget *label; |
134 | size_t i; |
135 | GtkTreeModel *model; |
136 | GtkTreeIter iter; |
137 | gchar *op = nullptr; |
138 | |
139 | label = GTK_WIDGET(gtk_builder_get_object(inspector->builder, "description_label" )); |
140 | gtk_label_set_markup(GTK_LABEL(label), str: "<i>No Description</i>" ); |
141 | |
142 | if (gtk_tree_selection_get_selected(selection, model: &model, iter: &iter)) { |
143 | gtk_tree_model_get(tree_model: model, iter: &iter, OP_STRING, &op, -1); |
144 | } |
145 | |
146 | if (op == nullptr) { |
147 | return; |
148 | } |
149 | |
150 | for (i = 0; i < G_N_ELEMENTS(op_mapping); i++) { |
151 | |
152 | if (!strcmp(s1: op, s2: op_mapping[i].op)) { |
153 | gchar *text; |
154 | text = g_strdup_printf(format: "<i>%s</i>" , op_mapping[i].description); |
155 | gtk_label_set_markup(GTK_LABEL(label), str: text); |
156 | g_free(mem: text); |
157 | break; |
158 | } |
159 | } |
160 | |
161 | g_free(mem: op); |
162 | } |
163 | |
164 | void PdfInspector::on_analyze_clicked(GtkWidget *widget, PdfInspector *inspector) |
165 | { |
166 | GtkWidget *spin; |
167 | int page; |
168 | |
169 | spin = GTK_WIDGET(gtk_builder_get_object(inspector->builder, "pdf_spin" )); |
170 | |
171 | page = (int)gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)); |
172 | |
173 | inspector->analyze_page(page); |
174 | } |
175 | |
176 | void PdfInspector::analyze_page(int page) |
177 | { |
178 | GtkWidget *label; |
179 | char *text; |
180 | cairo_t *cr; |
181 | cairo_surface_t *surface; |
182 | |
183 | label = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_total_label" )); |
184 | |
185 | output->startProfile(); |
186 | gtk_list_store_clear(GTK_LIST_STORE(model)); |
187 | |
188 | GooTimer timer; |
189 | surface = cairo_image_surface_create(format: CAIRO_FORMAT_RGB24, width: doc->getPageCropWidth(page: page + 1), height: doc->getPageCropHeight(page: page + 1)); |
190 | cr = cairo_create(target: surface); |
191 | cairo_surface_destroy(surface); |
192 | output->setCairo(cr); |
193 | cairo_destroy(cr); |
194 | doc->displayPage(out: output, page: page + 1, hDPI: 72, vDPI: 72, rotate: 0, useMediaBox: false, crop: true, printing: false); |
195 | output->setCairo(nullptr); |
196 | |
197 | // Total time; |
198 | text = g_strdup_printf(format: "%g" , timer.getElapsed()); |
199 | gtk_label_set_text(GTK_LABEL(label), str: text); |
200 | g_free(mem: text); |
201 | |
202 | // Individual times; |
203 | auto hash = output->endProfile(); |
204 | for (const auto &kvp : *hash) { |
205 | GtkTreeIter tree_iter; |
206 | const auto *const data_p = &kvp.second; |
207 | |
208 | gtk_list_store_append(GTK_LIST_STORE(model), iter: &tree_iter); |
209 | gtk_list_store_set(GTK_LIST_STORE(model), iter: &tree_iter, OP_STRING, kvp.first.c_str(), OP_COUNT, data_p->getCount(), OP_TOTAL, data_p->getTotal(), OP_MIN, data_p->getMin(), OP_MAX, data_p->getMax(), -1); |
210 | } |
211 | } |
212 | |
213 | void PdfInspector::load(const char *file_name) |
214 | { |
215 | GtkWidget *spin; |
216 | GtkWidget *button; |
217 | GtkWidget *label; |
218 | |
219 | // kill the old PDF file |
220 | if (doc != nullptr) { |
221 | delete doc; |
222 | doc = nullptr; |
223 | } |
224 | |
225 | // load the new file |
226 | if (file_name) { |
227 | doc = new PDFDoc(std::make_unique<GooString>(args&: file_name)); |
228 | } |
229 | |
230 | if (doc && !doc->isOk()) { |
231 | this->error_dialog(error_message: "Failed to load file." ); |
232 | delete doc; |
233 | doc = nullptr; |
234 | } |
235 | |
236 | spin = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_spin" )); |
237 | button = GTK_WIDGET(gtk_builder_get_object(builder, "analyze_button" )); |
238 | label = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_total_label" )); |
239 | gtk_label_set_text(GTK_LABEL(label), str: "" ); |
240 | |
241 | if (doc) { |
242 | gtk_widget_set_sensitive(widget: spin, TRUE); |
243 | gtk_widget_set_sensitive(widget: button, TRUE); |
244 | gtk_widget_set_sensitive(widget: label, TRUE); |
245 | gtk_spin_button_set_range(GTK_SPIN_BUTTON(spin), min: 0, max: doc->getNumPages() - 1); |
246 | gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value: 0); |
247 | |
248 | output->startDoc(docA: doc); |
249 | } else { |
250 | gtk_widget_set_sensitive(widget: spin, FALSE); |
251 | gtk_widget_set_sensitive(widget: button, FALSE); |
252 | gtk_widget_set_sensitive(widget: label, FALSE); |
253 | } |
254 | } |
255 | |
256 | void PdfInspector::error_dialog(const char *error_message) |
257 | { |
258 | g_warning("%s" , error_message); |
259 | } |
260 | |
261 | void PdfInspector::run() |
262 | { |
263 | GtkWidget *dialog; |
264 | |
265 | dialog = GTK_WIDGET(gtk_builder_get_object(builder, "pdf_dialog" )); |
266 | |
267 | gtk_dialog_run(GTK_DIALOG(dialog)); |
268 | } |
269 | |
270 | int main(int argc, char *argv[]) |
271 | { |
272 | const char *file_name = nullptr; |
273 | PdfInspector *inspector; |
274 | |
275 | gtk_init(argc: &argc, argv: &argv); |
276 | |
277 | globalParams = std::make_unique<GlobalParams>(); |
278 | globalParams->setProfileCommands(true); |
279 | globalParams->setPrintCommands(true); |
280 | |
281 | if (argc == 2) { |
282 | file_name = argv[1]; |
283 | } else if (argc > 2) { |
284 | fprintf(stderr, format: "usage: %s [PDF-FILE]\n" , argv[0]); |
285 | return -1; |
286 | } |
287 | |
288 | inspector = new PdfInspector(); |
289 | |
290 | if (file_name) { |
291 | inspector->set_file_name(file_name); |
292 | } |
293 | |
294 | inspector->run(); |
295 | |
296 | delete inspector; |
297 | |
298 | return 0; |
299 | } |
300 | |