1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 /* Webpage plug-in.
19  * Copyright (C) 2011 Mukund Sivaraman <muks@banu.com>.
20  * Portions are copyright of the author of the
21  * file-open-location-dialog.c code.
22  */
23 
24 #include "config.h"
25 
26 #include <libgimp/gimp.h>
27 #include <libgimp/gimpui.h>
28 
29 #include <webkit/webkit.h>
30 
31 #include "libgimp/stdplugins-intl.h"
32 
33 /* Defines */
34 #define PLUG_IN_PROC   "plug-in-web-page"
35 #define PLUG_IN_BINARY "web-page"
36 #define PLUG_IN_ROLE   "gimp-web-page"
37 #define MAX_URL_LEN    2048
38 
39 typedef struct
40 {
41   char      *url;
42   gint32     width;
43   gint       font_size;
44   GdkPixbuf *pixbuf;
45   GError    *error;
46 } WebpageVals;
47 
48 static WebpageVals webpagevals;
49 
50 typedef struct
51 {
52   char   url[MAX_URL_LEN];
53   gint32 width;
54   gint   font_size;
55 } WebpageSaveVals;
56 
57 static void     query           (void);
58 static void     run             (const gchar      *name,
59                                  gint              nparams,
60                                  const GimpParam  *param,
61                                  gint             *nreturn_vals,
62                                  GimpParam       **return_vals);
63 static gboolean webpage_dialog  (void);
64 static gint32   webpage_capture (void);
65 
66 
67 /* Global Variables */
68 const GimpPlugInInfo PLUG_IN_INFO =
69 {
70   NULL,  /* init_proc  */
71   NULL,  /* quit_proc  */
72   query, /* query_proc */
73   run    /* run_proc   */
74 };
75 
76 
77 /* Functions */
78 
MAIN()79 MAIN ()
80 
81 static void
82 query (void)
83 {
84   static const GimpParamDef args[] =
85   {
86     { GIMP_PDB_INT32,  "run-mode",  "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
87     { GIMP_PDB_STRING, "url",       "URL of the webpage to screenshot"                             },
88     { GIMP_PDB_INT32,  "width",     "The width of the screenshot (in pixels)"                      },
89     { GIMP_PDB_INT32,  "font-size", "The font size to use in the page (in pt)"                     }
90   };
91 
92   static const GimpParamDef return_vals[] =
93   {
94     { GIMP_PDB_IMAGE, "image", "Output image" }
95   };
96 
97   gimp_install_procedure (PLUG_IN_PROC,
98                           N_("Create an image of a webpage"),
99                           "The plug-in allows you to take a screenshot "
100                           "of a webpage.",
101                           "Mukund Sivaraman <muks@banu.com>",
102                           "2011",
103                           "2011",
104                           N_("From _Webpage..."),
105                           NULL,
106                           GIMP_PLUGIN,
107                           G_N_ELEMENTS (args),
108                           G_N_ELEMENTS (return_vals),
109                           args, return_vals);
110 
111   gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/File/Create/Acquire");
112 }
113 
114 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)115 run (const gchar      *name,
116      gint             nparams,
117      const GimpParam  *param,
118      gint             *nreturn_vals,
119      GimpParam       **return_vals)
120 {
121   GimpRunMode        run_mode = param[0].data.d_int32;
122   GimpPDBStatusType  status   = GIMP_PDB_EXECUTION_ERROR;
123   gint32             image_id = -1;
124   static GimpParam   values[2];
125   WebpageSaveVals    save = {"https://www.gimp.org/", 1024, 12};
126 
127   INIT_I18N ();
128 
129   /* initialize the return of the status */
130   *nreturn_vals = 1;
131   *return_vals  = values;
132   values[0].type = GIMP_PDB_STATUS;
133 
134   gimp_get_data (PLUG_IN_PROC, &save);
135 
136   webpagevals.url = g_strdup (save.url);
137   webpagevals.width = save.width;
138   webpagevals.font_size = save.font_size;
139 
140   /* how are we running today? */
141   switch (run_mode)
142     {
143     case GIMP_RUN_INTERACTIVE:
144       if (webpage_dialog ())
145         status = GIMP_PDB_SUCCESS;
146       else
147         status = GIMP_PDB_CANCEL;
148       break;
149 
150     case GIMP_RUN_WITH_LAST_VALS:
151       /* This is currently not supported. */
152       break;
153 
154     case GIMP_RUN_NONINTERACTIVE:
155       webpagevals.url = param[1].data.d_string;
156       webpagevals.width = param[2].data.d_int32;
157       webpagevals.font_size = param[3].data.d_int32;
158       status = GIMP_PDB_SUCCESS;
159       break;
160 
161     default:
162       break;
163     }
164 
165   if (status == GIMP_PDB_SUCCESS)
166     {
167       image_id = webpage_capture ();
168 
169       if (image_id == -1)
170         {
171           status = GIMP_PDB_EXECUTION_ERROR;
172 
173           if (webpagevals.error)
174             {
175               *nreturn_vals = 2;
176 
177               values[1].type = GIMP_PDB_STRING;
178               values[1].data.d_string = webpagevals.error->message;
179             }
180         }
181       else
182         {
183           save.width = webpagevals.width;
184           save.font_size = webpagevals.font_size;
185 
186           if (strlen (webpagevals.url) < MAX_URL_LEN)
187             {
188               strncpy (save.url, webpagevals.url, MAX_URL_LEN);
189               save.url[MAX_URL_LEN - 1] = 0;
190             }
191           else
192             {
193               memset (save.url, 0, MAX_URL_LEN);
194             }
195 
196           gimp_set_data (PLUG_IN_PROC, &save, sizeof save);
197 
198           if (run_mode == GIMP_RUN_INTERACTIVE)
199             gimp_display_new (image_id);
200 
201           *nreturn_vals = 2;
202 
203           values[1].type         = GIMP_PDB_IMAGE;
204           values[1].data.d_image = image_id;
205         }
206     }
207 
208   values[0].data.d_status = status;
209 }
210 
211 static gboolean
webpage_dialog(void)212 webpage_dialog (void)
213 {
214   GtkWidget     *dialog;
215   GtkWidget     *hbox;
216   GtkWidget     *vbox;
217   GtkWidget     *image;
218   GtkWidget     *label;
219   GtkWidget     *entry;
220   GtkSizeGroup  *sizegroup;
221   GtkAdjustment *adjustment;
222   GtkWidget     *spinbutton;
223   GtkWidget     *combo;
224   gint           active;
225   gint           status;
226   gboolean       ret = FALSE;
227 
228   gimp_ui_init (PLUG_IN_BINARY, FALSE);
229 
230   dialog = gimp_dialog_new (_("Create from webpage"), PLUG_IN_ROLE,
231                             NULL, 0,
232                             gimp_standard_help_func, PLUG_IN_PROC,
233 
234                             _("_Cancel"), GTK_RESPONSE_CANCEL,
235                             _("Cre_ate"), GTK_RESPONSE_OK,
236 
237                             NULL);
238 
239   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
240                                            GTK_RESPONSE_OK,
241                                            GTK_RESPONSE_CANCEL,
242                                            -1);
243   gimp_window_set_transient (GTK_WINDOW (dialog));
244 
245   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
246   gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
247   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
248                       hbox, FALSE, FALSE, 0);
249   gtk_widget_show (hbox);
250 
251   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
252   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
253   gtk_widget_show (vbox);
254 
255   image = gtk_image_new_from_icon_name (GIMP_ICON_WEB,
256                                         GTK_ICON_SIZE_BUTTON);
257   gtk_box_pack_start (GTK_BOX (vbox), image, FALSE, FALSE, 0);
258   gtk_widget_show (image);
259 
260   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
261   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
262   gtk_widget_show (vbox);
263 
264   label = gtk_label_new (_("Enter location (URI):"));
265   gtk_label_set_xalign (GTK_LABEL (label), 0.0);
266   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
267   gtk_widget_show (label);
268 
269   entry = gtk_entry_new ();
270   gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
271   gtk_widget_set_size_request (entry, 400, -1);
272   gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0);
273 
274   if (webpagevals.url)
275     gtk_entry_set_text (GTK_ENTRY (entry),
276                         webpagevals.url);
277   gtk_widget_show (entry);
278 
279   sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
280 
281   /* Width */
282   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
283   gtk_box_pack_start (GTK_BOX (vbox),
284                       hbox, FALSE, FALSE, 0);
285   gtk_widget_show (hbox);
286 
287   label = gtk_label_new (_("Width (pixels):"));
288   gtk_size_group_add_widget (sizegroup, label);
289 
290   gtk_label_set_xalign (GTK_LABEL (label), 0.0);
291   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
292   gtk_widget_show (label);
293 
294   adjustment = (GtkAdjustment *)
295     gtk_adjustment_new (webpagevals.width,
296                         1, 8192, 1, 10, 0);
297   spinbutton = gimp_spin_button_new (adjustment, 1.0, 0);
298   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
299   gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
300   gtk_widget_show (spinbutton);
301 
302   /* Font size */
303   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
304   gtk_box_pack_start (GTK_BOX (vbox),
305                       hbox, FALSE, FALSE, 0);
306   gtk_widget_show (hbox);
307 
308   label = gtk_label_new (_("Font size:"));
309   gtk_size_group_add_widget (sizegroup, label);
310 
311   gtk_label_set_xalign (GTK_LABEL (label), 0.0);
312   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
313   gtk_widget_show (label);
314 
315   combo = gimp_int_combo_box_new (_("Huge"), 16,
316                                   _("Large"), 14,
317                                   C_("web-page", "Default"), 12,
318                                   _("Small"), 10,
319                                   _("Tiny"), 8,
320                                   NULL);
321 
322   switch (webpagevals.font_size)
323     {
324     case 16:
325     case 14:
326     case 12:
327     case 10:
328     case 8:
329       active = webpagevals.font_size;
330       break;
331     default:
332       active = 12;
333     }
334 
335   gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), active);
336 
337   gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
338   gtk_widget_show (combo);
339 
340   g_object_unref (sizegroup);
341 
342   status = gimp_dialog_run (GIMP_DIALOG (dialog));
343   if (status == GTK_RESPONSE_OK)
344     {
345       g_free (webpagevals.url);
346       webpagevals.url = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
347 
348       webpagevals.width = (gint) gtk_adjustment_get_value
349         (GTK_ADJUSTMENT (adjustment));
350 
351       gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo),
352                                      &webpagevals.font_size);
353 
354       ret = TRUE;
355     }
356 
357   gtk_widget_destroy (dialog);
358 
359   return ret;
360 }
361 
362 static void
notify_progress_cb(WebKitWebView * view,GParamSpec * pspec,gpointer user_data)363 notify_progress_cb (WebKitWebView  *view,
364                     GParamSpec     *pspec,
365                     gpointer        user_data)
366 {
367   static gdouble old_progress = 0.0;
368   gdouble progress;
369 
370   g_object_get (view,
371                 "progress", &progress,
372                 NULL);
373 
374   if ((progress - old_progress) > 0.01)
375     {
376       gimp_progress_update (progress);
377       old_progress = progress;
378     }
379 }
380 
381 static gboolean
load_error_cb(WebKitWebView * view,WebKitWebFrame * web_frame,gchar * uri,gpointer web_error,gpointer user_data)382 load_error_cb (WebKitWebView  *view,
383                WebKitWebFrame *web_frame,
384                gchar          *uri,
385                gpointer        web_error,
386                gpointer        user_data)
387 {
388   webpagevals.error = g_error_copy ((GError *) web_error);
389 
390   gtk_main_quit ();
391 
392   return TRUE;
393 }
394 
395 static void
notify_load_status_cb(WebKitWebView * view,GParamSpec * pspec,gpointer user_data)396 notify_load_status_cb (WebKitWebView  *view,
397                        GParamSpec     *pspec,
398                        gpointer        user_data)
399 {
400   WebKitLoadStatus status;
401 
402   g_object_get (view,
403                 "load-status", &status,
404                 NULL);
405 
406   if (status == WEBKIT_LOAD_FINISHED)
407     {
408       if (!webpagevals.error)
409         {
410           webpagevals.pixbuf = gtk_offscreen_window_get_pixbuf
411             (GTK_OFFSCREEN_WINDOW (user_data));
412         }
413 
414       gtk_main_quit ();
415     }
416 }
417 
418 static gint32
webpage_capture(void)419 webpage_capture (void)
420 {
421   gint32 image = -1;
422   gchar *scheme;
423   GtkWidget *window;
424   GtkWidget *view;
425   WebKitWebSettings *settings;
426   char *ua_old;
427   char *ua;
428 
429   if (webpagevals.pixbuf)
430     {
431       g_object_unref (webpagevals.pixbuf);
432       webpagevals.pixbuf = NULL;
433     }
434   if (webpagevals.error)
435     {
436       g_error_free (webpagevals.error);
437       webpagevals.error = NULL;
438     }
439 
440   if ((!webpagevals.url) ||
441       (strlen (webpagevals.url) == 0))
442     {
443       g_set_error (&webpagevals.error, 0, 0, _("No URL was specified"));
444       return -1;
445     }
446 
447   scheme = g_uri_parse_scheme (webpagevals.url);
448   if (!scheme)
449     {
450       char *url;
451 
452       /* If we were not given a well-formed URL, make one. */
453 
454       url = g_strconcat ("http://", webpagevals.url, NULL);
455       g_free (webpagevals.url);
456       webpagevals.url = url;
457 
458       g_free (scheme);
459     }
460 
461   if (webpagevals.width < 32)
462     {
463       g_warning ("Width '%d' is too small. Clamped to 32.",
464                  webpagevals.width);
465       webpagevals.width = 32;
466     }
467   else if (webpagevals.width > 8192)
468     {
469       g_warning ("Width '%d' is too large. Clamped to 8192.",
470                  webpagevals.width);
471       webpagevals.width = 8192;
472     }
473 
474   window = gtk_offscreen_window_new ();
475   gtk_widget_show (window);
476 
477   view = webkit_web_view_new ();
478   gtk_widget_show (view);
479 
480   gtk_widget_set_size_request (view, webpagevals.width, -1);
481   gtk_container_add (GTK_CONTAINER (window), view);
482 
483   /* Append "GIMP/<GIMP_VERSION>" to the user agent string */
484   settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
485   g_object_get (settings,
486                 "user-agent", &ua_old,
487                 NULL);
488   ua = g_strdup_printf ("%s GIMP/%s", ua_old, GIMP_VERSION);
489   g_object_set (settings,
490                 "user-agent", ua,
491                 NULL);
492   g_free (ua_old);
493   g_free (ua);
494 
495   /* Set font size */
496   g_object_set (settings,
497                 "default-font-size", webpagevals.font_size,
498                 NULL);
499 
500   g_signal_connect (view, "notify::progress",
501                     G_CALLBACK (notify_progress_cb),
502                     window);
503   g_signal_connect (view, "load-error",
504                     G_CALLBACK (load_error_cb),
505                     window);
506   g_signal_connect (view, "notify::load-status",
507                     G_CALLBACK (notify_load_status_cb),
508                     window);
509 
510   gimp_progress_init_printf (_("Downloading webpage '%s'"), webpagevals.url);
511 
512   webkit_web_view_open (WEBKIT_WEB_VIEW (view),
513                         webpagevals.url);
514 
515   gtk_main ();
516 
517   gtk_widget_destroy (window);
518 
519   gimp_progress_update (1.0);
520 
521   if (webpagevals.pixbuf)
522     {
523       gint width;
524       gint height;
525       gint32 layer;
526 
527       gimp_progress_init_printf (_("Transferring webpage image for '%s'"),
528                                  webpagevals.url);
529 
530       width  = gdk_pixbuf_get_width (webpagevals.pixbuf);
531       height = gdk_pixbuf_get_height (webpagevals.pixbuf);
532 
533       image = gimp_image_new (width, height, GIMP_RGB);
534 
535       gimp_image_undo_disable (image);
536       layer = gimp_layer_new_from_pixbuf (image, _("Webpage"),
537                                           webpagevals.pixbuf,
538                                           100,
539                                           gimp_image_get_default_new_layer_mode (image),
540                                           0.0, 1.0);
541       gimp_image_insert_layer (image, layer, -1, 0);
542       gimp_image_undo_enable (image);
543 
544       g_object_unref (webpagevals.pixbuf);
545       webpagevals.pixbuf = NULL;
546 
547       gimp_progress_update (1.0);
548     }
549 
550   return image;
551 }
552