/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "print-preview.h"


enum
{
  OFFSETS_CHANGED,
  LAST_SIGNAL
};


#define SIZE_REQUEST  200


struct _PrintPreview
{
  GtkEventBox      parent_instance;

  GdkCursor       *cursor;

  GtkPageSetup    *page;
  cairo_surface_t *thumbnail;
  gboolean         dragging;
  gboolean         inside;

  GimpDrawable    *drawable;

  gdouble          image_offset_x;
  gdouble          image_offset_y;
  gdouble          image_offset_x_max;
  gdouble          image_offset_y_max;
  gdouble          image_width;
  gdouble          image_height;

  gboolean         use_full_page;

  /* for mouse drags */
  gdouble          orig_offset_x;
  gdouble          orig_offset_y;
  gint             start_x;
  gint             start_y;
};

struct _PrintPreviewClass
{
  GtkEventBoxClass  parent_class;

  void (* offsets_changed)  (PrintPreview *print_preview,
                             gint          offset_x,
                             gint          offset_y);
};


static void      print_preview_finalize             (GObject          *object);

static void      print_preview_realize              (GtkWidget        *widget);
static void      print_preview_unrealize            (GtkWidget        *widget);
static void      print_preview_get_preferred_width  (GtkWidget        *widget,
                                                     gint             *minimum_width,
                                                     gint             *natural_width);
static void      print_preview_get_preferred_height (GtkWidget        *widget,
                                                     gint             *minimum_height,
                                                     gint             *natural_height);
static void      print_preview_size_allocate        (GtkWidget        *widget,
                                                     GtkAllocation    *allocation);
static gboolean  print_preview_draw                 (GtkWidget        *widget,
                                                     cairo_t          *cr);
static gboolean  print_preview_button_press_event   (GtkWidget        *widget,
                                                     GdkEventButton   *event);
static gboolean  print_preview_button_release_event (GtkWidget        *widget,
                                                     GdkEventButton   *event);
static gboolean  print_preview_motion_notify_event  (GtkWidget        *widget,
                                                     GdkEventMotion   *event);
static gboolean  print_preview_leave_notify_event   (GtkWidget        *widget,
                                                     GdkEventCrossing *event);

static gboolean  print_preview_is_inside            (PrintPreview     *preview,
                                                     gdouble           x,
                                                     gdouble           y);
static void      print_preview_set_inside           (PrintPreview     *preview,
                                                     gboolean          inside);

static gdouble   print_preview_get_scale            (PrintPreview     *preview);

static void      print_preview_get_page_size        (PrintPreview     *preview,
                                                     gdouble          *paper_width,
                                                     gdouble          *paper_height);
static void      print_preview_get_page_margins     (PrintPreview     *preview,
                                                     gdouble          *left_margin,
                                                     gdouble          *right_margin,
                                                     gdouble          *top_margin,
                                                     gdouble          *bottom_margin);
static cairo_surface_t * print_preview_get_thumbnail (GimpDrawable    *drawable,
                                                      gint             width,
                                                      gint             height);


G_DEFINE_TYPE (PrintPreview, print_preview, GTK_TYPE_EVENT_BOX)

#define parent_class print_preview_parent_class

static guint print_preview_signals[LAST_SIGNAL] = { 0 };


#define g_marshal_value_peek_double(v)   (v)->data[0].v_double

static void
marshal_VOID__DOUBLE_DOUBLE (GClosure     *closure,
                             GValue       *return_value,
                             guint         n_param_values,
                             const GValue *param_values,
                             gpointer      invocation_hint,
                             gpointer      marshal_data)
{
  typedef void (*GMarshalFunc_VOID__DOUBLE_DOUBLE) (gpointer     data1,
                                                    gdouble      arg_1,
                                                    gdouble      arg_2,
                                                    gpointer     data2);
  register GMarshalFunc_VOID__DOUBLE_DOUBLE callback;
  register GCClosure *cc = (GCClosure*) closure;
  register gpointer data1, data2;

  g_return_if_fail (n_param_values == 3);

  if (G_CCLOSURE_SWAP_DATA (closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer (param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer (param_values + 0);
      data2 = closure->data;
    }

  callback = (GMarshalFunc_VOID__DOUBLE_DOUBLE) (marshal_data ?
                                                 marshal_data : cc->callback);

  callback (data1,
            g_marshal_value_peek_double (param_values + 1),
            g_marshal_value_peek_double (param_values + 2),
            data2);
}

static void
print_preview_class_init (PrintPreviewClass *klass)
{
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  print_preview_signals[OFFSETS_CHANGED] =
    g_signal_new ("offsets-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PrintPreviewClass, offsets_changed),
                  NULL, NULL,
                  marshal_VOID__DOUBLE_DOUBLE,
                  G_TYPE_NONE, 2,
                  G_TYPE_DOUBLE,
                  G_TYPE_DOUBLE);

  object_class->finalize             = print_preview_finalize;

  widget_class->realize              = print_preview_realize;
  widget_class->unrealize            = print_preview_unrealize;
  widget_class->get_preferred_width  = print_preview_get_preferred_width;
  widget_class->get_preferred_height = print_preview_get_preferred_height;
  widget_class->size_allocate        = print_preview_size_allocate;
  widget_class->draw                 = print_preview_draw;
  widget_class->button_press_event   = print_preview_button_press_event;
  widget_class->button_release_event = print_preview_button_release_event;
  widget_class->motion_notify_event  = print_preview_motion_notify_event;
  widget_class->leave_notify_event   = print_preview_leave_notify_event;

  klass->offsets_changed = NULL;
}

static void
print_preview_init (PrintPreview *preview)
{
  gtk_event_box_set_visible_window (GTK_EVENT_BOX (preview), FALSE);

  gtk_widget_add_events (GTK_WIDGET (preview),
                         GDK_BUTTON_PRESS_MASK   |
                         GDK_BUTTON_RELEASE_MASK |
                         GDK_POINTER_MOTION_MASK);
}


static void
print_preview_finalize (GObject *object)
{
  PrintPreview *preview = PRINT_PREVIEW (object);

  if (preview->thumbnail)
    {
      cairo_surface_destroy (preview->thumbnail);
      preview->thumbnail = NULL;
    }

  if (preview->page)
    {
      g_object_unref (preview->page);
      preview->page = NULL;
    }

  G_OBJECT_CLASS (print_preview_parent_class)->finalize (object);
}

static void
print_preview_realize (GtkWidget *widget)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);

  GTK_WIDGET_CLASS (print_preview_parent_class)->realize (widget);

  preview->cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
                                                GDK_HAND1);
}

static void
print_preview_unrealize (GtkWidget *widget)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);

  if (preview->cursor)
    g_object_unref (preview->cursor);

  GTK_WIDGET_CLASS (print_preview_parent_class)->unrealize (widget);
}

static void
print_preview_size_request (GtkWidget      *widget,
                            GtkRequisition *requisition)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);
  gdouble       paper_width;
  gdouble       paper_height;
  gint          border;

  border = gtk_container_get_border_width (GTK_CONTAINER (widget)) + 1;

  print_preview_get_page_size (preview, &paper_width, &paper_height);

  if (paper_width > paper_height)
    {
      requisition->height = SIZE_REQUEST;
      requisition->width  = paper_width * SIZE_REQUEST / paper_height;
      requisition->width  = MIN (requisition->width, 2 * SIZE_REQUEST);
    }
  else
    {
      requisition->width  = SIZE_REQUEST;
      requisition->height = paper_height * SIZE_REQUEST / paper_width;
      requisition->height = MIN (requisition->height, 2 * SIZE_REQUEST);
    }

  requisition->width  += 2 * border;
  requisition->height += 2 * border;
}

static void
print_preview_get_preferred_width (GtkWidget *widget,
                                   gint      *minimum_width,
                                   gint      *natural_width)
{
  GtkRequisition requisition;

  print_preview_size_request (widget, &requisition);

  *minimum_width = *natural_width = requisition.width;
}

static void
print_preview_get_preferred_height (GtkWidget *widget,
                                    gint      *minimum_height,
                                    gint      *natural_height)
{
  GtkRequisition requisition;

  print_preview_size_request (widget, &requisition);

  *minimum_height = *natural_height = requisition.height;
}

static void
print_preview_size_allocate (GtkWidget     *widget,
                             GtkAllocation *allocation)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);

  GTK_WIDGET_CLASS (print_preview_parent_class)->size_allocate (widget,
                                                                allocation);

  if (preview->thumbnail)
    {
      cairo_surface_destroy (preview->thumbnail);
      preview->thumbnail = NULL;
    }
}

static gboolean
print_preview_button_press_event (GtkWidget      *widget,
                                  GdkEventButton *event)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);

  if (event->type == GDK_BUTTON_PRESS && event->button == 1 && preview->inside)
    {
      GdkDisplay *display = gtk_widget_get_display (widget);
      GdkSeat    *seat    = gdk_display_get_default_seat (display);
      GdkCursor  *cursor;

      cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
                                           GDK_FLEUR);

      if (gdk_seat_grab (seat, gdk_event_get_window ((GdkEvent *) event),
                         GDK_SEAT_CAPABILITY_ALL_POINTING, FALSE,
                         cursor,
                         (GdkEvent *) event,
                         NULL, NULL) == GDK_GRAB_SUCCESS)
        {
          preview->orig_offset_x = preview->image_offset_x;
          preview->orig_offset_y = preview->image_offset_y;

          preview->start_x = event->x;
          preview->start_y = event->y;

          preview->dragging = TRUE;
        }

      g_object_unref (cursor);
    }

  return FALSE;
}

static gboolean
print_preview_button_release_event (GtkWidget      *widget,
                                    GdkEventButton *event)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);

  if (preview->dragging)
    {
      GdkDisplay *display = gtk_widget_get_display (widget);
      GdkSeat    *seat    = gdk_display_get_default_seat (display);

      gdk_seat_ungrab (seat);

      preview->dragging = FALSE;

      print_preview_set_inside (preview,
                                print_preview_is_inside (preview,
                                                         event->x, event->y));
    }

  return FALSE;
}

static gboolean
print_preview_motion_notify_event (GtkWidget      *widget,
                                   GdkEventMotion *event)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);

  if (preview->dragging)
    {
      gdouble scale = print_preview_get_scale (preview);
      gdouble offset_x;
      gdouble offset_y;

      offset_x = (preview->orig_offset_x +
                  (event->x - preview->start_x) / scale);
      offset_y = (preview->orig_offset_y +
                  (event->y - preview->start_y) / scale);

      offset_x = CLAMP (offset_x, 0, preview->image_offset_x_max);
      offset_y = CLAMP (offset_y, 0, preview->image_offset_y_max);

      if (preview->image_offset_x != offset_x ||
          preview->image_offset_y != offset_y)
        {
          print_preview_set_image_offsets (preview, offset_x, offset_y);

          g_signal_emit (preview,
                         print_preview_signals[OFFSETS_CHANGED], 0,
                         preview->image_offset_x,
                         preview->image_offset_y);
        }
    }
  else
    {
      print_preview_set_inside (preview,
                                print_preview_is_inside (preview,
                                                         event->x, event->y));
    }

  return FALSE;
}

static gboolean
print_preview_leave_notify_event (GtkWidget        *widget,
                                  GdkEventCrossing *event)
{
  PrintPreview *preview = PRINT_PREVIEW (widget);

  if (event->mode == GDK_CROSSING_NORMAL)
    print_preview_set_inside (preview, FALSE);

  return FALSE;
}

static gboolean
print_preview_draw (GtkWidget *widget,
                    cairo_t   *cr)
{
  PrintPreview  *preview = PRINT_PREVIEW (widget);
  GtkAllocation  allocation;
  GdkRGBA        color;
  gdouble        paper_width;
  gdouble        paper_height;
  gdouble        left_margin;
  gdouble        right_margin;
  gdouble        top_margin;
  gdouble        bottom_margin;
  gdouble        scale;
  gint           border;

  gtk_widget_get_allocation (widget, &allocation);

  border = gtk_container_get_border_width (GTK_CONTAINER (widget)) + 1;

  print_preview_get_page_size (preview, &paper_width, &paper_height);
  print_preview_get_page_margins (preview,
                                  &left_margin, &right_margin,
                                  &top_margin,  &bottom_margin);

  scale = print_preview_get_scale (preview);

  cairo_translate (cr, border, border);

  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
    {
      gint width = allocation.width - 2 * border;

      cairo_translate (cr, width - scale * paper_width, 0);
    }

  cairo_set_line_width (cr, 1.0);

  /* draw page background */
  cairo_rectangle (cr, 0, 0, scale * paper_width, scale * paper_height);

  color = (GdkRGBA) { 0.0, 0.0, 0.0, 1.0 };
  gdk_cairo_set_source_rgba (cr, &color);
  cairo_stroke_preserve (cr);

  color = (GdkRGBA) { 1.0, 1.0, 1.0, 1.0 };
  gdk_cairo_set_source_rgba (cr, &color);
  cairo_fill (cr);

  /* draw page_margins */
  cairo_rectangle (cr,
                   scale * left_margin,
                   scale * top_margin,
                   scale * (paper_width - left_margin - right_margin),
                   scale * (paper_height - top_margin - bottom_margin));

  color = (GdkRGBA) { 0.0, 0.0, 0.0, 0.3 };
  gdk_cairo_set_source_rgba (cr, &color);
  cairo_stroke (cr);

  cairo_translate (cr,
                   scale * (left_margin + preview->image_offset_x),
                   scale * (top_margin  + preview->image_offset_y));

  if (preview->dragging || preview->inside)
    {
      cairo_rectangle (cr,
                       0, 0,
                       scale * preview->image_width,
                       scale * preview->image_height);

      color = (GdkRGBA) { 0.0, 0.0, 0.0, 1.0 };
      gdk_cairo_set_source_rgba (cr, &color);
      cairo_stroke (cr);
    }

  if (preview->thumbnail == NULL &&
      gimp_item_is_valid (GIMP_ITEM (preview->drawable)))
    {
      preview->thumbnail =
        print_preview_get_thumbnail (preview->drawable,
                                     MIN (allocation.width,  1024),
                                     MIN (allocation.height, 1024));
    }

  if (preview->thumbnail != NULL)
    {
      gdouble scale_x;
      gdouble scale_y;

      scale_x = (preview->image_width  /
                 cairo_image_surface_get_width (preview->thumbnail));
      scale_y = (preview->image_height /
                 cairo_image_surface_get_height (preview->thumbnail));

      cairo_rectangle (cr, 0, 0, preview->image_width, preview->image_height);

      cairo_scale (cr, scale_x * scale, scale_y * scale);

      cairo_set_source_surface (cr, preview->thumbnail, 0, 0);
      cairo_fill (cr);
    }

  return FALSE;
}

/**
 * print_preview_new:
 * @page: page setup
 * @drawable: the drawable to print
 *
 * Creates a new #PrintPreview widget.
 *
 * Returns: the new #PrintPreview widget.
 **/
GtkWidget *
print_preview_new (GtkPageSetup *page,
                   GimpDrawable *drawable)
{
  PrintPreview *preview;

  g_return_val_if_fail (GTK_IS_PAGE_SETUP (page), NULL);

  preview = g_object_new (PRINT_TYPE_PREVIEW, NULL);

  preview->drawable = drawable;

  print_preview_set_page_setup (preview, page);

  return GTK_WIDGET (preview);
}

/**
 * print_preview_set_image_dpi:
 * @preview: a #PrintPreview.
 * @xres: the X resolution
 * @yres: the Y resolution
 *
 * Sets the resolution of the image/drawable displayed by the
 * #PrintPreview.
 **/
void
print_preview_set_image_dpi (PrintPreview *preview,
                             gdouble       xres,
                             gdouble       yres)
{
  gdouble width;
  gdouble height;

  g_return_if_fail (PRINT_IS_PREVIEW (preview));
  g_return_if_fail (xres > 0.0 && yres > 0.0);

  width  = gimp_drawable_get_width  (preview->drawable) * 72.0 / xres;
  height = gimp_drawable_get_height (preview->drawable) * 72.0 / yres;

  if (width != preview->image_width || height != preview->image_height)
    {
      preview->image_width  = width;
      preview->image_height = height;

      gtk_widget_queue_draw (GTK_WIDGET (preview));
    }
}

/**
 * print_preview_set_page_setup:
 * @preview: a #PrintPreview.
 * @page: the page setup to use
 *
 * Sets the page setup to use by the #PrintPreview.
 **/
void
print_preview_set_page_setup (PrintPreview *preview,
                              GtkPageSetup *page)
{
  g_return_if_fail (PRINT_IS_PREVIEW (preview));
  g_return_if_fail (GTK_IS_PAGE_SETUP (page));

  if (preview->page)
    g_object_unref (preview->page);

  preview->page = gtk_page_setup_copy (page);

  gtk_widget_queue_resize (GTK_WIDGET (preview));
}

/**
 * print_preview_set_image_offsets:
 * @preview: a #PrintPreview.
 * @offset_x: the X offset
 * @offset_y: the Y offset
 *
 * Sets the offsets of the image/drawable displayed by the #PrintPreview.
 * It does not emit the "offsets-changed" signal.
 **/
void
print_preview_set_image_offsets (PrintPreview *preview,
                                 gdouble       offset_x,
                                 gdouble       offset_y)
{
  g_return_if_fail (PRINT_IS_PREVIEW (preview));

  preview->image_offset_x = offset_x;
  preview->image_offset_y = offset_y;

  gtk_widget_queue_draw (GTK_WIDGET (preview));
}

/**
 * print_preview_set_image_offsets_max:
 * @preview: a #PrintPreview.
 * @offset_x_max: the maximum X offset allowed
 * @offset_y_max: the maximum Y offset allowed
 *
 * Sets the maximum offsets of the image/drawable displayed by the
 * #PrintPreview.  It does not emit the "offsets-changed" signal.
 **/
void
print_preview_set_image_offsets_max (PrintPreview *preview,
                                     gdouble       offset_x_max,
                                     gdouble       offset_y_max)
{
  g_return_if_fail (PRINT_IS_PREVIEW (preview));

  preview->image_offset_x_max = offset_x_max;
  preview->image_offset_y_max = offset_y_max;

  gtk_widget_queue_draw (GTK_WIDGET (preview));
}

/**
 * print_preview_set_use_full_page:
 * @preview: a #PrintPreview.
 * @full_page: TRUE to ignore the page margins
 *
 * If @full_page is TRUE, the page margins are ignored and the full page
 * can be used to setup printing.
 **/
void
print_preview_set_use_full_page (PrintPreview *preview,
                                 gboolean      full_page)
{
  g_return_if_fail (PRINT_IS_PREVIEW (preview));

  preview->use_full_page = full_page;

  gtk_widget_queue_draw (GTK_WIDGET (preview));
}

static gboolean
print_preview_is_inside (PrintPreview *preview,
                         gdouble       x,
                         gdouble       y)
{
  GtkWidget     *widget = GTK_WIDGET (preview);
  GtkAllocation  allocation;
  gdouble        left_margin;
  gdouble        right_margin;
  gdouble        top_margin;
  gdouble        bottom_margin;
  gdouble        scale;
  gint           border;

  gtk_widget_get_allocation (widget, &allocation);

  border = gtk_container_get_border_width (GTK_CONTAINER (widget)) + 1;

  x -= border;

  scale = print_preview_get_scale (preview);

  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
    {
      gdouble paper_width;
      gdouble paper_height;
      gint    width = allocation.width - 2 * border;

      print_preview_get_page_size (preview, &paper_width, &paper_height);

      x -= width - scale * paper_width;
    }

  print_preview_get_page_margins (preview,
                                  &left_margin, &right_margin,
                                  &top_margin,  &bottom_margin);

  x = x / scale - left_margin;
  y = y / scale - top_margin;

  return (x > preview->image_offset_x                        &&
          x < preview->image_offset_x + preview->image_width &&
          y > preview->image_offset_y                        &&
          y < preview->image_offset_y + preview->image_height);
}

static void
print_preview_set_inside (PrintPreview *preview,
                          gboolean      inside)
{
  if (inside != preview->inside)
    {
      GtkWidget *widget = GTK_WIDGET (preview);

      preview->inside = inside;

      if (gtk_widget_is_drawable (widget))
        gdk_window_set_cursor (gtk_widget_get_window (widget),
                               inside ? preview->cursor : NULL);

      gtk_widget_queue_draw (widget);
    }
}

static gdouble
print_preview_get_scale (PrintPreview *preview)
{
  GtkWidget     *widget = GTK_WIDGET (preview);
  GtkAllocation  allocation;
  gdouble        paper_width;
  gdouble        paper_height;
  gdouble        scale_x;
  gdouble        scale_y;
  gint           border;

  gtk_widget_get_allocation (widget, &allocation);

  border = gtk_container_get_border_width (GTK_CONTAINER (widget)) + 1;

  print_preview_get_page_size (preview, &paper_width, &paper_height);

  scale_x = (gdouble) (allocation.width  - 2 * border) / paper_width;
  scale_y = (gdouble) (allocation.height - 2 * border) / paper_height;

  return MIN (scale_x, scale_y);
}

static void
print_preview_get_page_size (PrintPreview *preview,
                             gdouble      *paper_width,
                             gdouble      *paper_height)
{
  *paper_width  = gtk_page_setup_get_paper_width  (preview->page,
                                                   GTK_UNIT_POINTS);
  *paper_height = gtk_page_setup_get_paper_height (preview->page,
                                                   GTK_UNIT_POINTS);
}

static void
print_preview_get_page_margins (PrintPreview *preview,
                                gdouble      *left_margin,
                                gdouble      *right_margin,
                                gdouble      *top_margin,
                                gdouble      *bottom_margin)
{
  if (preview->use_full_page)
    {
      *left_margin   = 0.0;
      *right_margin  = 0.0;
      *top_margin    = 0.0;
      *bottom_margin = 0.0;
    }
  else
    {
      *left_margin   = gtk_page_setup_get_left_margin   (preview->page,
                                                         GTK_UNIT_POINTS);
      *right_margin  = gtk_page_setup_get_right_margin  (preview->page,
                                                         GTK_UNIT_POINTS);
      *top_margin    = gtk_page_setup_get_top_margin    (preview->page,
                                                         GTK_UNIT_POINTS);
      *bottom_margin = gtk_page_setup_get_bottom_margin (preview->page,
                                                         GTK_UNIT_POINTS);
    }
}


/*  This thumbnail code should eventually end up in libgimpui.  */

static cairo_surface_t *
print_preview_get_thumbnail (GimpDrawable *drawable,
                             gint          width,
                             gint          height)
{
  cairo_surface_t *surface;
  cairo_format_t   format;
  GBytes          *data;
  guchar          *dest;
  const guchar    *src;
  gint             src_stride;
  gint             dest_stride;
  gint             y;
  gint             bpp;

  g_return_val_if_fail (width  > 0 && width  <= 1024, NULL);
  g_return_val_if_fail (height > 0 && height <= 1024, NULL);

  data = gimp_drawable_get_thumbnail_data (drawable,
                                           width, height,
                                           &width, &height, &bpp);

  /* For now, convert preview of higher bit depth to 8BPC */
  if (bpp > 4)
    {
      GimpImage *temp_image;
      GList     *layers = NULL;

      g_bytes_unref (data);

      temp_image =
        gimp_image_duplicate (gimp_item_get_image (GIMP_ITEM (drawable)));
      gimp_image_convert_precision (temp_image, GIMP_PRECISION_U8_NON_LINEAR);
      gimp_image_merge_visible_layers (temp_image, GIMP_CLIP_TO_IMAGE);

      layers = gimp_image_list_layers (temp_image);
      data   = gimp_drawable_get_thumbnail_data (GIMP_DRAWABLE (layers->data),
                                                 width, height,
                                                 &width, &height, &bpp);

      g_list_free (layers);
      gimp_image_delete (temp_image);
    }

  switch (bpp)
    {
    case 1:
    case 3:
      format = CAIRO_FORMAT_RGB24;
      break;

    case 2:
    case 4:
      format = CAIRO_FORMAT_ARGB32;
      break;

    default:
      g_assert_not_reached ();
      break;
    }

  surface = cairo_image_surface_create (format, width, height);

  src         = g_bytes_get_data (data, NULL);
  src_stride  = width * bpp;

  dest        = cairo_image_surface_get_data (surface);
  dest_stride = cairo_image_surface_get_stride (surface);

  for (y = 0; y < height; y++)
    {
      const guchar *s = src;
      guchar       *d = dest;
      gint          w = width;

      switch (bpp)
        {
        case 1:
          while (w--)
            {
              GIMP_CAIRO_RGB24_SET_PIXEL (d, s[0], s[0], s[0]);
              s += 1;
              d += 4;
            }
          break;

        case 2:
          while (w--)
            {
              GIMP_CAIRO_ARGB32_SET_PIXEL (d, s[0], s[0], s[0], s[1]);
              s += 2;
              d += 4;
            }
          break;

        case 3:
          while (w--)
            {
              GIMP_CAIRO_RGB24_SET_PIXEL (d, s[0], s[1], s[2]);
              s += 3;
              d += 4;
            }
          break;

        case 4:
          while (w--)
            {
              GIMP_CAIRO_ARGB32_SET_PIXEL (d, s[0], s[1], s[2], s[3]);
              s += 4;
              d += 4;
            }
          break;
        }

      src  += src_stride;
      dest += dest_stride;
    }

  g_bytes_unref (data);

  cairo_surface_mark_dirty (surface);

  return surface;
}
