2010-08-27 23:22:10 +03:00
|
|
|
/* GIMP - The GNU Image Manipulation Program
|
|
|
|
*
|
2024-04-21 16:36:01 +02:00
|
|
|
* file-pdf-export.c - PDF file exporter, based on the cairo PDF surface
|
2010-08-27 23:22:10 +03:00
|
|
|
*
|
|
|
|
* Copyright (C) 2010 Barak Itkin <lightningismyname@gmail.com>
|
|
|
|
*
|
|
|
|
* 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
|
2018-07-11 23:27:07 +02:00
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2010-08-27 23:22:10 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
/* The PDF export plugin has 3 main procedures:
|
2024-04-13 15:10:25 +00:00
|
|
|
* 1. file-pdf-export
|
2010-08-27 23:22:10 +03:00
|
|
|
* This is the main procedure. It has 3 options for optimizations of
|
|
|
|
* the pdf file, and it can show a gui. This procedure works on a single
|
|
|
|
* image.
|
2024-04-13 15:10:25 +00:00
|
|
|
* 2. file-pdf-export-defaults
|
2010-08-27 23:22:10 +03:00
|
|
|
* This procedures is the one that will be invoked by gimp's file-save,
|
|
|
|
* when the pdf extension is chosen. If it's in RUN_INTERACTIVE, it will
|
2024-04-13 15:10:25 +00:00
|
|
|
* pop a user interface with more options, like file-pdf-export. If it's in
|
2010-08-27 23:22:10 +03:00
|
|
|
* RUN_NONINTERACTIVE, it will simply use the default values. Note that on
|
|
|
|
* RUN_WITH_LAST_VALS there will be no gui, however the values will be the
|
|
|
|
* ones that were used in the last interactive run (or the defaults if none
|
|
|
|
* are available.
|
2024-04-13 15:10:25 +00:00
|
|
|
* 3. file-pdf-export-multi
|
2010-08-27 23:22:10 +03:00
|
|
|
* This procedures is more advanced, and it allows the creation of multiple
|
|
|
|
* paged pdf files. It will be located in File/Create/Multiple page PDF...
|
|
|
|
*
|
2024-04-13 15:10:25 +00:00
|
|
|
* It was suggested that file-pdf-export-multi will be removed from the UI as it
|
2010-08-27 23:22:10 +03:00
|
|
|
* does not match the product vision (GIMP isn't a program for editing multiple
|
|
|
|
* paged documents).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Known Issues (except for the coding style issues):
|
|
|
|
* 1. Grayscale layers are inverted (although layer masks which are not grayscale,
|
|
|
|
* are not inverted)
|
|
|
|
* 2. Exporting some fonts doesn't work since gimp_text_layer_get_font Returns a
|
2019-09-21 17:10:46 +00:00
|
|
|
* font which is sometimes incompatible with pango_font_description_from_string
|
2010-08-27 23:22:10 +03:00
|
|
|
* (gimp_text_layer_get_font sometimes returns suffixes such as "semi-expanded" to
|
|
|
|
* the font's name although the GIMP's font selection dialog shows the don'ts name
|
|
|
|
* normally - This should be checked again in GIMP 2.7)
|
|
|
|
* 3. Indexed layers can't be optimized yet (Since gimp_histogram won't work on
|
|
|
|
* indexed layers)
|
|
|
|
* 4. Rendering the pango layout requires multiplying the size in PANGO_SCALE. This
|
|
|
|
* means I'll need to do some hacking on the markup returned from GIMP.
|
|
|
|
* 5. When accessing the contents of layer groups is supported, we should do use it
|
|
|
|
* (since this plugin should preserve layers).
|
|
|
|
*
|
|
|
|
* Also, there are 2 things which we should warn the user about:
|
|
|
|
* 1. Cairo does not support bitmap masks for text.
|
|
|
|
* 2. Currently layer modes are ignored. We do support layers, including
|
|
|
|
* transparency and opacity, but layer modes are not supported.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Changelog
|
|
|
|
*
|
|
|
|
* April 29, 2009 | Barak Itkin <lightningismyname@gmail.com>
|
|
|
|
* First version of the plugin. This is only a proof of concept and not a full
|
|
|
|
* working plugin.
|
|
|
|
*
|
|
|
|
* May 6, 2009 Barak | Itkin <lightningismyname@gmail.com>
|
|
|
|
* Added new features and several bugfixes:
|
|
|
|
* - Added handling for image resolutions
|
2023-07-21 15:35:23 -04:00
|
|
|
* - fixed the behavior of getting font sizes
|
2010-08-27 23:22:10 +03:00
|
|
|
* - Added various optimizations (solid rectangles instead of bitmaps, ignoring
|
|
|
|
* invisible layers, etc.) as a macro flag.
|
|
|
|
* - Added handling for layer masks, use CAIRO_FORMAT_A8 for grayscale drawables.
|
|
|
|
* - Indexed layers are now supported
|
|
|
|
*
|
|
|
|
* August 17, 2009 | Barak Itkin <lightningismyname@gmail.com>
|
|
|
|
* Most of the plugin was rewritten from scratch and it now has several new
|
|
|
|
* features:
|
2012-11-11 15:50:25 +01:00
|
|
|
* - Got rid of the optimization macros in the code. The gui now supports
|
|
|
|
* selecting which optimizations to apply.
|
2010-08-27 23:22:10 +03:00
|
|
|
* - Added a procedure to allow the creation of multiple paged PDF's
|
|
|
|
* - Registered the plugin on "<Image>/File/Create/PDF"
|
|
|
|
*
|
|
|
|
* August 21, 2009 | Barak Itkin <lightningismyname@gmail.com>
|
|
|
|
* Fixed a typo that prevented the plugin from compiling...
|
|
|
|
* A migration to the new GIMP 2.8 api, which includes:
|
|
|
|
* - Now using gimp_export_dialog_new
|
2019-09-21 17:10:46 +00:00
|
|
|
* - Using gimp_text_layer_get_hint_style (2.8) instead of the deprecated
|
2010-08-27 23:22:10 +03:00
|
|
|
* gimp_text_layer_get_hinting (2.6).
|
|
|
|
*
|
|
|
|
* August 24, 2010 | Barak Itkin <lightningismyname@gmail.com>
|
|
|
|
* More migrations to the new GIMP 2.8 api:
|
|
|
|
* - Now using the GimpItem api
|
|
|
|
* - Using gimp_text_layer_get_markup where possible
|
|
|
|
* - Fixed some compiler warnings
|
|
|
|
* Also merged the header and c file into one file, Updated some of the comments
|
|
|
|
* and documentation, and moved this into the main source repository.
|
|
|
|
*/
|
|
|
|
|
2010-08-28 12:44:30 +02:00
|
|
|
#include "config.h"
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2014-07-29 16:55:01 +00:00
|
|
|
#include <errno.h>
|
|
|
|
|
2012-06-09 15:36:35 +02:00
|
|
|
#include <glib/gstdio.h>
|
2010-08-27 23:22:10 +03:00
|
|
|
#include <cairo-pdf.h>
|
|
|
|
#include <pango/pangocairo.h>
|
2010-08-28 12:44:30 +02:00
|
|
|
|
|
|
|
#include <libgimp/gimp.h>
|
|
|
|
#include <libgimp/gimpui.h>
|
|
|
|
|
2011-11-29 05:27:02 +04:00
|
|
|
#include "libgimp/stdplugins-intl.h"
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
#define EXPORT_PROC "file-pdf-export"
|
|
|
|
#define EXPORT_MULTI_PROC "file-pdf-export-multi"
|
|
|
|
#define PLUG_IN_BINARY "file-pdf-export"
|
|
|
|
#define PLUG_IN_ROLE "gimp-file-pdf-export"
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
#define DATA_IMAGE_LIST "file-pdf-data-multi-page"
|
|
|
|
|
|
|
|
/* Gimp will crash before you reach this limitation :D */
|
|
|
|
#define MAX_PAGE_COUNT 350
|
|
|
|
#define MAX_FILE_NAME_LENGTH 350
|
|
|
|
|
|
|
|
#define THUMB_WIDTH 90
|
|
|
|
#define THUMB_HEIGHT 120
|
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
#define GIMP_PLUGIN_PDF_EXPORT_ERROR gimp_plugin_pdf_export_error_quark ()
|
2014-04-29 17:37:25 +02:00
|
|
|
|
|
|
|
typedef enum
|
|
|
|
{
|
2024-04-13 15:10:25 +00:00
|
|
|
GIMP_PLUGIN_PDF_EXPORT_ERROR_FAILED
|
2017-12-10 05:15:40 +01:00
|
|
|
} GimpPluginPDFError;
|
2014-04-29 17:37:25 +02:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
GQuark gimp_plugin_pdf_export_error_quark (void);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
GimpImage *images[MAX_PAGE_COUNT];
|
|
|
|
guint32 image_count;
|
|
|
|
gchar file_name[MAX_FILE_NAME_LENGTH];
|
2010-08-27 23:22:10 +03:00
|
|
|
} PdfMultiPage;
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
enum
|
|
|
|
{
|
2010-08-27 23:22:10 +03:00
|
|
|
THUMB,
|
|
|
|
PAGE_NUMBER,
|
|
|
|
IMAGE_NAME,
|
2019-08-27 14:12:50 +02:00
|
|
|
IMAGE
|
2010-08-27 23:22:10 +03:00
|
|
|
};
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
typedef struct
|
|
|
|
{
|
2010-08-27 23:22:10 +03:00
|
|
|
GdkPixbuf *thumb;
|
2012-11-21 15:04:15 +01:00
|
|
|
gint32 page_number;
|
|
|
|
gchar *image_name;
|
2010-08-27 23:22:10 +03:00
|
|
|
} Page;
|
|
|
|
|
2010-08-28 12:44:30 +02:00
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
typedef struct _Pdf Pdf;
|
|
|
|
typedef struct _PdfClass PdfClass;
|
2014-04-29 17:37:25 +02:00
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
struct _Pdf
|
|
|
|
{
|
|
|
|
GimpPlugIn parent_instance;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _PdfClass
|
|
|
|
{
|
|
|
|
GimpPlugInClass parent_class;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#define PDF_TYPE (pdf_get_type ())
|
2023-10-18 18:29:37 +02:00
|
|
|
#define PDF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PDF_TYPE, Pdf))
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-11-01 19:13:02 +01:00
|
|
|
GType pdf_get_type (void) G_GNUC_CONST;
|
|
|
|
|
|
|
|
static GList * pdf_query_procedures (GimpPlugIn *plug_in);
|
|
|
|
static GimpProcedure * pdf_create_procedure (GimpPlugIn *plug_in,
|
|
|
|
const gchar *name);
|
|
|
|
|
|
|
|
static GimpValueArray * pdf_export (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
|
|
|
GFile *file,
|
|
|
|
GimpExportOptions *options,
|
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data);
|
|
|
|
static GimpValueArray * pdf_export_multi (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data);
|
|
|
|
|
|
|
|
static GimpPDBStatusType pdf_export_image (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GimpExportOptions *options,
|
|
|
|
gboolean single_image,
|
|
|
|
gboolean show_progress,
|
|
|
|
GError **error);
|
|
|
|
|
|
|
|
static GimpExportCapabilities export_edit_options (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GimpExportOptions *options,
|
|
|
|
gpointer create_data);
|
|
|
|
|
|
|
|
static void init_image_list_defaults (GimpImage *image);
|
|
|
|
|
|
|
|
static void validate_image_list (void);
|
|
|
|
|
|
|
|
static gboolean gui_single (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GimpImage *image);
|
|
|
|
static gboolean gui_multi (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config);
|
|
|
|
|
|
|
|
static void choose_file_call (GtkWidget *browse_button,
|
|
|
|
gpointer file_entry);
|
|
|
|
|
|
|
|
static gboolean get_image_list (void);
|
|
|
|
|
|
|
|
static GtkTreeModel * create_model (void);
|
|
|
|
|
|
|
|
static void add_image_call (GtkWidget *widget,
|
|
|
|
gpointer img_combo);
|
|
|
|
static void del_image_call (GtkWidget *widget,
|
|
|
|
gpointer icon_view);
|
|
|
|
static void remove_call (GtkTreeModel *tree_model,
|
|
|
|
GtkTreePath *path,
|
|
|
|
gpointer user_data);
|
|
|
|
static void recount_pages (void);
|
2014-04-29 17:37:25 +02:00
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
static cairo_surface_t *get_cairo_surface (GimpDrawable *drawable,
|
|
|
|
gboolean as_mask,
|
|
|
|
GError **error);
|
|
|
|
|
2024-03-20 02:53:41 +00:00
|
|
|
static GeglColor * get_layer_color (GimpLayer *layer,
|
2019-08-27 14:12:50 +02:00
|
|
|
gboolean *single);
|
|
|
|
|
|
|
|
static void drawText (GimpLayer *layer,
|
|
|
|
gdouble opacity,
|
|
|
|
cairo_t *cr,
|
|
|
|
gdouble x_res,
|
|
|
|
gdouble y_res);
|
|
|
|
|
|
|
|
static gboolean draw_layer (GimpLayer **layers,
|
|
|
|
gint n_layers,
|
2022-09-10 22:16:11 +02:00
|
|
|
GimpProcedureConfig *config,
|
2024-03-24 02:20:05 +00:00
|
|
|
gboolean single_image,
|
2019-08-27 14:12:50 +02:00
|
|
|
gint j,
|
|
|
|
cairo_t *cr,
|
|
|
|
gdouble x_res,
|
|
|
|
gdouble y_res,
|
|
|
|
const gchar *name,
|
2021-02-15 22:21:30 +01:00
|
|
|
gboolean show_progress,
|
|
|
|
gdouble progress_start,
|
|
|
|
gdouble progress_end,
|
2022-09-10 22:56:01 +02:00
|
|
|
gint layer_level,
|
2019-08-27 14:12:50 +02:00
|
|
|
GError **error);
|
|
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (Pdf, pdf, GIMP_TYPE_PLUG_IN)
|
|
|
|
|
|
|
|
GIMP_MAIN (PDF_TYPE)
|
2022-05-26 00:59:36 +02:00
|
|
|
DEFINE_STD_SET_I18N
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
static gboolean dnd_remove = TRUE;
|
2010-08-27 23:22:10 +03:00
|
|
|
static PdfMultiPage multi_page;
|
|
|
|
|
|
|
|
static GtkTreeModel *model;
|
2012-11-21 15:04:15 +01:00
|
|
|
static GtkWidget *file_choose;
|
|
|
|
static gchar *file_name;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
G_DEFINE_QUARK (gimp-plugin-pdf-export-error-quark, gimp_plugin_pdf_export_error)
|
2014-04-29 17:37:25 +02:00
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
static void
|
|
|
|
pdf_class_init (PdfClass *klass)
|
|
|
|
{
|
|
|
|
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
|
|
|
|
|
|
|
|
plug_in_class->query_procedures = pdf_query_procedures;
|
|
|
|
plug_in_class->create_procedure = pdf_create_procedure;
|
2022-05-26 00:59:36 +02:00
|
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
2019-08-27 14:12:50 +02:00
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2011-12-31 00:57:16 +02:00
|
|
|
static void
|
2019-08-27 14:12:50 +02:00
|
|
|
pdf_init (Pdf *pdf)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
}
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
static GList *
|
|
|
|
pdf_query_procedures (GimpPlugIn *plug_in)
|
|
|
|
{
|
|
|
|
GList *list = NULL;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
list = g_list_append (list, g_strdup (EXPORT_PROC));
|
|
|
|
list = g_list_append (list, g_strdup (EXPORT_MULTI_PROC));
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
return list;
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
static GimpProcedure *
|
|
|
|
pdf_create_procedure (GimpPlugIn *plug_in,
|
|
|
|
const gchar *name)
|
2012-06-09 15:36:35 +02:00
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
GimpProcedure *procedure = NULL;
|
|
|
|
|
2024-04-13 15:10:25 +00:00
|
|
|
if (! strcmp (name, EXPORT_PROC))
|
2019-08-27 14:12:50 +02:00
|
|
|
{
|
2024-04-20 03:08:57 +00:00
|
|
|
procedure = gimp_export_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
|
|
|
TRUE, pdf_export, NULL, NULL);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_image_types (procedure, "*");
|
|
|
|
|
2022-07-04 22:50:53 +02:00
|
|
|
gimp_procedure_set_menu_label (procedure, _("Portable Document Format"));
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
2024-03-24 02:20:05 +00:00
|
|
|
_("Save files in PDF format"),
|
|
|
|
_("Saves files in Adobe's Portable "
|
|
|
|
"Document Format. PDF is designed to "
|
|
|
|
"be easily processed by a variety of "
|
|
|
|
"different platforms, and is a "
|
|
|
|
"distant cousin of PostScript."),
|
|
|
|
name);
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_procedure_set_attribution (procedure,
|
|
|
|
"Barak Itkin, Lionel N., Jehan",
|
|
|
|
"Copyright Barak Itkin, Lionel N., Jehan",
|
|
|
|
"August 2009, 2017");
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
_("PDF"));
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"application/pdf");
|
|
|
|
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"pdf");
|
|
|
|
|
2024-05-06 18:38:12 +00:00
|
|
|
gimp_export_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
|
2024-11-01 19:13:02 +01:00
|
|
|
0, export_edit_options, NULL, NULL);
|
2024-05-06 18:38:12 +00:00
|
|
|
|
2024-06-12 16:53:12 +00:00
|
|
|
gimp_procedure_add_boolean_argument (procedure, "vectorize",
|
|
|
|
_("Convert _bitmaps to vector graphics where possible"),
|
|
|
|
_("Convert bitmaps to vector graphics where possible"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "ignore-hidden",
|
|
|
|
_("O_mit hidden layers and layers with zero opacity"),
|
|
|
|
_("Non-visible layers will not be exported"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "apply-masks",
|
|
|
|
_("_Apply layer masks"),
|
|
|
|
_("Apply layer masks before saving (Keeping the mask "
|
|
|
|
"will not change the output, only the PDF structure)"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "layers-as-pages",
|
|
|
|
_("La_yers as pages"),
|
|
|
|
_("Layers as pages (bottom layers first)."),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "reverse-order",
|
|
|
|
_("Re_verse order"),
|
|
|
|
_("Reverse the pages order (top layers first)."),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "root-layers-only",
|
|
|
|
_("Roo_t layers only"),
|
|
|
|
_("Only the root layers are considered pages"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "convert-text-layers",
|
|
|
|
_("Convert te_xt layers to image"),
|
|
|
|
_("Convert text layers to raster graphics"),
|
|
|
|
FALSE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "fill-background-color",
|
|
|
|
_("_Fill transparent areas with background color"),
|
|
|
|
_("Fill transparent areas with background color if "
|
|
|
|
"layer has an alpha channel"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
2019-08-27 14:12:50 +02:00
|
|
|
}
|
2024-04-13 15:10:25 +00:00
|
|
|
else if (! strcmp (name, EXPORT_MULTI_PROC))
|
2019-08-27 14:12:50 +02:00
|
|
|
{
|
2023-06-16 18:34:53 +02:00
|
|
|
procedure = gimp_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
2024-04-13 15:10:25 +00:00
|
|
|
pdf_export_multi, NULL, NULL);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
gimp_procedure_set_image_types (procedure, "*");
|
|
|
|
|
2022-07-04 22:50:53 +02:00
|
|
|
gimp_procedure_set_menu_label (procedure, _("_Create multipage PDF..."));
|
2019-08-27 14:12:50 +02:00
|
|
|
#if 0
|
|
|
|
gimp_procedure_add_menu_path (procedure, "<Image>/File/Create/PDF");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
2024-03-24 02:20:05 +00:00
|
|
|
_("Save files in PDF format"),
|
|
|
|
_("Saves files in Adobe's Portable "
|
|
|
|
"Document Format. PDF is designed to "
|
|
|
|
"be easily processed by a variety of "
|
|
|
|
"different platforms, and is a "
|
|
|
|
"distant cousin of PostScript."),
|
2019-08-27 14:12:50 +02:00
|
|
|
name);
|
|
|
|
gimp_procedure_set_attribution (procedure,
|
|
|
|
"Barak Itkin",
|
|
|
|
"Copyright Barak Itkin",
|
|
|
|
"August 2009");
|
|
|
|
|
2024-06-12 16:53:12 +00:00
|
|
|
gimp_procedure_add_enum_argument (procedure, "run-mode",
|
|
|
|
"Run mode",
|
|
|
|
"The run mode",
|
|
|
|
GIMP_TYPE_RUN_MODE,
|
|
|
|
GIMP_RUN_INTERACTIVE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
2024-10-24 17:43:09 +02:00
|
|
|
gimp_procedure_add_core_object_array_argument (procedure, "images",
|
|
|
|
"Images",
|
|
|
|
"Input image for each page (An image can "
|
|
|
|
"appear more than once)",
|
|
|
|
GIMP_TYPE_IMAGE,
|
|
|
|
G_PARAM_READWRITE);
|
2024-06-12 16:53:12 +00:00
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "vectorize",
|
|
|
|
_("Convert _bitmaps to vector graphics where possible"),
|
|
|
|
_("Convert bitmaps to vector graphics where possible"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "ignore-hidden",
|
|
|
|
_("O_mit hidden layers and layers with zero opacity"),
|
|
|
|
_("Non-visible layers will not be exported"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "apply-masks",
|
|
|
|
_("_Apply layer masks"),
|
|
|
|
_("Apply layer masks before saving (Keeping the mask "
|
|
|
|
"will not change the output, only the PDF structure)"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_boolean_argument (procedure, "fill-background-color",
|
|
|
|
_("_Fill transparent areas with background color"),
|
|
|
|
_("Fill transparent areas with background color if "
|
|
|
|
"layer has an alpha channel"),
|
|
|
|
TRUE,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
gimp_procedure_add_string_argument (procedure, "uri",
|
|
|
|
_("Save to"),
|
|
|
|
_("The URI of the file to save to"),
|
|
|
|
NULL,
|
|
|
|
GIMP_PARAM_READWRITE);
|
2019-08-27 14:12:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return procedure;
|
2012-06-09 15:36:35 +02:00
|
|
|
}
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
static GimpValueArray *
|
2024-04-13 15:10:25 +00:00
|
|
|
pdf_export (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
|
|
|
GFile *file,
|
2024-05-06 18:38:12 +00:00
|
|
|
GimpExportOptions *options,
|
2024-04-13 15:10:25 +00:00
|
|
|
GimpMetadata *metadata,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2024-04-30 13:50:24 +00:00
|
|
|
GError *error = NULL;
|
|
|
|
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2013-01-15 14:24:20 +01:00
|
|
|
gegl_init (NULL, NULL);
|
2011-11-30 21:13:26 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
/* Initializing all the settings */
|
|
|
|
multi_page.image_count = 0;
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
file_name = g_file_get_path (file);
|
|
|
|
|
2023-10-23 18:19:10 +02:00
|
|
|
init_image_list_defaults (image);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
validate_image_list ();
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE)
|
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
if (! gui_single (procedure, config, image))
|
|
|
|
status = GIMP_PDB_CANCEL;
|
2019-08-27 14:12:50 +02:00
|
|
|
}
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
if (status == GIMP_PDB_SUCCESS)
|
2024-05-06 18:38:12 +00:00
|
|
|
status = pdf_export_image (procedure, config, options, TRUE,
|
2024-04-13 15:10:25 +00:00
|
|
|
(run_mode != GIMP_RUN_NONINTERACTIVE),
|
|
|
|
&error);
|
2022-09-10 22:16:11 +02:00
|
|
|
|
|
|
|
return gimp_procedure_new_return_values (procedure, status, error);
|
2019-08-27 14:12:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static GimpValueArray *
|
2024-04-13 15:10:25 +00:00
|
|
|
pdf_export_multi (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
gpointer run_data)
|
2019-08-27 14:12:50 +02:00
|
|
|
{
|
2024-03-24 02:20:05 +00:00
|
|
|
GError *error = NULL;
|
|
|
|
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
|
2023-10-23 18:19:10 +02:00
|
|
|
GimpRunMode run_mode;
|
2024-05-06 18:38:12 +00:00
|
|
|
GimpExportOptions *options = NULL;
|
2023-10-23 18:19:10 +02:00
|
|
|
gchar *uri;
|
2024-10-24 17:43:09 +02:00
|
|
|
GimpImage **images = NULL;
|
2024-03-24 02:20:05 +00:00
|
|
|
GimpImage *image = NULL;
|
|
|
|
GFile *file = NULL;
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
g_object_get (config,
|
2023-06-15 04:39:28 +02:00
|
|
|
"run-mode", &run_mode,
|
|
|
|
"uri", &uri,
|
2024-10-24 17:43:09 +02:00
|
|
|
"images", &images,
|
2022-09-10 22:16:11 +02:00
|
|
|
NULL);
|
|
|
|
|
app, libgimp*, pdb, plug-ins: review and enhance MR !1549.
- Fix annotations for gimp_export_options_get_image() to make it
actually introspectable with the GimpImage being both input and
output. Even though the logic doesn't change much (the input image may
be overriden or not), it doesn't matter for introspection because
images are handled centrally by libgimp and therefore must not be
freed. Actually deleting the image from the central list of images
though remains a manual action depending on code logic, not some
automatic action to be handled by binding engines.
- Add G_GNUC_WARN_UNUSED_RESULT to gimp_export_options_get_image()
because ignoring the returned value is rarely a good idea (as you
usually want to delete the image).
- Remove gimp_export_options_new(): we don't need this constructor
because at this point, the best is to tell plug-in developers to just
pass NULL everywhere. This leaves us free to create a more useful
default constructor if needed, in the future. Main description for
GimpExportOptions has also been updated to say this.
- Add a data_destroy callback for the user data passed in
gimp_export_procedure_set_capabilities().
- Fixing annotations of 'export_options' object from pdb/pdb.pl: input
args would actually be (nullable) and would not transfer ownership
(calling code must still free the object). Return value's ownership on
the other hand is fully transfered.
- Add C and Python unit testing for GimpExportOptions and
gimp_export_options_get_image() in particular.
- Fix or improve various details.
Note that I have also considered for a long time changing the signature
of gimp_export_options_get_image() to return a boolean indicating
whether `image` had been replaced (hence needed deletion) or not. This
also meant getting rid of the GimpExportReturn enum. Right now it would
work because there are no third case, but I was considering the future
possibility that for instance we got some impossible conversion for some
future capability. I'm not sure it would ever happen; and for sure, this
is not desirable because it implies an export failure a bit late in the
workflow. But just in case, let's keep the enum return value. It does
not even make the using code that much more complicated (well just a
value comparison instead of a simple boolean test).
2024-08-17 15:06:27 +02:00
|
|
|
options = g_object_new (GIMP_TYPE_EXPORT_OPTIONS,
|
|
|
|
"capabilities",
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_RGB |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_ALPHA |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_GRAY |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_LAYERS |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_INDEXED,
|
|
|
|
NULL);
|
2024-05-06 18:38:12 +00:00
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
if (uri != NULL)
|
|
|
|
{
|
|
|
|
file = g_file_new_for_uri (uri);
|
|
|
|
g_free (uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file != NULL)
|
|
|
|
file_name = g_file_get_path (file);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2024-10-24 17:43:09 +02:00
|
|
|
if (images)
|
|
|
|
{
|
|
|
|
multi_page.image_count = gimp_core_object_array_get_length ((GObject **) images);
|
|
|
|
for (gint i = 0; i < multi_page.image_count; i++)
|
|
|
|
multi_page.images[i] = images[i];
|
|
|
|
}
|
2023-10-23 18:19:10 +02:00
|
|
|
else
|
2024-10-24 17:43:09 +02:00
|
|
|
{
|
|
|
|
init_image_list_defaults (image);
|
|
|
|
}
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
validate_image_list ();
|
|
|
|
|
|
|
|
/* Starting the executions */
|
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE)
|
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
if (! gui_multi (procedure, config))
|
|
|
|
status = GIMP_PDB_CANCEL;
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
if (status == GIMP_PDB_SUCCESS)
|
2024-05-06 18:38:12 +00:00
|
|
|
status = pdf_export_image (procedure, config, options, FALSE,
|
2024-04-13 15:10:25 +00:00
|
|
|
(run_mode != GIMP_RUN_NONINTERACTIVE),
|
|
|
|
&error);
|
2022-09-10 22:16:11 +02:00
|
|
|
|
|
|
|
return gimp_procedure_new_return_values (procedure, status, error);
|
2019-08-27 14:12:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static cairo_status_t
|
|
|
|
write_func (void *fp,
|
|
|
|
const unsigned char *data,
|
|
|
|
unsigned int size)
|
|
|
|
{
|
|
|
|
return fwrite (data, 1, size, fp) == size ? CAIRO_STATUS_SUCCESS
|
|
|
|
: CAIRO_STATUS_WRITE_ERROR;
|
|
|
|
}
|
|
|
|
|
2020-01-01 17:53:02 +01:00
|
|
|
static GList *
|
|
|
|
get_missing_fonts (GList *layers)
|
|
|
|
{
|
|
|
|
GList *missing_fonts = NULL;
|
|
|
|
GList *iter;
|
|
|
|
|
|
|
|
for (iter = layers; iter; iter = iter->next)
|
|
|
|
{
|
|
|
|
GimpLayer *layer = iter->data;
|
|
|
|
|
|
|
|
if (gimp_item_is_group (GIMP_ITEM (layer)))
|
|
|
|
{
|
2020-01-01 18:28:04 +01:00
|
|
|
GList *child_missing_fonts;
|
|
|
|
GList *iter2;
|
|
|
|
|
|
|
|
child_missing_fonts = get_missing_fonts (gimp_item_list_children (GIMP_ITEM (layer)));
|
|
|
|
for (iter2 = child_missing_fonts; iter2; iter2 = iter2->next)
|
|
|
|
{
|
|
|
|
gchar *missing = iter2->data;
|
|
|
|
|
|
|
|
if (g_list_find_custom (missing_fonts, missing, (GCompareFunc) g_strcmp0) == NULL)
|
|
|
|
missing_fonts = g_list_prepend (missing_fonts, missing);
|
|
|
|
else
|
|
|
|
g_free (missing);
|
|
|
|
}
|
|
|
|
g_list_free (child_missing_fonts);
|
2020-01-01 17:53:02 +01:00
|
|
|
}
|
|
|
|
else if (gimp_item_is_text_layer (GIMP_ITEM (layer)))
|
|
|
|
{
|
2023-09-13 19:13:51 +02:00
|
|
|
GimpFont *gimp_font;
|
2020-01-01 17:53:02 +01:00
|
|
|
PangoFontDescription *font_description;
|
|
|
|
PangoFontDescription *font_description2;
|
|
|
|
PangoFontMap *fontmap;
|
|
|
|
PangoFont *font;
|
|
|
|
PangoContext *context;
|
|
|
|
|
|
|
|
fontmap = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
|
|
|
|
context = pango_font_map_create_context (fontmap);
|
|
|
|
|
2023-09-13 19:13:51 +02:00
|
|
|
gimp_font = gimp_text_layer_get_font (GIMP_TEXT_LAYER (layer));
|
|
|
|
font_description = gimp_font_get_pango_font_description (gimp_font);
|
2020-01-01 17:53:02 +01:00
|
|
|
|
|
|
|
font = pango_font_map_load_font (fontmap, context, font_description);
|
|
|
|
font_description2 = pango_font_describe (font);
|
|
|
|
if (g_strcmp0 (pango_font_description_get_family (font_description),
|
|
|
|
pango_font_description_get_family (font_description2)) != 0)
|
|
|
|
{
|
|
|
|
const gchar *missing = pango_font_description_get_family (font_description);
|
|
|
|
|
|
|
|
if (g_list_find_custom (missing_fonts, missing, (GCompareFunc) g_strcmp0) == NULL)
|
|
|
|
missing_fonts = g_list_prepend (missing_fonts,
|
|
|
|
g_strdup (missing));
|
|
|
|
}
|
|
|
|
|
|
|
|
g_object_unref (font);
|
|
|
|
pango_font_description_free (font_description);
|
|
|
|
pango_font_description_free (font_description2);
|
|
|
|
g_object_unref (context);
|
|
|
|
g_object_unref (fontmap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_list_free (layers);
|
|
|
|
|
|
|
|
return missing_fonts;
|
|
|
|
}
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
static GimpPDBStatusType
|
2024-04-13 15:10:25 +00:00
|
|
|
pdf_export_image (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
2024-05-06 18:38:12 +00:00
|
|
|
GimpExportOptions *options,
|
2024-04-13 15:10:25 +00:00
|
|
|
gboolean single_image,
|
|
|
|
gboolean show_progress,
|
|
|
|
GError **error)
|
2019-08-27 14:12:50 +02:00
|
|
|
{
|
2024-05-06 18:38:12 +00:00
|
|
|
cairo_surface_t *pdf_file;
|
|
|
|
cairo_t *cr;
|
|
|
|
FILE *fp;
|
|
|
|
gint i;
|
|
|
|
gboolean layers_as_pages = FALSE;
|
|
|
|
gboolean fill_background_color;
|
2022-09-10 22:16:11 +02:00
|
|
|
|
|
|
|
g_object_get (config,
|
2023-01-27 03:31:55 +00:00
|
|
|
"fill-background-color", &fill_background_color,
|
2022-09-10 22:16:11 +02:00
|
|
|
NULL);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
if (single_image)
|
|
|
|
g_object_get (config, "layers-as-pages", &layers_as_pages, NULL);
|
|
|
|
|
2012-06-09 15:36:35 +02:00
|
|
|
fp = g_fopen (file_name, "wb");
|
2019-08-27 14:12:50 +02:00
|
|
|
if (! fp)
|
2014-07-28 19:28:13 +00:00
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
2019-08-27 14:12:50 +02:00
|
|
|
_("Could not open '%s' for writing: %s"),
|
|
|
|
gimp_filename_to_utf8 (file_name), g_strerror (errno));
|
2014-07-29 16:55:01 +00:00
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
return GIMP_PDB_EXECUTION_ERROR;
|
2014-07-28 19:28:13 +00:00
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2012-06-09 15:36:35 +02:00
|
|
|
pdf_file = cairo_pdf_surface_create_for_stream (write_func, fp, 1, 1);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
if (cairo_surface_status (pdf_file) != CAIRO_STATUS_SUCCESS)
|
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
|
|
_("An error occurred while creating the PDF file:\n"
|
|
|
|
"%s\n"
|
|
|
|
"Make sure you entered a valid filename and that the "
|
|
|
|
"selected location isn't read only!"),
|
|
|
|
cairo_status_to_string (cairo_surface_status (pdf_file)));
|
|
|
|
|
|
|
|
return GIMP_PDB_EXECUTION_ERROR;
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
cr = cairo_create (pdf_file);
|
|
|
|
|
|
|
|
for (i = 0; i < multi_page.image_count; i++)
|
|
|
|
{
|
2020-04-14 11:46:17 +02:00
|
|
|
GimpImage *image = multi_page.images[i];
|
|
|
|
GimpLayer **layers;
|
|
|
|
gint32 n_layers;
|
|
|
|
gdouble x_res, y_res;
|
|
|
|
gdouble x_scale, y_scale;
|
2024-10-21 21:59:26 +02:00
|
|
|
GimpDrawable **drawables;
|
2020-04-14 11:46:17 +02:00
|
|
|
gint j;
|
|
|
|
|
2024-10-21 21:59:26 +02:00
|
|
|
drawables = gimp_image_get_selected_drawables (image);
|
|
|
|
if (drawables[0] == NULL)
|
2022-07-19 22:44:58 +02:00
|
|
|
{
|
|
|
|
g_free (drawables);
|
|
|
|
continue;
|
|
|
|
}
|
2024-10-21 21:59:26 +02:00
|
|
|
g_free (drawables);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
|
|
|
/* Save the state of the surface before any changes, so that
|
|
|
|
* settings from one page won't affect all the others
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
cairo_save (cr);
|
|
|
|
|
2024-05-06 18:38:12 +00:00
|
|
|
if (! (gimp_export_options_get_image (options,
|
app, libgimp*, pdb, plug-ins: review and enhance MR !1549.
- Fix annotations for gimp_export_options_get_image() to make it
actually introspectable with the GimpImage being both input and
output. Even though the logic doesn't change much (the input image may
be overriden or not), it doesn't matter for introspection because
images are handled centrally by libgimp and therefore must not be
freed. Actually deleting the image from the central list of images
though remains a manual action depending on code logic, not some
automatic action to be handled by binding engines.
- Add G_GNUC_WARN_UNUSED_RESULT to gimp_export_options_get_image()
because ignoring the returned value is rarely a good idea (as you
usually want to delete the image).
- Remove gimp_export_options_new(): we don't need this constructor
because at this point, the best is to tell plug-in developers to just
pass NULL everywhere. This leaves us free to create a more useful
default constructor if needed, in the future. Main description for
GimpExportOptions has also been updated to say this.
- Add a data_destroy callback for the user data passed in
gimp_export_procedure_set_capabilities().
- Fixing annotations of 'export_options' object from pdb/pdb.pl: input
args would actually be (nullable) and would not transfer ownership
(calling code must still free the object). Return value's ownership on
the other hand is fully transfered.
- Add C and Python unit testing for GimpExportOptions and
gimp_export_options_get_image() in particular.
- Fix or improve various details.
Note that I have also considered for a long time changing the signature
of gimp_export_options_get_image() to return a boolean indicating
whether `image` had been replaced (hence needed deletion) or not. This
also meant getting rid of the GimpExportReturn enum. Right now it would
work because there are no third case, but I was considering the future
possibility that for instance we got some impossible conversion for some
future capability. I'm not sure it would ever happen; and for sure, this
is not desirable because it implies an export failure a bit late in the
workflow. But just in case, let's keep the enum return value. It does
not even make the using code that much more complicated (well just a
value comparison instead of a simple boolean test).
2024-08-17 15:06:27 +02:00
|
|
|
&image) == GIMP_EXPORT_EXPORT))
|
2012-11-21 16:18:33 +01:00
|
|
|
{
|
2018-11-08 15:20:20 +01:00
|
|
|
/* gimp_drawable_histogram() only works within the bounds of
|
|
|
|
* the selection, which is a problem (see issue #2431).
|
|
|
|
* Instead of saving the selection, unselecting to later
|
|
|
|
* reselect, let's just always work on a duplicate of the
|
|
|
|
* image.
|
|
|
|
*/
|
2019-08-27 14:12:50 +02:00
|
|
|
image = gimp_image_duplicate (image);
|
2012-11-21 16:18:33 +01:00
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_selection_none (image);
|
|
|
|
|
|
|
|
gimp_image_get_resolution (image, &x_res, &y_res);
|
2010-08-27 23:22:10 +03:00
|
|
|
x_scale = 72.0 / x_res;
|
|
|
|
y_scale = 72.0 / y_res;
|
|
|
|
|
|
|
|
cairo_pdf_surface_set_size (pdf_file,
|
2021-04-06 00:47:07 +02:00
|
|
|
gimp_image_get_width (image) * x_scale,
|
|
|
|
gimp_image_get_height (image) * y_scale);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
/* This way we set how many pixels are there in every inch.
|
2012-11-21 15:04:15 +01:00
|
|
|
* It's very important for PangoCairo
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
cairo_surface_set_fallback_resolution (pdf_file, x_res, y_res);
|
|
|
|
|
2017-06-19 23:46:46 +02:00
|
|
|
/* Cairo has a concept of user-space vs device-space units.
|
|
|
|
* From what I understand, by default the user-space unit is the
|
|
|
|
* typographical "point". Since we work mostly with pixels, not
|
|
|
|
* points, the following call simply scales the transformation
|
|
|
|
* matrix from points to pixels, relatively to the image
|
|
|
|
* resolution, knowing that 1 typographical point == 1/72 inch.
|
2012-11-21 15:04:15 +01:00
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
cairo_scale (cr, x_scale, y_scale);
|
|
|
|
|
2024-10-22 01:47:45 +02:00
|
|
|
layers = gimp_image_get_layers (image);
|
|
|
|
n_layers = gimp_core_object_array_get_length ((GObject **) layers);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-11-13 07:38:06 -05:00
|
|
|
/* First fill image with opaque background color
|
|
|
|
* when last layer has transparency and user chose that option.
|
|
|
|
* Later we paint the same layer and others layers with transparency.
|
2015-07-25 03:19:47 -03:00
|
|
|
*/
|
2023-01-27 03:31:55 +00:00
|
|
|
if (gimp_drawable_has_alpha (GIMP_DRAWABLE (layers[n_layers - 1])) &&
|
|
|
|
fill_background_color)
|
2015-07-25 03:19:47 -03:00
|
|
|
{
|
2023-11-14 20:04:14 +01:00
|
|
|
GeglColor *color;
|
|
|
|
double rgb[3];
|
2017-06-19 22:30:08 +02:00
|
|
|
|
|
|
|
cairo_rectangle (cr, 0.0, 0.0,
|
2021-04-06 00:47:07 +02:00
|
|
|
gimp_image_get_width (image),
|
|
|
|
gimp_image_get_height (image));
|
2023-11-14 20:04:14 +01:00
|
|
|
color = gimp_context_get_background ();
|
2024-11-13 07:38:06 -05:00
|
|
|
gegl_color_get_pixel (color, babl_format_with_space ("R'G'B' double", NULL), rgb);
|
2023-11-14 20:04:14 +01:00
|
|
|
cairo_set_source_rgb (cr, rgb[0], rgb[1], rgb[2]);
|
2015-07-25 03:19:47 -03:00
|
|
|
cairo_fill (cr);
|
2023-11-14 20:04:14 +01:00
|
|
|
|
|
|
|
g_object_unref (color);
|
2015-07-25 03:19:47 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Now, we should loop over the layers of each image */
|
2012-11-21 16:18:33 +01:00
|
|
|
for (j = 0; j < n_layers; j++)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
if (! draw_layer (layers, n_layers, config,
|
2024-03-24 13:56:42 +00:00
|
|
|
single_image, j, cr, x_res, y_res,
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_procedure_get_name (procedure),
|
2021-02-15 22:21:30 +01:00
|
|
|
show_progress,
|
|
|
|
/* Progression is showed per image, and would restart at 0
|
|
|
|
* if you open several images.
|
|
|
|
*/
|
|
|
|
(gdouble) j / n_layers,
|
|
|
|
(gdouble) (j + 1) / n_layers,
|
2022-09-10 22:56:01 +02:00
|
|
|
0, error))
|
2019-07-11 23:55:51 +02:00
|
|
|
{
|
|
|
|
/* free the resources */
|
2019-07-12 13:11:47 +02:00
|
|
|
g_free (layers);
|
2019-07-11 23:55:51 +02:00
|
|
|
cairo_surface_destroy (pdf_file);
|
|
|
|
cairo_destroy (cr);
|
|
|
|
fclose (fp);
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
return GIMP_PDB_EXECUTION_ERROR;
|
2019-07-11 23:55:51 +02:00
|
|
|
}
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
2021-02-14 23:51:25 +01:00
|
|
|
if (show_progress)
|
|
|
|
gimp_progress_update (1.0);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2019-07-12 13:11:47 +02:00
|
|
|
g_free (layers);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2017-06-13 20:45:09 +02:00
|
|
|
/* We are done with this image - Show it!
|
|
|
|
* Unless that's a multi-page to avoid blank page at the end
|
|
|
|
*/
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
if (! layers_as_pages)
|
2017-06-13 20:45:09 +02:00
|
|
|
cairo_show_page (cr);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
cairo_restore (cr);
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_image_delete (image);
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* We are done with all the images - time to free the resources */
|
|
|
|
cairo_surface_destroy (pdf_file);
|
|
|
|
cairo_destroy (cr);
|
|
|
|
|
2012-06-09 15:36:35 +02:00
|
|
|
fclose (fp);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
return GIMP_PDB_SUCCESS;
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
2024-11-01 19:13:02 +01:00
|
|
|
static GimpExportCapabilities
|
2024-05-06 18:38:12 +00:00
|
|
|
export_edit_options (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GimpExportOptions *options,
|
|
|
|
gpointer create_data)
|
|
|
|
{
|
|
|
|
GimpExportCapabilities capabilities;
|
|
|
|
gboolean apply_masks;
|
2025-06-01 02:29:07 +00:00
|
|
|
gboolean layers_as_page;
|
2024-05-06 18:38:12 +00:00
|
|
|
|
|
|
|
g_object_get (G_OBJECT (config),
|
2025-06-01 02:29:07 +00:00
|
|
|
"apply-masks", &apply_masks,
|
|
|
|
"layers-as-pages", &layers_as_page,
|
2024-05-06 18:38:12 +00:00
|
|
|
NULL);
|
|
|
|
|
2025-06-01 02:29:07 +00:00
|
|
|
capabilities = (GIMP_EXPORT_CAN_HANDLE_RGB |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_GRAY |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_INDEXED |
|
|
|
|
GIMP_EXPORT_CAN_HANDLE_ALPHA);
|
|
|
|
|
|
|
|
if (layers_as_page)
|
|
|
|
capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;
|
2024-05-06 18:38:12 +00:00
|
|
|
|
|
|
|
if (! apply_masks)
|
|
|
|
capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS;
|
|
|
|
|
2024-11-01 19:13:02 +01:00
|
|
|
return capabilities;
|
2024-05-06 18:38:12 +00:00
|
|
|
}
|
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
/******************************************************/
|
2013-01-27 17:52:38 +02:00
|
|
|
/* Beginning of parameter handling functions */
|
2010-08-27 23:22:10 +03:00
|
|
|
/******************************************************/
|
|
|
|
|
|
|
|
/* A function that initializes the image list to default values */
|
|
|
|
static void
|
2019-08-27 14:12:50 +02:00
|
|
|
init_image_list_defaults (GimpImage *image)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
if (image)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2012-11-21 15:04:15 +01:00
|
|
|
multi_page.images[0] = image;
|
2010-08-27 23:22:10 +03:00
|
|
|
multi_page.image_count = 1;
|
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
multi_page.image_count = 0;
|
|
|
|
}
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* A function that removes images that are no longer valid from the
|
|
|
|
* image list
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
static void
|
|
|
|
validate_image_list (void)
|
|
|
|
{
|
|
|
|
gint32 valid = 0;
|
2012-11-21 15:04:15 +01:00
|
|
|
guint32 i = 0;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
for (i = 0 ; i < MAX_PAGE_COUNT && i < multi_page.image_count ; i++)
|
|
|
|
{
|
|
|
|
if (gimp_image_is_valid (multi_page.images[i]))
|
|
|
|
{
|
|
|
|
multi_page.images[valid] = multi_page.images[i];
|
|
|
|
valid++;
|
|
|
|
}
|
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
multi_page.image_count = valid;
|
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
/******************************************************/
|
2013-01-27 17:52:38 +02:00
|
|
|
/* Beginning of GUI functions */
|
2010-08-27 23:22:10 +03:00
|
|
|
/******************************************************/
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
/* The main GUI function for saving single-paged PDFs */
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
static gboolean
|
2022-09-10 22:16:11 +02:00
|
|
|
gui_single (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GimpImage *image)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2023-11-06 20:56:33 +00:00
|
|
|
GtkWidget *window;
|
|
|
|
GtkWidget *widget;
|
|
|
|
GimpLayer **layers;
|
|
|
|
GList *missing_fonts;
|
|
|
|
GList *dialog_props = NULL;
|
|
|
|
gboolean run;
|
|
|
|
gint32 n_layers;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-04-20 03:08:57 +00:00
|
|
|
window = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
|
|
|
|
GIMP_PROCEDURE_CONFIG (config),
|
|
|
|
image);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2022-09-10 22:56:01 +02:00
|
|
|
gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (window),
|
|
|
|
"pages-box",
|
|
|
|
"reverse-order", "root-layers-only", NULL);
|
2022-09-10 22:16:11 +02:00
|
|
|
/* XXX the "layers-as-pages" checkbox label used to be changing,
|
|
|
|
* showing "top layers first" or "bottom layers first" depending on
|
|
|
|
* the value of "reverse-order". Should we want this? Or do it
|
|
|
|
* differently, i.e. maybe with an additional label showing the order?
|
|
|
|
*/
|
|
|
|
widget = gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (window),
|
|
|
|
"pages-frame", "layers-as-pages", FALSE,
|
2022-09-10 22:56:01 +02:00
|
|
|
"pages-box");
|
2023-11-06 20:56:33 +00:00
|
|
|
/* Enable "layers-as-pages" if more than one layer, or there's a single
|
|
|
|
* layer group has more than one layer */
|
2024-10-22 01:47:45 +02:00
|
|
|
layers = gimp_image_get_layers (multi_page.images[0]);
|
|
|
|
n_layers = gimp_core_object_array_get_length ((GObject **) layers);
|
2024-10-21 23:57:10 +02:00
|
|
|
|
2023-11-06 20:56:33 +00:00
|
|
|
if (n_layers == 1 && gimp_item_is_group (GIMP_ITEM (layers[0])))
|
2024-10-21 23:57:10 +02:00
|
|
|
{
|
|
|
|
GimpItem **children;
|
|
|
|
|
|
|
|
children = gimp_item_get_children (GIMP_ITEM (layers[0]));
|
|
|
|
n_layers = gimp_core_object_array_get_length ((GObject **) children);
|
|
|
|
g_free (children);
|
|
|
|
}
|
2023-11-06 20:56:33 +00:00
|
|
|
g_free (layers);
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
gtk_widget_set_sensitive (widget, n_layers > 1);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
/* Warning for missing fonts (non-embeddable with rasterization
|
|
|
|
* possible).
|
|
|
|
*/
|
2020-01-01 17:53:02 +01:00
|
|
|
missing_fonts = get_missing_fonts (gimp_image_list_layers (multi_page.images[0]));
|
|
|
|
if (missing_fonts != NULL)
|
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
GList *iter;
|
|
|
|
gchar *font_list = NULL;
|
|
|
|
gchar *text;
|
2020-01-01 17:53:02 +01:00
|
|
|
|
|
|
|
for (iter = missing_fonts; iter; iter = iter->next)
|
|
|
|
{
|
|
|
|
gchar *fontname = iter->data;
|
|
|
|
|
|
|
|
if (font_list == NULL)
|
|
|
|
{
|
|
|
|
font_list = g_strdup (fontname);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gchar *tmp = font_list;
|
|
|
|
|
|
|
|
font_list = g_strjoin (", ", tmp, fontname, NULL);
|
|
|
|
g_free (tmp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
text = g_strdup_printf (_("The following fonts cannot be found: %s.\n"
|
|
|
|
"It is recommended to convert your text layers to image "
|
|
|
|
"or to install the missing fonts before exporting, "
|
|
|
|
"otherwise your design may not look right."),
|
|
|
|
font_list);
|
2022-09-10 22:16:11 +02:00
|
|
|
/* TODO: we used to have a GtkImage showing a GIMP_ICON_WILBER_EEK
|
|
|
|
* icon in GTK_ICON_SIZE_BUTTON size, next to the label, to make
|
|
|
|
* the warning more obvious.
|
|
|
|
*/
|
|
|
|
widget = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (window),
|
2023-07-18 00:06:26 +02:00
|
|
|
"missing-fonts-label",
|
|
|
|
text, FALSE, FALSE);
|
2022-09-10 22:16:11 +02:00
|
|
|
gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
|
|
|
|
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (window),
|
|
|
|
"convert-text-layers-frame",
|
|
|
|
"convert-text-layers", TRUE,
|
|
|
|
"missing-fonts-label");
|
|
|
|
|
|
|
|
dialog_props = g_list_prepend (dialog_props, "convert-text-layers-frame");
|
2020-01-01 17:53:02 +01:00
|
|
|
|
|
|
|
g_list_free_full (missing_fonts, g_free);
|
2022-09-10 22:16:11 +02:00
|
|
|
g_free (text);
|
|
|
|
g_free (font_list);
|
2020-01-01 17:53:02 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
dialog_props = g_list_prepend (dialog_props, "convert-text-layers");
|
2020-01-01 17:53:02 +01:00
|
|
|
}
|
|
|
|
|
2023-01-27 03:31:55 +00:00
|
|
|
dialog_props = g_list_prepend (dialog_props, "fill-background-color");
|
2022-09-10 22:16:11 +02:00
|
|
|
dialog_props = g_list_prepend (dialog_props, "ignore-hidden");
|
|
|
|
dialog_props = g_list_prepend (dialog_props, "vectorize");
|
|
|
|
dialog_props = g_list_prepend (dialog_props, "apply-masks");
|
|
|
|
dialog_props = g_list_prepend (dialog_props, "pages-frame");
|
|
|
|
gimp_procedure_dialog_fill_list (GIMP_PROCEDURE_DIALOG (window),
|
|
|
|
dialog_props);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (window));
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
gtk_widget_destroy (window);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
return run;
|
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
/* The main GUI function for saving multi-paged PDFs */
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
static gboolean
|
2022-09-10 22:16:11 +02:00
|
|
|
gui_multi (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
|
|
|
GtkWidget *window;
|
|
|
|
GtkWidget *vbox;
|
|
|
|
GtkWidget *file_label;
|
|
|
|
GtkWidget *file_entry;
|
|
|
|
GtkWidget *file_browse;
|
|
|
|
GtkWidget *file_hbox;
|
|
|
|
GtkWidget *scroll;
|
|
|
|
GtkWidget *page_view;
|
|
|
|
GtkWidget *h_but_box;
|
|
|
|
GtkWidget *del;
|
|
|
|
GtkWidget *h_box;
|
|
|
|
GtkWidget *img_combo;
|
|
|
|
GtkWidget *add_image;
|
|
|
|
gboolean run;
|
|
|
|
const gchar *temp;
|
|
|
|
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
window = gimp_procedure_dialog_new (procedure,
|
|
|
|
GIMP_PROCEDURE_CONFIG (config),
|
|
|
|
_("Export Image as Multi-Page PDF"));
|
|
|
|
gimp_procedure_dialog_set_ok_label (GIMP_PROCEDURE_DIALOG (window), _("_Export"));
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (window),
|
|
|
|
"multi-pdf-options-vbox",
|
|
|
|
"apply-masks", "vectorize",
|
|
|
|
"fill-background-color",
|
|
|
|
"ignore-hidden", NULL);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2011-09-30 12:17:53 +02:00
|
|
|
file_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
|
2011-11-29 05:27:02 +04:00
|
|
|
file_label = gtk_label_new (_("Save to:"));
|
2010-08-27 23:22:10 +03:00
|
|
|
file_entry = gtk_entry_new ();
|
|
|
|
if (file_name != NULL)
|
|
|
|
gtk_entry_set_text (GTK_ENTRY (file_entry), file_name);
|
2011-11-29 05:33:51 +04:00
|
|
|
file_browse = gtk_button_new_with_label (_("Browse..."));
|
2011-11-29 05:27:02 +04:00
|
|
|
file_choose = gtk_file_chooser_dialog_new (_("Multipage PDF export"),
|
2017-03-05 16:01:59 +01:00
|
|
|
GTK_WINDOW (window),
|
|
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
|
|
_("_Save"), GTK_RESPONSE_OK,
|
|
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
2010-08-27 23:22:10 +03:00
|
|
|
NULL);
|
|
|
|
|
|
|
|
gtk_box_pack_start (GTK_BOX (file_hbox), file_label, FALSE, FALSE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (file_label, TRUE);
|
2010-08-28 00:08:35 +03:00
|
|
|
gtk_box_pack_start (GTK_BOX (file_hbox), file_entry, TRUE, TRUE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (file_entry, TRUE);
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_box_pack_start (GTK_BOX (file_hbox), file_browse, FALSE, FALSE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (file_browse, TRUE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2010-08-28 00:08:35 +03:00
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), file_hbox, TRUE, TRUE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (file_hbox, TRUE);
|
|
|
|
gtk_box_reorder_child (GTK_BOX (vbox), file_hbox, 0);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
page_view = gtk_icon_view_new ();
|
|
|
|
model = create_model ();
|
|
|
|
gtk_icon_view_set_model (GTK_ICON_VIEW (page_view), model);
|
|
|
|
gtk_icon_view_set_reorderable (GTK_ICON_VIEW (page_view), TRUE);
|
2012-11-21 15:04:15 +01:00
|
|
|
gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (page_view),
|
|
|
|
GTK_SELECTION_MULTIPLE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (page_view), THUMB);
|
|
|
|
gtk_icon_view_set_text_column (GTK_ICON_VIEW (page_view), PAGE_NUMBER);
|
|
|
|
gtk_icon_view_set_tooltip_column (GTK_ICON_VIEW (page_view), IMAGE_NAME);
|
|
|
|
|
|
|
|
scroll = gtk_scrolled_window_new (NULL, NULL);
|
|
|
|
gtk_widget_set_size_request (scroll, -1, 300);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (scroll, TRUE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
|
|
|
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_container_add (GTK_CONTAINER (scroll), page_view);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (page_view, TRUE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2010-08-28 00:08:35 +03:00
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), scroll, TRUE, TRUE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_box_reorder_child (GTK_BOX (vbox), scroll, 1);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2011-09-30 15:43:02 +02:00
|
|
|
h_but_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_button_box_set_layout (GTK_BUTTON_BOX (h_but_box), GTK_BUTTONBOX_START);
|
|
|
|
|
2011-11-29 05:27:02 +04:00
|
|
|
del = gtk_button_new_with_label (_("Remove the selected pages"));
|
2010-08-28 00:08:35 +03:00
|
|
|
gtk_box_pack_start (GTK_BOX (h_but_box), del, TRUE, TRUE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (del, TRUE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), h_but_box, FALSE, FALSE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (h_but_box, TRUE);
|
|
|
|
gtk_box_reorder_child (GTK_BOX (vbox), h_but_box, 2);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2011-09-30 12:17:53 +02:00
|
|
|
h_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2019-08-07 23:16:25 +02:00
|
|
|
img_combo = gimp_image_combo_box_new (NULL, NULL, NULL);
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_box_pack_start (GTK_BOX (h_box), img_combo, FALSE, FALSE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (img_combo, TRUE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2011-11-29 05:27:02 +04:00
|
|
|
add_image = gtk_button_new_with_label (_("Add this image"));
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_box_pack_start (GTK_BOX (h_box), add_image, FALSE, FALSE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (add_image, TRUE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), h_box, FALSE, FALSE, 0);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (h_box, TRUE);
|
|
|
|
gtk_box_reorder_child (GTK_BOX (vbox), h_box, 3);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
g_signal_connect (G_OBJECT (file_browse), "clicked",
|
2012-11-21 15:04:15 +01:00
|
|
|
G_CALLBACK (choose_file_call),
|
|
|
|
file_entry);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
g_signal_connect (G_OBJECT (add_image), "clicked",
|
2012-11-21 15:04:15 +01:00
|
|
|
G_CALLBACK (add_image_call),
|
|
|
|
img_combo);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
g_signal_connect (G_OBJECT (del), "clicked",
|
2012-11-21 15:04:15 +01:00
|
|
|
G_CALLBACK (del_image_call),
|
|
|
|
page_view);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
g_signal_connect (G_OBJECT (model), "row-deleted",
|
2012-11-21 15:04:15 +01:00
|
|
|
G_CALLBACK (remove_call),
|
|
|
|
NULL);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (window),
|
|
|
|
"multi-pdf-options-vbox",
|
|
|
|
NULL);
|
|
|
|
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (window));
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
if (run)
|
|
|
|
{
|
|
|
|
run &= get_image_list ();
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
temp = gtk_entry_get_text (GTK_ENTRY (file_entry));
|
|
|
|
if (temp != NULL)
|
|
|
|
{
|
|
|
|
g_stpcpy (file_name, temp);
|
|
|
|
g_object_set (config, "uri", temp, NULL);
|
|
|
|
}
|
|
|
|
}
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
gtk_widget_destroy (window);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
return run;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A function that is called when the button for browsing for file
|
2012-11-21 15:04:15 +01:00
|
|
|
* locations was clicked
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
static void
|
|
|
|
choose_file_call (GtkWidget *browse_button,
|
|
|
|
gpointer file_entry)
|
|
|
|
{
|
|
|
|
GFile *file = g_file_new_for_path (gtk_entry_get_text (GTK_ENTRY (file_entry)));
|
2012-11-21 15:04:15 +01:00
|
|
|
|
|
|
|
gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (file_choose),
|
|
|
|
g_file_get_uri (file));
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
if (gtk_dialog_run (GTK_DIALOG (file_choose)) == GTK_RESPONSE_OK)
|
|
|
|
{
|
|
|
|
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (file_choose));
|
2021-10-01 18:56:12 +02:00
|
|
|
gtk_entry_set_text (GTK_ENTRY (file_entry), g_file_peek_path (file));
|
2012-11-21 15:04:15 +01:00
|
|
|
}
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
file_name = g_file_get_path (file);
|
2024-03-24 02:20:05 +00:00
|
|
|
gtk_widget_set_visible (file_choose, FALSE);
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* A function to create the basic GtkTreeModel for the icon view */
|
|
|
|
static GtkTreeModel*
|
|
|
|
create_model (void)
|
|
|
|
{
|
|
|
|
GtkListStore *model;
|
|
|
|
guint32 i;
|
|
|
|
|
|
|
|
/* validate_image_list was called earlier, so all the images
|
2012-11-21 15:04:15 +01:00
|
|
|
* up to multi_page.image_count are valid
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
model = gtk_list_store_new (4,
|
2019-08-27 14:12:50 +02:00
|
|
|
GDK_TYPE_PIXBUF, /* THUMB */
|
|
|
|
G_TYPE_STRING, /* PAGE_NUMBER */
|
|
|
|
G_TYPE_STRING, /* IMAGE_NAME */
|
|
|
|
GIMP_TYPE_IMAGE); /* IMAGE */
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
for (i = 0 ; i < multi_page.image_count && i < MAX_PAGE_COUNT ; i++)
|
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
GtkTreeIter iter;
|
|
|
|
GimpImage *image = multi_page.images[i];
|
|
|
|
GdkPixbuf *pixbuf;
|
2012-11-21 15:04:15 +01:00
|
|
|
|
|
|
|
pixbuf = gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT,
|
|
|
|
GIMP_PIXBUF_SMALL_CHECKS);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
gtk_list_store_append (model, &iter);
|
|
|
|
gtk_list_store_set (model, &iter,
|
2012-11-21 15:04:15 +01:00
|
|
|
THUMB, pixbuf,
|
|
|
|
PAGE_NUMBER, g_strdup_printf (_("Page %d"), i + 1),
|
|
|
|
IMAGE_NAME, gimp_image_get_name (image),
|
2019-08-27 14:12:50 +02:00
|
|
|
IMAGE, image,
|
2010-08-27 23:22:10 +03:00
|
|
|
-1);
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
g_object_unref (pixbuf);
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return GTK_TREE_MODEL (model);
|
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* A function that puts the images from the model inside the images
|
|
|
|
* (pages) array
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
static gboolean
|
|
|
|
get_image_list (void)
|
|
|
|
{
|
|
|
|
GtkTreeIter iter;
|
2012-11-21 15:04:15 +01:00
|
|
|
gboolean valid;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
multi_page.image_count = 0;
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
for (valid = gtk_tree_model_get_iter_first (model, &iter);
|
|
|
|
valid;
|
|
|
|
valid = gtk_tree_model_iter_next (model, &iter))
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
GimpImage *image;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
gtk_tree_model_get (model, &iter,
|
2019-08-27 14:12:50 +02:00
|
|
|
IMAGE, &image,
|
2010-08-27 23:22:10 +03:00
|
|
|
-1);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
multi_page.images[multi_page.image_count] = image;
|
|
|
|
multi_page.image_count++;
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
g_object_unref (image);
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
if (multi_page.image_count == 0)
|
|
|
|
{
|
|
|
|
g_message (_("Error! In order to save the file, at least one image "
|
|
|
|
"should be added!"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* A function that is called when the button for adding an image was
|
|
|
|
* clicked
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
static void
|
|
|
|
add_image_call (GtkWidget *widget,
|
|
|
|
gpointer img_combo)
|
|
|
|
{
|
|
|
|
GtkListStore *store;
|
|
|
|
GtkTreeIter iter;
|
2019-08-27 14:12:50 +02:00
|
|
|
gint32 image_id;
|
|
|
|
GimpImage *image;
|
2012-11-21 15:04:15 +01:00
|
|
|
GdkPixbuf *pixbuf;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
dnd_remove = FALSE;
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_int_combo_box_get_active (img_combo, &image_id);
|
|
|
|
image = gimp_image_get_by_id (image_id);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
store = GTK_LIST_STORE (model);
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
pixbuf = gimp_image_get_thumbnail (image, THUMB_WIDTH, THUMB_HEIGHT,
|
|
|
|
GIMP_PIXBUF_SMALL_CHECKS);
|
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_list_store_append (store, &iter);
|
|
|
|
gtk_list_store_set (store, &iter,
|
2012-11-21 15:04:15 +01:00
|
|
|
PAGE_NUMBER, g_strdup_printf (_("Page %d"),
|
|
|
|
multi_page.image_count+1),
|
|
|
|
THUMB, pixbuf,
|
|
|
|
IMAGE_NAME, gimp_image_get_name (image),
|
2019-08-27 14:12:50 +02:00
|
|
|
IMAGE, image,
|
2012-11-21 15:04:15 +01:00
|
|
|
-1);
|
|
|
|
|
|
|
|
g_object_unref (pixbuf);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
multi_page.image_count++;
|
|
|
|
|
|
|
|
dnd_remove = TRUE;
|
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* A function that is called when the button for deleting the selected
|
|
|
|
* images was clicked
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
static void
|
|
|
|
del_image_call (GtkWidget *widget,
|
|
|
|
gpointer icon_view)
|
|
|
|
{
|
|
|
|
GList *list;
|
|
|
|
GtkTreeRowReference **items;
|
|
|
|
GtkTreePath *item_path;
|
|
|
|
GtkTreeIter item;
|
|
|
|
gpointer temp;
|
|
|
|
guint32 len;
|
|
|
|
|
|
|
|
dnd_remove = FALSE;
|
|
|
|
|
|
|
|
list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view));
|
|
|
|
|
|
|
|
len = g_list_length (list);
|
|
|
|
if (len > 0)
|
|
|
|
{
|
2012-11-21 15:04:15 +01:00
|
|
|
gint i;
|
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
items = g_newa (GtkTreeRowReference*, len);
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
{
|
|
|
|
temp = g_list_nth_data (list, i);
|
|
|
|
items[i] = gtk_tree_row_reference_new (model, temp);
|
|
|
|
gtk_tree_path_free (temp);
|
|
|
|
}
|
|
|
|
g_list_free (list);
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
{
|
|
|
|
item_path = gtk_tree_row_reference_get_path (items[i]);
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
gtk_tree_model_get_iter (model, &item, item_path);
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_list_store_remove (GTK_LIST_STORE (model), &item);
|
|
|
|
|
|
|
|
gtk_tree_path_free (item_path);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
gtk_tree_row_reference_free (items[i]);
|
|
|
|
multi_page.image_count--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dnd_remove = TRUE;
|
|
|
|
|
|
|
|
recount_pages ();
|
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* A function that is called on rows-deleted signal. It will call the
|
|
|
|
* function to relabel the pages
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
static void
|
|
|
|
remove_call (GtkTreeModel *tree_model,
|
|
|
|
GtkTreePath *path,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (dnd_remove)
|
|
|
|
/* The gtk documentation says that we should not free the indices array */
|
|
|
|
recount_pages ();
|
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* A function to relabel the pages in the icon view, when their order
|
|
|
|
* was changed
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
static void
|
|
|
|
recount_pages (void)
|
|
|
|
{
|
|
|
|
GtkListStore *store;
|
|
|
|
GtkTreeIter iter;
|
|
|
|
gboolean valid;
|
|
|
|
gint32 i = 0;
|
|
|
|
|
|
|
|
store = GTK_LIST_STORE (model);
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
for (valid = gtk_tree_model_get_iter_first (model, &iter);
|
|
|
|
valid;
|
|
|
|
valid = gtk_tree_model_iter_next (model, &iter))
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
|
|
|
gtk_list_store_set (store, &iter,
|
2012-11-21 15:04:15 +01:00
|
|
|
PAGE_NUMBER, g_strdup_printf (_("Page %d"), i + 1),
|
2010-08-27 23:22:10 +03:00
|
|
|
-1);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-21 18:51:41 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
/******************************************************/
|
2013-01-27 17:52:38 +02:00
|
|
|
/* Beginning of the actual PDF functions */
|
2010-08-27 23:22:10 +03:00
|
|
|
/******************************************************/
|
|
|
|
|
2011-12-31 03:27:47 +02:00
|
|
|
static cairo_surface_t *
|
2019-08-27 14:12:50 +02:00
|
|
|
get_cairo_surface (GimpDrawable *drawable,
|
|
|
|
gboolean as_mask,
|
|
|
|
GError **error)
|
2011-12-31 03:27:47 +02:00
|
|
|
{
|
2012-11-21 18:51:41 +01:00
|
|
|
GeglBuffer *src_buffer;
|
|
|
|
GeglBuffer *dest_buffer;
|
2011-12-31 03:27:47 +02:00
|
|
|
cairo_surface_t *surface;
|
2014-04-29 17:37:25 +02:00
|
|
|
cairo_status_t status;
|
2011-12-31 03:27:47 +02:00
|
|
|
cairo_format_t format;
|
2012-11-21 15:04:15 +01:00
|
|
|
gint width;
|
|
|
|
gint height;
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
src_buffer = gimp_drawable_get_buffer (drawable);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2012-11-21 18:51:41 +01:00
|
|
|
width = gegl_buffer_get_width (src_buffer);
|
|
|
|
height = gegl_buffer_get_height (src_buffer);
|
2011-12-31 03:27:47 +02:00
|
|
|
|
2017-06-16 16:23:38 +02:00
|
|
|
if (as_mask)
|
|
|
|
format = CAIRO_FORMAT_A8;
|
2019-08-27 14:12:50 +02:00
|
|
|
else if (gimp_drawable_has_alpha (drawable))
|
2012-11-21 18:51:41 +01:00
|
|
|
format = CAIRO_FORMAT_ARGB32;
|
|
|
|
else
|
|
|
|
format = CAIRO_FORMAT_RGB24;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
surface = cairo_image_surface_create (format, width, height);
|
|
|
|
|
2014-04-29 17:37:25 +02:00
|
|
|
status = cairo_surface_status (surface);
|
|
|
|
if (status != CAIRO_STATUS_SUCCESS)
|
|
|
|
{
|
|
|
|
switch (status)
|
|
|
|
{
|
|
|
|
case CAIRO_STATUS_INVALID_SIZE:
|
|
|
|
g_set_error_literal (error,
|
2024-04-13 15:10:25 +00:00
|
|
|
GIMP_PLUGIN_PDF_EXPORT_ERROR,
|
|
|
|
GIMP_PLUGIN_PDF_EXPORT_ERROR_FAILED,
|
2014-05-01 14:59:28 +02:00
|
|
|
_("Cannot handle the size (either width or height) of the image."));
|
2014-04-29 17:37:25 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
g_set_error (error,
|
2024-04-13 15:10:25 +00:00
|
|
|
GIMP_PLUGIN_PDF_EXPORT_ERROR,
|
|
|
|
GIMP_PLUGIN_PDF_EXPORT_ERROR_FAILED,
|
2014-04-29 17:37:25 +02:00
|
|
|
"Cairo error: %s",
|
|
|
|
cairo_status_to_string (status));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
app, libgimp, pdb, plug-ins: GimpText* using GeglColor.
One of the big improvement in this commit is that text layers are now much
better at space accuracy. They were already space-aware, yet rendered as sRGB u8
only before being converted to the image's space. It means that text layers had
the following limitations:
* Any color out of sRGB gamut were trimmed.
* Precision was always 8-bit (even if the image was high-bit depth).
Now GimpTextLayout keeps track of its source space (for RGB and CMYK only, this
won't be as easy when we will support more backend, since Cairo has only RGB
support for image data) and the image TRC (in case it bypasses the color space's
TRB) and it draws within this gamut and space.
It means first that we are not limited to sRGB colors; we will draw text main
color in the full image gamut, with still 2 remaining limitations:
* Unbounded colors are impossible because Pango format (to color text) uses
hexadecimal (so even with half/float images, you can't draw out-of-gamut text
unfortunately).
* Main color precision is still 8-bit, yet a tiny bit better than before as we
at least follow TRC (so we avoid some of the precision loss when converting,
even though the bit-depth is still the biggest loss).
The outline color on the other hand is drawn through Cairo API entirely, in
float. This means that the outline color will now be without any precision loss.
Note that this depends on CAIRO_FORMAT_RGBA128F which is only available since
Cairo 1.17.2 which is not in Debian bookworm (our current baseline for GIMP
3.0). It means that the old precision will still happen with older Cairo
version, as determined by #if code at compilation.
2023-11-17 22:36:31 +01:00
|
|
|
dest_buffer = gimp_cairo_surface_create_buffer (surface, NULL);
|
2017-06-16 16:23:38 +02:00
|
|
|
if (as_mask)
|
|
|
|
{
|
|
|
|
/* src_buffer represents a mask in "Y u8", "Y u16", etc. formats.
|
|
|
|
* Yet cairo_mask*() functions only care about the alpha channel of
|
|
|
|
* the surface. Hence I change the format of dest_buffer so that the
|
|
|
|
* Y channel of src_buffer actually refers to the A channel of
|
|
|
|
* dest_buffer/surface in Cairo.
|
|
|
|
*/
|
|
|
|
gegl_buffer_set_format (dest_buffer, babl_format ("Y u8"));
|
|
|
|
}
|
2011-12-31 03:27:47 +02:00
|
|
|
|
2015-05-24 22:32:55 +02:00
|
|
|
gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dest_buffer, NULL);
|
2011-12-31 03:27:47 +02:00
|
|
|
|
|
|
|
cairo_surface_mark_dirty (surface);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2012-11-21 18:51:41 +01:00
|
|
|
g_object_unref (src_buffer);
|
|
|
|
g_object_unref (dest_buffer);
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
return surface;
|
|
|
|
}
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* A function to check if a drawable is single colored This allows to
|
|
|
|
* convert bitmaps to vector where possible
|
|
|
|
*/
|
2024-03-20 02:53:41 +00:00
|
|
|
static GeglColor *
|
2019-08-27 14:12:50 +02:00
|
|
|
get_layer_color (GimpLayer *layer,
|
|
|
|
gboolean *single)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2024-03-20 02:53:41 +00:00
|
|
|
GeglColor *col = gegl_color_new (NULL);
|
2010-08-27 23:22:10 +03:00
|
|
|
gdouble red, green, blue, alpha;
|
|
|
|
gdouble dev, devSum;
|
Fix typos
Found via:
```
codespell -q 3 -S ./ChangeLog*,*.po,./.git,./NEWS* -L als,ang,ba,chello,daa,doubleclick,foto,hist,iff,inport,klass,mut,nd,ower,paeth,params,pard,pevent,sinc,thru,tim,uint
```
2020-11-05 11:43:53 -05:00
|
|
|
gdouble median, pixels, count, percentile;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
devSum = 0;
|
|
|
|
red = 0;
|
|
|
|
green = 0;
|
|
|
|
blue = 0;
|
|
|
|
alpha = 0;
|
|
|
|
dev = 0;
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
if (gimp_drawable_is_indexed (GIMP_DRAWABLE (layer)))
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2018-04-18 20:57:03 +02:00
|
|
|
/* FIXME: We can't do a proper histogram on indexed layers! */
|
2010-08-27 23:22:10 +03:00
|
|
|
*single = FALSE;
|
2024-03-20 02:53:41 +00:00
|
|
|
gegl_color_set_rgba (col, 0.0, 0.0, 0.0, 0.0);
|
2010-08-27 23:22:10 +03:00
|
|
|
return col;
|
|
|
|
}
|
|
|
|
|
2021-04-06 14:28:40 +02:00
|
|
|
if (gimp_drawable_get_bpp (GIMP_DRAWABLE (layer)) >= 3)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2012-11-21 15:04:15 +01:00
|
|
|
/* Are we in RGB mode? */
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_drawable_histogram (GIMP_DRAWABLE (layer),
|
|
|
|
GIMP_HISTOGRAM_RED, 0.0, 1.0,
|
Fix typos
Found via:
```
codespell -q 3 -S ./ChangeLog*,*.po,./.git,./NEWS* -L als,ang,ba,chello,daa,doubleclick,foto,hist,iff,inport,klass,mut,nd,ower,paeth,params,pard,pevent,sinc,thru,tim,uint
```
2020-11-05 11:43:53 -05:00
|
|
|
&red, &dev, &median, &pixels, &count, &percentile);
|
2010-08-27 23:22:10 +03:00
|
|
|
devSum += dev;
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
gimp_drawable_histogram (GIMP_DRAWABLE (layer),
|
|
|
|
GIMP_HISTOGRAM_GREEN, 0.0, 1.0,
|
Fix typos
Found via:
```
codespell -q 3 -S ./ChangeLog*,*.po,./.git,./NEWS* -L als,ang,ba,chello,daa,doubleclick,foto,hist,iff,inport,klass,mut,nd,ower,paeth,params,pard,pevent,sinc,thru,tim,uint
```
2020-11-05 11:43:53 -05:00
|
|
|
&green, &dev, &median, &pixels, &count, &percentile);
|
2010-08-27 23:22:10 +03:00
|
|
|
devSum += dev;
|
2019-08-27 14:12:50 +02:00
|
|
|
|
|
|
|
gimp_drawable_histogram (GIMP_DRAWABLE (layer),
|
|
|
|
GIMP_HISTOGRAM_BLUE, 0.0, 1.0,
|
Fix typos
Found via:
```
codespell -q 3 -S ./ChangeLog*,*.po,./.git,./NEWS* -L als,ang,ba,chello,daa,doubleclick,foto,hist,iff,inport,klass,mut,nd,ower,paeth,params,pard,pevent,sinc,thru,tim,uint
```
2020-11-05 11:43:53 -05:00
|
|
|
&blue, &dev, &median, &pixels, &count, &percentile);
|
2010-08-27 23:22:10 +03:00
|
|
|
devSum += dev;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-11-21 15:04:15 +01:00
|
|
|
/* We are in Grayscale mode (or Indexed) */
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
gimp_drawable_histogram (GIMP_DRAWABLE (layer),
|
|
|
|
GIMP_HISTOGRAM_VALUE, 0.0, 1.0,
|
Fix typos
Found via:
```
codespell -q 3 -S ./ChangeLog*,*.po,./.git,./NEWS* -L als,ang,ba,chello,daa,doubleclick,foto,hist,iff,inport,klass,mut,nd,ower,paeth,params,pard,pevent,sinc,thru,tim,uint
```
2020-11-05 11:43:53 -05:00
|
|
|
&red, &dev, &median, &pixels, &count, &percentile);
|
2010-08-27 23:22:10 +03:00
|
|
|
devSum += dev;
|
|
|
|
green = red;
|
|
|
|
blue = red;
|
|
|
|
}
|
2012-11-21 15:04:15 +01:00
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
if (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
|
|
|
|
gimp_drawable_histogram (GIMP_DRAWABLE (layer),
|
|
|
|
GIMP_HISTOGRAM_ALPHA, 0.0, 1.0,
|
Fix typos
Found via:
```
codespell -q 3 -S ./ChangeLog*,*.po,./.git,./NEWS* -L als,ang,ba,chello,daa,doubleclick,foto,hist,iff,inport,klass,mut,nd,ower,paeth,params,pard,pevent,sinc,thru,tim,uint
```
2020-11-05 11:43:53 -05:00
|
|
|
&alpha, &dev, &median, &pixels, &count, &percentile);
|
2010-08-27 23:22:10 +03:00
|
|
|
else
|
|
|
|
alpha = 255;
|
|
|
|
|
|
|
|
devSum += dev;
|
|
|
|
*single = devSum == 0;
|
|
|
|
|
2024-03-20 02:53:41 +00:00
|
|
|
gegl_color_set_rgba (col, red, green, blue, alpha);
|
2010-08-27 23:22:10 +03:00
|
|
|
return col;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A function that uses Pango to render the text to our cairo surface,
|
|
|
|
* in the same way it was the user saw it inside gimp.
|
|
|
|
* Needs some work on choosing the font name better, and on hinting
|
|
|
|
* (freetype and pango differences)
|
|
|
|
*/
|
|
|
|
static void
|
2019-08-27 14:12:50 +02:00
|
|
|
drawText (GimpLayer *layer,
|
|
|
|
gdouble opacity,
|
|
|
|
cairo_t *cr,
|
|
|
|
gdouble x_res,
|
|
|
|
gdouble y_res)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
GimpImageType type = gimp_drawable_type (GIMP_DRAWABLE (layer));
|
2022-09-30 16:21:47 +02:00
|
|
|
gchar *text = gimp_text_layer_get_text (GIMP_TEXT_LAYER (layer));
|
|
|
|
gchar *markup = gimp_text_layer_get_markup (GIMP_TEXT_LAYER (layer));
|
2018-08-03 12:32:15 +09:00
|
|
|
gchar *language;
|
2010-08-27 23:22:10 +03:00
|
|
|
cairo_font_options_t *options;
|
|
|
|
gint x;
|
|
|
|
gint y;
|
app, libgimp, pdb, plug-ins: GimpText* using GeglColor.
One of the big improvement in this commit is that text layers are now much
better at space accuracy. They were already space-aware, yet rendered as sRGB u8
only before being converted to the image's space. It means that text layers had
the following limitations:
* Any color out of sRGB gamut were trimmed.
* Precision was always 8-bit (even if the image was high-bit depth).
Now GimpTextLayout keeps track of its source space (for RGB and CMYK only, this
won't be as easy when we will support more backend, since Cairo has only RGB
support for image data) and the image TRC (in case it bypasses the color space's
TRB) and it draws within this gamut and space.
It means first that we are not limited to sRGB colors; we will draw text main
color in the full image gamut, with still 2 remaining limitations:
* Unbounded colors are impossible because Pango format (to color text) uses
hexadecimal (so even with half/float images, you can't draw out-of-gamut text
unfortunately).
* Main color precision is still 8-bit, yet a tiny bit better than before as we
at least follow TRC (so we avoid some of the precision loss when converting,
even though the bit-depth is still the biggest loss).
The outline color on the other hand is drawn through Cairo API entirely, in
float. This means that the outline color will now be without any precision loss.
Note that this depends on CAIRO_FORMAT_RGBA128F which is only available since
Cairo 1.17.2 which is not in Debian bookworm (our current baseline for GIMP
3.0). It means that the old precision will still happen with older Cairo
version, as determined by #if code at compilation.
2023-11-17 22:36:31 +01:00
|
|
|
GeglColor *color;
|
|
|
|
gdouble rgb[3];
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-25 20:55:21 +02:00
|
|
|
GimpUnit *unit;
|
2010-08-27 23:22:10 +03:00
|
|
|
gdouble size;
|
|
|
|
GimpTextHintStyle hinting;
|
|
|
|
GimpTextJustification j;
|
|
|
|
gboolean justify;
|
|
|
|
PangoAlignment align;
|
|
|
|
GimpTextDirection dir;
|
|
|
|
PangoLayout *layout;
|
|
|
|
PangoContext *context;
|
2023-09-13 19:13:51 +02:00
|
|
|
GimpFont *font;
|
2010-08-27 23:22:10 +03:00
|
|
|
PangoFontDescription *font_description;
|
|
|
|
gdouble indent;
|
|
|
|
gdouble line_spacing;
|
|
|
|
gdouble letter_spacing;
|
|
|
|
PangoAttribute *letter_spacing_at;
|
|
|
|
PangoAttrList *attr_list = pango_attr_list_new ();
|
2015-01-28 19:31:32 +01:00
|
|
|
PangoFontMap *fontmap;
|
2011-12-01 01:02:13 +05:30
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
cairo_save (cr);
|
|
|
|
|
|
|
|
options = cairo_font_options_create ();
|
|
|
|
attr_list = pango_attr_list_new ();
|
|
|
|
cairo_get_font_options (cr, options);
|
|
|
|
|
|
|
|
/* Position */
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);
|
2018-08-03 12:32:15 +09:00
|
|
|
cairo_translate (cr, x, y);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
/* Color */
|
|
|
|
/* When dealing with a gray/indexed image, the viewed color of the text layer
|
|
|
|
* can be different than the one kept in the memory */
|
2012-06-21 19:04:32 +02:00
|
|
|
if (type == GIMP_RGBA_IMAGE)
|
app, libgimp, pdb, plug-ins: GimpText* using GeglColor.
One of the big improvement in this commit is that text layers are now much
better at space accuracy. They were already space-aware, yet rendered as sRGB u8
only before being converted to the image's space. It means that text layers had
the following limitations:
* Any color out of sRGB gamut were trimmed.
* Precision was always 8-bit (even if the image was high-bit depth).
Now GimpTextLayout keeps track of its source space (for RGB and CMYK only, this
won't be as easy when we will support more backend, since Cairo has only RGB
support for image data) and the image TRC (in case it bypasses the color space's
TRB) and it draws within this gamut and space.
It means first that we are not limited to sRGB colors; we will draw text main
color in the full image gamut, with still 2 remaining limitations:
* Unbounded colors are impossible because Pango format (to color text) uses
hexadecimal (so even with half/float images, you can't draw out-of-gamut text
unfortunately).
* Main color precision is still 8-bit, yet a tiny bit better than before as we
at least follow TRC (so we avoid some of the precision loss when converting,
even though the bit-depth is still the biggest loss).
The outline color on the other hand is drawn through Cairo API entirely, in
float. This means that the outline color will now be without any precision loss.
Note that this depends on CAIRO_FORMAT_RGBA128F which is only available since
Cairo 1.17.2 which is not in Debian bookworm (our current baseline for GIMP
3.0). It means that the old precision will still happen with older Cairo
version, as determined by #if code at compilation.
2023-11-17 22:36:31 +01:00
|
|
|
color = gimp_text_layer_get_color (GIMP_TEXT_LAYER (layer));
|
2010-08-27 23:22:10 +03:00
|
|
|
else
|
2024-10-21 21:59:26 +02:00
|
|
|
gimp_image_pick_color (gimp_item_get_image (GIMP_ITEM (layer)),
|
|
|
|
(const GimpDrawable*[2]) { GIMP_DRAWABLE (layer), NULL },
|
|
|
|
x, y, FALSE, FALSE, 0, &color);
|
2023-11-12 17:56:32 +01:00
|
|
|
|
app, libgimp, pdb, plug-ins: GimpText* using GeglColor.
One of the big improvement in this commit is that text layers are now much
better at space accuracy. They were already space-aware, yet rendered as sRGB u8
only before being converted to the image's space. It means that text layers had
the following limitations:
* Any color out of sRGB gamut were trimmed.
* Precision was always 8-bit (even if the image was high-bit depth).
Now GimpTextLayout keeps track of its source space (for RGB and CMYK only, this
won't be as easy when we will support more backend, since Cairo has only RGB
support for image data) and the image TRC (in case it bypasses the color space's
TRB) and it draws within this gamut and space.
It means first that we are not limited to sRGB colors; we will draw text main
color in the full image gamut, with still 2 remaining limitations:
* Unbounded colors are impossible because Pango format (to color text) uses
hexadecimal (so even with half/float images, you can't draw out-of-gamut text
unfortunately).
* Main color precision is still 8-bit, yet a tiny bit better than before as we
at least follow TRC (so we avoid some of the precision loss when converting,
even though the bit-depth is still the biggest loss).
The outline color on the other hand is drawn through Cairo API entirely, in
float. This means that the outline color will now be without any precision loss.
Note that this depends on CAIRO_FORMAT_RGBA128F which is only available since
Cairo 1.17.2 which is not in Debian bookworm (our current baseline for GIMP
3.0). It means that the old precision will still happen with older Cairo
version, as determined by #if code at compilation.
2023-11-17 22:36:31 +01:00
|
|
|
/* TODO: this export plug-in is not space-aware yet, so we draw everything as
|
|
|
|
* sRGB for the time being.
|
|
|
|
*/
|
|
|
|
gegl_color_get_pixel (color, babl_format_with_space ("R'G'B' double", NULL), rgb);
|
|
|
|
cairo_set_source_rgba (cr, rgb[0], rgb[1], rgb[2], opacity);
|
|
|
|
g_object_unref (color);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
/* Hinting */
|
2022-09-30 16:21:47 +02:00
|
|
|
hinting = gimp_text_layer_get_hint_style (GIMP_TEXT_LAYER (layer));
|
2010-08-27 23:22:10 +03:00
|
|
|
switch (hinting)
|
|
|
|
{
|
|
|
|
case GIMP_TEXT_HINT_STYLE_NONE:
|
|
|
|
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_TEXT_HINT_STYLE_SLIGHT:
|
|
|
|
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_SLIGHT);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_TEXT_HINT_STYLE_MEDIUM:
|
|
|
|
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_MEDIUM);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_TEXT_HINT_STYLE_FULL:
|
|
|
|
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_FULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Antialiasing */
|
2022-09-30 16:21:47 +02:00
|
|
|
if (gimp_text_layer_get_antialias (GIMP_TEXT_LAYER (layer)))
|
2010-08-27 23:22:10 +03:00
|
|
|
cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_DEFAULT);
|
|
|
|
else
|
|
|
|
cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_NONE);
|
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* We are done with cairo's settings. It's time to create the
|
|
|
|
* context
|
|
|
|
*/
|
2015-01-28 19:31:32 +01:00
|
|
|
fontmap = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
|
|
|
|
|
2023-09-13 19:13:51 +02:00
|
|
|
/* Font */
|
|
|
|
font = gimp_text_layer_get_font (GIMP_TEXT_LAYER (layer));
|
|
|
|
font_description = gimp_font_get_pango_font_description (font);
|
|
|
|
|
|
|
|
/* This function breaks rendering with some fonts if it's called before
|
|
|
|
* gimp_font_get_pango_font_description(). I'm still unsure why yet it
|
|
|
|
* probably means there is a bug somewhere we must fix. Until then, let's make
|
|
|
|
* sure we keep this order. XXX
|
|
|
|
*/
|
2015-01-28 19:31:32 +01:00
|
|
|
pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (fontmap), y_res);
|
|
|
|
|
|
|
|
context = pango_font_map_create_context (fontmap);
|
|
|
|
g_object_unref (fontmap);
|
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
pango_cairo_context_set_font_options (context, options);
|
|
|
|
|
2018-08-03 12:32:15 +09:00
|
|
|
/* Language */
|
2022-09-30 16:21:47 +02:00
|
|
|
language = gimp_text_layer_get_language (GIMP_TEXT_LAYER (layer));
|
2018-08-03 12:32:15 +09:00
|
|
|
if (language)
|
|
|
|
pango_context_set_language (context,
|
2023-09-13 19:13:51 +02:00
|
|
|
pango_language_from_string (language));
|
2018-08-03 12:32:15 +09:00
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
/* Text Direction */
|
2022-09-30 16:21:47 +02:00
|
|
|
dir = gimp_text_layer_get_base_direction (GIMP_TEXT_LAYER (layer));
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2018-08-03 12:32:15 +09:00
|
|
|
switch (dir)
|
|
|
|
{
|
|
|
|
case GIMP_TEXT_DIRECTION_LTR:
|
|
|
|
pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
|
|
|
|
pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_NATURAL);
|
|
|
|
pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_TEXT_DIRECTION_RTL:
|
|
|
|
pango_context_set_base_dir (context, PANGO_DIRECTION_RTL);
|
|
|
|
pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_NATURAL);
|
|
|
|
pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_TEXT_DIRECTION_TTB_RTL:
|
|
|
|
pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
|
|
|
|
pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_LINE);
|
|
|
|
pango_context_set_base_gravity (context, PANGO_GRAVITY_EAST);
|
|
|
|
break;
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2018-08-03 12:32:15 +09:00
|
|
|
case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
|
|
|
|
pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
|
|
|
|
pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_STRONG);
|
|
|
|
pango_context_set_base_gravity (context, PANGO_GRAVITY_EAST);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_TEXT_DIRECTION_TTB_LTR:
|
|
|
|
pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
|
|
|
|
pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_LINE);
|
|
|
|
pango_context_set_base_gravity (context, PANGO_GRAVITY_WEST);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
|
|
|
|
pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
|
|
|
|
pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_STRONG);
|
|
|
|
pango_context_set_base_gravity (context, PANGO_GRAVITY_WEST);
|
|
|
|
break;
|
|
|
|
}
|
2010-08-27 23:22:10 +03:00
|
|
|
|
2012-11-21 15:04:15 +01:00
|
|
|
/* We are done with the context's settings. It's time to create the
|
|
|
|
* layout
|
|
|
|
*/
|
2010-08-27 23:22:10 +03:00
|
|
|
layout = pango_layout_new (context);
|
2015-01-28 19:31:32 +01:00
|
|
|
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
/* Font Size */
|
2022-09-30 16:21:47 +02:00
|
|
|
size = gimp_text_layer_get_font_size (GIMP_TEXT_LAYER (layer), &unit);
|
2012-06-21 19:04:32 +02:00
|
|
|
size = gimp_units_to_pixels (size, unit, y_res);
|
2010-08-27 23:22:10 +03:00
|
|
|
pango_font_description_set_absolute_size (font_description, size * PANGO_SCALE);
|
|
|
|
|
|
|
|
pango_layout_set_font_description (layout, font_description);
|
|
|
|
|
2015-01-28 19:31:32 +01:00
|
|
|
/* Width */
|
2018-08-03 12:32:15 +09:00
|
|
|
if (! PANGO_GRAVITY_IS_VERTICAL (pango_context_get_base_gravity (context)))
|
2019-08-27 14:12:50 +02:00
|
|
|
pango_layout_set_width (layout,
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_width (GIMP_DRAWABLE (layer)) *
|
2019-08-27 14:12:50 +02:00
|
|
|
PANGO_SCALE);
|
2018-08-03 12:32:15 +09:00
|
|
|
else
|
2019-08-27 14:12:50 +02:00
|
|
|
pango_layout_set_width (layout,
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_height (GIMP_DRAWABLE (layer)) *
|
2019-08-27 14:12:50 +02:00
|
|
|
PANGO_SCALE);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
/* Justification, and Alignment */
|
|
|
|
justify = FALSE;
|
2022-09-30 16:21:47 +02:00
|
|
|
j = gimp_text_layer_get_justification (GIMP_TEXT_LAYER (layer));
|
2018-08-03 12:32:15 +09:00
|
|
|
align = PANGO_ALIGN_LEFT;
|
|
|
|
switch (j)
|
2010-08-27 23:22:10 +03:00
|
|
|
{
|
2018-08-03 12:32:15 +09:00
|
|
|
case GIMP_TEXT_JUSTIFY_LEFT:
|
|
|
|
align = PANGO_ALIGN_LEFT;
|
|
|
|
break;
|
|
|
|
case GIMP_TEXT_JUSTIFY_RIGHT:
|
|
|
|
align = PANGO_ALIGN_RIGHT;
|
|
|
|
break;
|
|
|
|
case GIMP_TEXT_JUSTIFY_CENTER:
|
|
|
|
align = PANGO_ALIGN_CENTER;
|
|
|
|
break;
|
|
|
|
case GIMP_TEXT_JUSTIFY_FILL:
|
|
|
|
align = PANGO_ALIGN_LEFT;
|
2010-08-27 23:22:10 +03:00
|
|
|
justify = TRUE;
|
2018-08-03 12:32:15 +09:00
|
|
|
break;
|
2010-08-27 23:22:10 +03:00
|
|
|
}
|
2018-08-03 12:32:15 +09:00
|
|
|
pango_layout_set_alignment (layout, align);
|
|
|
|
pango_layout_set_justify (layout, justify);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
/* Indentation */
|
2022-09-30 16:21:47 +02:00
|
|
|
indent = gimp_text_layer_get_indent (GIMP_TEXT_LAYER (layer));
|
2010-08-27 23:22:10 +03:00
|
|
|
pango_layout_set_indent (layout, (int)(PANGO_SCALE * indent));
|
|
|
|
|
|
|
|
/* Line Spacing */
|
2022-09-30 16:21:47 +02:00
|
|
|
line_spacing = gimp_text_layer_get_line_spacing (GIMP_TEXT_LAYER (layer));
|
2010-08-27 23:22:10 +03:00
|
|
|
pango_layout_set_spacing (layout, (int)(PANGO_SCALE * line_spacing));
|
|
|
|
|
|
|
|
/* Letter Spacing */
|
2022-09-30 16:21:47 +02:00
|
|
|
letter_spacing = gimp_text_layer_get_letter_spacing (GIMP_TEXT_LAYER (layer));
|
2010-08-27 23:22:10 +03:00
|
|
|
letter_spacing_at = pango_attr_letter_spacing_new ((int)(PANGO_SCALE * letter_spacing));
|
|
|
|
pango_attr_list_insert (attr_list, letter_spacing_at);
|
|
|
|
|
|
|
|
|
|
|
|
pango_layout_set_attributes (layout, attr_list);
|
|
|
|
|
|
|
|
/* Use the pango markup of the text layer */
|
|
|
|
|
|
|
|
if (markup != NULL && markup[0] != '\0')
|
|
|
|
pango_layout_set_markup (layout, markup, -1);
|
|
|
|
else /* If we can't find a markup, then it has just text */
|
|
|
|
pango_layout_set_text (layout, text, -1);
|
|
|
|
|
2018-08-03 12:32:15 +09:00
|
|
|
if (dir == GIMP_TEXT_DIRECTION_TTB_RTL ||
|
|
|
|
dir == GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT)
|
|
|
|
{
|
2021-04-06 14:28:40 +02:00
|
|
|
cairo_translate (cr, gimp_drawable_get_width (GIMP_DRAWABLE (layer)), 0);
|
2018-08-03 12:32:15 +09:00
|
|
|
cairo_rotate (cr, G_PI_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dir == GIMP_TEXT_DIRECTION_TTB_LTR ||
|
|
|
|
dir == GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT)
|
|
|
|
{
|
2021-04-06 14:28:40 +02:00
|
|
|
cairo_translate (cr, 0, gimp_drawable_get_height (GIMP_DRAWABLE (layer)));
|
2018-08-03 12:32:15 +09:00
|
|
|
cairo_rotate (cr, -G_PI_2);
|
|
|
|
}
|
|
|
|
|
2010-08-27 23:22:10 +03:00
|
|
|
pango_cairo_show_layout (cr, layout);
|
|
|
|
|
|
|
|
g_free (text);
|
2018-08-03 12:32:15 +09:00
|
|
|
g_free (language);
|
2010-08-27 23:22:10 +03:00
|
|
|
|
|
|
|
g_object_unref (layout);
|
|
|
|
pango_font_description_free (font_description);
|
|
|
|
g_object_unref (context);
|
|
|
|
pango_attr_list_unref (attr_list);
|
|
|
|
|
|
|
|
cairo_font_options_destroy (options);
|
|
|
|
|
|
|
|
cairo_restore (cr);
|
|
|
|
}
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2019-07-11 23:55:51 +02:00
|
|
|
static gboolean
|
2022-09-10 22:16:11 +02:00
|
|
|
draw_layer (GimpLayer **layers,
|
|
|
|
gint n_layers,
|
|
|
|
GimpProcedureConfig *config,
|
2024-03-24 02:20:05 +00:00
|
|
|
gboolean single_image,
|
2022-09-10 22:16:11 +02:00
|
|
|
gint j,
|
|
|
|
cairo_t *cr,
|
|
|
|
gdouble x_res,
|
|
|
|
gdouble y_res,
|
|
|
|
const gchar *name,
|
|
|
|
gboolean show_progress,
|
|
|
|
gdouble progress_start,
|
|
|
|
gdouble progress_end,
|
2022-09-10 22:56:01 +02:00
|
|
|
gint layer_level,
|
2022-09-10 22:16:11 +02:00
|
|
|
GError **error)
|
2019-03-20 19:59:07 +01:00
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
GimpLayer *layer;
|
2020-01-01 18:40:52 +01:00
|
|
|
gdouble opacity;
|
2022-09-10 22:16:11 +02:00
|
|
|
gboolean vectorize;
|
|
|
|
gboolean ignore_hidden;
|
2024-03-24 02:20:05 +00:00
|
|
|
gboolean layers_as_pages = FALSE;
|
2024-04-13 15:10:25 +00:00
|
|
|
gboolean reverse_order = FALSE;
|
2022-09-10 22:56:01 +02:00
|
|
|
gboolean root_layers_only;
|
2022-09-10 22:16:11 +02:00
|
|
|
gboolean convert_text;
|
|
|
|
|
|
|
|
g_object_get (config,
|
|
|
|
"vectorize", &vectorize,
|
|
|
|
"ignore-hidden", &ignore_hidden,
|
|
|
|
NULL);
|
|
|
|
|
2024-03-24 02:20:05 +00:00
|
|
|
if (single_image)
|
|
|
|
g_object_get (config,
|
|
|
|
"layers-as-pages", &layers_as_pages,
|
|
|
|
"reverse-order", &reverse_order,
|
|
|
|
"root-layers-only", &root_layers_only,
|
|
|
|
"convert-text-layers", &convert_text,
|
|
|
|
NULL);
|
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
if (reverse_order && layers_as_pages)
|
2019-08-27 14:12:50 +02:00
|
|
|
layer = layers [j];
|
2019-03-20 19:59:07 +01:00
|
|
|
else
|
2019-08-27 14:12:50 +02:00
|
|
|
layer = layers [n_layers - j - 1];
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
opacity = gimp_layer_get_opacity (layer) / 100.0;
|
|
|
|
if ((! gimp_item_get_visible (GIMP_ITEM (layer)) || opacity == 0.0) &&
|
2022-09-10 22:16:11 +02:00
|
|
|
ignore_hidden)
|
2020-01-01 18:40:52 +01:00
|
|
|
return TRUE;
|
|
|
|
|
2019-08-27 14:12:50 +02:00
|
|
|
if (gimp_item_is_group (GIMP_ITEM (layer)))
|
2019-03-20 19:59:07 +01:00
|
|
|
{
|
2019-08-27 14:12:50 +02:00
|
|
|
GimpItem **children;
|
2024-10-21 23:57:10 +02:00
|
|
|
gint children_num = 0;
|
2019-08-27 14:12:50 +02:00
|
|
|
gint i;
|
2019-07-11 23:55:51 +02:00
|
|
|
|
2024-10-21 23:57:10 +02:00
|
|
|
children = gimp_item_get_children (GIMP_ITEM (layer));
|
|
|
|
children_num = gimp_core_object_array_get_length ((GObject **) children);
|
|
|
|
|
|
|
|
for (i = 0; children[i] != NULL; i++)
|
2019-03-20 19:59:07 +01:00
|
|
|
{
|
2022-09-10 22:16:11 +02:00
|
|
|
if (! draw_layer ((GimpLayer **) children, children_num,
|
2024-03-24 02:20:05 +00:00
|
|
|
config, single_image, i,
|
2021-02-15 22:21:30 +01:00
|
|
|
cr, x_res, y_res, name,
|
|
|
|
show_progress,
|
|
|
|
progress_start + i * (progress_end - progress_start) / children_num,
|
|
|
|
progress_end,
|
2022-09-10 22:56:01 +02:00
|
|
|
layer_level + 1, error))
|
2019-07-12 13:11:47 +02:00
|
|
|
{
|
|
|
|
g_free (children);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2019-03-20 19:59:07 +01:00
|
|
|
}
|
2019-07-12 13:11:47 +02:00
|
|
|
g_free (children);
|
2022-09-10 22:56:01 +02:00
|
|
|
|
|
|
|
if (root_layers_only && layers_as_pages &&
|
|
|
|
children_num > 0 && layer_level == 0)
|
|
|
|
cairo_show_page (cr);
|
2019-03-20 19:59:07 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-07-11 23:55:51 +02:00
|
|
|
cairo_surface_t *mask_image = NULL;
|
2019-08-27 14:12:50 +02:00
|
|
|
GimpLayerMask *mask = NULL;
|
2020-01-01 18:40:52 +01:00
|
|
|
gint x, y;
|
2019-07-11 23:55:51 +02:00
|
|
|
|
2021-02-15 22:21:30 +01:00
|
|
|
if (show_progress)
|
|
|
|
gimp_progress_update (progress_start);
|
|
|
|
|
2025-05-20 02:23:35 +00:00
|
|
|
/* Only render layer mask if it's not disabled */
|
|
|
|
if (gimp_layer_get_mask (layer) && gimp_layer_get_apply_mask (layer))
|
|
|
|
mask = gimp_layer_get_mask (layer);
|
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
if (mask)
|
|
|
|
{
|
|
|
|
mask_image = get_cairo_surface (GIMP_DRAWABLE (mask), TRUE,
|
|
|
|
error);
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
if (*error)
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);
|
2020-01-01 18:40:52 +01:00
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
if (! gimp_item_is_text_layer (GIMP_ITEM (layer)) || convert_text)
|
2019-03-20 19:59:07 +01:00
|
|
|
{
|
2020-01-01 18:40:52 +01:00
|
|
|
/* For raster layers */
|
2019-07-11 23:55:51 +02:00
|
|
|
|
2024-03-20 02:53:41 +00:00
|
|
|
GeglColor *layer_color;
|
|
|
|
gboolean single_color = FALSE;
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
layer_color = get_layer_color (layer, &single_color);
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
cairo_rectangle (cr, x, y,
|
2021-04-06 14:28:40 +02:00
|
|
|
gimp_drawable_get_width (GIMP_DRAWABLE (layer)),
|
|
|
|
gimp_drawable_get_height (GIMP_DRAWABLE (layer)));
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2022-09-10 22:16:11 +02:00
|
|
|
if (vectorize && single_color)
|
2019-03-20 19:59:07 +01:00
|
|
|
{
|
2024-03-20 02:53:41 +00:00
|
|
|
gimp_cairo_set_source_color (cr, layer_color, NULL, FALSE, NULL);
|
2020-01-01 18:40:52 +01:00
|
|
|
if (mask)
|
|
|
|
cairo_mask_surface (cr, mask_image, x, y);
|
2019-03-20 19:59:07 +01:00
|
|
|
else
|
2020-01-01 18:40:52 +01:00
|
|
|
cairo_fill (cr);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cairo_surface_t *layer_image;
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
layer_image = get_cairo_surface (GIMP_DRAWABLE (layer), FALSE,
|
|
|
|
error);
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
if (*error)
|
|
|
|
return FALSE;
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
cairo_clip (cr);
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
cairo_set_source_surface (cr, layer_image, x, y);
|
|
|
|
cairo_push_group (cr);
|
|
|
|
cairo_paint_with_alpha (cr, opacity);
|
|
|
|
cairo_pop_group_to_source (cr);
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
if (mask)
|
|
|
|
cairo_mask_surface (cr, mask_image, x, y);
|
|
|
|
else
|
|
|
|
cairo_paint (cr);
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
cairo_reset_clip (cr);
|
2019-03-20 19:59:07 +01:00
|
|
|
|
2020-01-01 18:40:52 +01:00
|
|
|
cairo_surface_destroy (layer_image);
|
2019-03-20 19:59:07 +01:00
|
|
|
}
|
2024-03-20 02:53:41 +00:00
|
|
|
|
|
|
|
g_object_unref (layer_color);
|
2019-03-20 19:59:07 +01:00
|
|
|
}
|
2020-01-01 18:40:52 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
/* For text layers */
|
|
|
|
drawText (layer, opacity, cr, x_res, y_res);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* draw new page if "layers as pages" option is checked */
|
2022-09-10 22:56:01 +02:00
|
|
|
if (layers_as_pages && (! root_layers_only || layer_level == 0))
|
2020-01-01 18:40:52 +01:00
|
|
|
cairo_show_page (cr);
|
2019-08-27 14:12:50 +02:00
|
|
|
|
2019-03-20 19:59:07 +01:00
|
|
|
/* We are done with the layer - time to free some resources */
|
2019-08-27 14:12:50 +02:00
|
|
|
if (mask)
|
2019-03-20 19:59:07 +01:00
|
|
|
cairo_surface_destroy (mask_image);
|
|
|
|
}
|
2019-07-11 23:55:51 +02:00
|
|
|
|
|
|
|
return TRUE;
|
2019-03-20 19:59:07 +01:00
|
|
|
}
|