Rewrite GTK printing using Scintilla
We used to draw the pages to print ourselves, but it is a tedious and error-prone task (and we did made mistakes) that Scintilla would do better by itself. So, rewrite the GTK (e.g. non-external) print code to use Scintilla's built-in capabilities of drawing the buffer's content on the pages. Not only this makes the code a lot simpler and shorter, but it gives a more accurate render (proper handling of tab stops, wrapping on word boundaries, no missing character when a wrapped line spans on multiple pages, ...), and it is noticeably faster (around 3 times). Additionally we now paginate properly, which fixes printing starting after page 1 or printing non-contiguous pages (we used to always print starting from page 1 and counting, no matter what pages were selected). However, note that obviously the render is not the same, even though it's quite similar.
This commit is contained in:
parent
5772178d3a
commit
4db0813a79
562
src/printing.c
562
src/printing.c
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright 2007-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
|
* Copyright 2007-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
|
||||||
* Copyright 2007-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
|
* Copyright 2007-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
|
||||||
|
* Copyright 2012 Colomban Wendling <ban(at)herbesfolles(dot)org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -34,56 +35,32 @@
|
|||||||
#include "document.h"
|
#include "document.h"
|
||||||
#include "sciwrappers.h"
|
#include "sciwrappers.h"
|
||||||
#include "editor.h"
|
#include "editor.h"
|
||||||
#include "sciwrappers.h"
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "support.h"
|
#include "support.h"
|
||||||
#include "dialogs.h"
|
#include "dialogs.h"
|
||||||
#include "utils.h"
|
|
||||||
#include "ui_utils.h"
|
#include "ui_utils.h"
|
||||||
#include "msgwindow.h"
|
#include "msgwindow.h"
|
||||||
|
#include "highlighting.h"
|
||||||
|
#include "Scintilla.h"
|
||||||
|
|
||||||
|
|
||||||
PrintingPrefs printing_prefs;
|
PrintingPrefs printing_prefs;
|
||||||
|
|
||||||
|
|
||||||
#define ROTATE_RGB(color) \
|
|
||||||
(((color) & 0xFF0000) >> 16) + ((color) & 0x00FF00) + (((color) & 0x0000FF) << 16)
|
|
||||||
#define ADD_ATTR(l, a) \
|
|
||||||
pango_attr_list_insert((l), (a)); \
|
|
||||||
(a)->start_index = 0; \
|
|
||||||
(a)->end_index = G_MAXUINT;
|
|
||||||
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
FORE = 0,
|
|
||||||
BACK,
|
|
||||||
BOLD,
|
|
||||||
ITALIC,
|
|
||||||
MAX_TYPES
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* document-related variables */
|
/* document-related variables */
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
GeanyDocument *doc;
|
GeanyDocument *doc;
|
||||||
gint font_width;
|
ScintillaObject *sci;
|
||||||
gint lines;
|
|
||||||
gint n_pages;
|
|
||||||
gint lines_per_page;
|
|
||||||
gint max_line_number_margin;
|
|
||||||
gint cur_line;
|
|
||||||
gint cur_pos;
|
|
||||||
gint styles[STYLE_MAX + 1][MAX_TYPES];
|
|
||||||
gdouble line_height;
|
gdouble line_height;
|
||||||
/* whether we have a wrapped line on page end to take care of on next page */
|
|
||||||
gboolean long_line;
|
|
||||||
/* set in begin_print() to hold the time when printing was started to ensure all printed
|
/* set in begin_print() to hold the time when printing was started to ensure all printed
|
||||||
* pages have the same date and time (in case of slow machines and many pages where rendering
|
* pages have the same date and time (in case of slow machines and many pages where rendering
|
||||||
* takes more than a second) */
|
* takes more than a second) */
|
||||||
time_t print_time;
|
time_t print_time;
|
||||||
PangoLayout *layout; /* commonly used layout object */
|
PangoLayout *layout; /* commonly used layout object */
|
||||||
|
|
||||||
|
struct Sci_RangeToFormat fr;
|
||||||
|
GArray *pages;
|
||||||
} DocInfo;
|
} DocInfo;
|
||||||
|
|
||||||
/* widget references for the custom widget in the print dialog */
|
/* widget references for the custom widget in the print dialog */
|
||||||
@ -102,33 +79,6 @@ static GtkPageSetup *page_setup = NULL;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* returns the "width" (count of needed characters) for the given number */
|
|
||||||
static gint get_line_numbers_arity(gint x)
|
|
||||||
{
|
|
||||||
gint a = 0;
|
|
||||||
while ((x /= 10) != 0)
|
|
||||||
a++;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* split a RGB colour into the three colour components */
|
|
||||||
static void get_rgb_values(gint c, gint *r, gint *g, gint *b)
|
|
||||||
{
|
|
||||||
c = ROTATE_RGB(c);
|
|
||||||
if (interface_prefs.highlighting_invert_all)
|
|
||||||
c = utils_invert_color(c);
|
|
||||||
|
|
||||||
*r = c % 256;
|
|
||||||
*g = (c & - 16711936) / 256;
|
|
||||||
*b = (c & 0xff0000) / 65536;
|
|
||||||
|
|
||||||
*r *= 257;
|
|
||||||
*g *= 257;
|
|
||||||
*b *= 257;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* creates a commonly used layout object from the given context for use in get_page_count and
|
/* creates a commonly used layout object from the given context for use in get_page_count and
|
||||||
* draw_page */
|
* draw_page */
|
||||||
static PangoLayout *setup_pango_layout(GtkPrintContext *context, PangoFontDescription *desc)
|
static PangoLayout *setup_pango_layout(GtkPrintContext *context, PangoFontDescription *desc)
|
||||||
@ -145,123 +95,20 @@ static PangoLayout *setup_pango_layout(GtkPrintContext *context, PangoFontDescri
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static gboolean utils_font_desc_check_monospace(PangoContext *pc, PangoFontDescription *desc)
|
static gdouble get_line_height(PangoLayout *layout)
|
||||||
{
|
{
|
||||||
PangoFontFamily **families;
|
|
||||||
gint n_families, i;
|
|
||||||
const gchar *font;
|
|
||||||
gboolean ret = TRUE;
|
|
||||||
|
|
||||||
font = pango_font_description_get_family(desc);
|
|
||||||
pango_context_list_families(pc, &families, &n_families);
|
|
||||||
for (i = 0; i < n_families; i++)
|
|
||||||
{
|
|
||||||
if (utils_str_equal(font, pango_font_family_get_name(families[i])))
|
|
||||||
{
|
|
||||||
if (!pango_font_family_is_monospace(families[i]))
|
|
||||||
{
|
|
||||||
ret = FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_free(families);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* We don't support variable width fonts (yet) */
|
|
||||||
static gint get_font_width(GtkPrintContext *context, PangoFontDescription *desc)
|
|
||||||
{
|
|
||||||
PangoContext *pc;
|
|
||||||
PangoFontMetrics *metrics;
|
|
||||||
gint width;
|
|
||||||
|
|
||||||
pc = gtk_print_context_create_pango_context(context);
|
|
||||||
|
|
||||||
if (!utils_font_desc_check_monospace(pc, desc))
|
|
||||||
dialogs_show_msgbox_with_secondary(GTK_MESSAGE_WARNING,
|
|
||||||
_("The editor font is not a monospaced font!"),
|
|
||||||
_("Text will be wrongly spaced."));
|
|
||||||
|
|
||||||
metrics = pango_context_get_metrics(pc, desc, pango_context_get_language(pc));
|
|
||||||
/** TODO is this the best result we can get? */
|
|
||||||
/* digit and char width are mostly equal for monospace fonts, char width might be
|
|
||||||
* for dual width characters(e.g. Japanese) so use digit width to get sure we get the width
|
|
||||||
* for one character */
|
|
||||||
width = pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE;
|
|
||||||
|
|
||||||
pango_font_metrics_unref(metrics);
|
|
||||||
g_object_unref(pc);
|
|
||||||
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static gint get_page_count(GtkPrintContext *context, DocInfo *dinfo)
|
|
||||||
{
|
|
||||||
gdouble width, height;
|
|
||||||
gint layout_h;
|
gint layout_h;
|
||||||
gint i, j, lines_left;
|
|
||||||
gchar *line_buf;
|
|
||||||
|
|
||||||
if (dinfo == NULL)
|
pango_layout_set_text(layout, "|XMfjgq_", -1); /* reasonably representative character set */
|
||||||
return -1;
|
pango_layout_get_size(layout, NULL, &layout_h);
|
||||||
|
|
||||||
width = gtk_print_context_get_width(context);
|
|
||||||
height = gtk_print_context_get_height(context);
|
|
||||||
|
|
||||||
if (printing_prefs.print_line_numbers)
|
|
||||||
/* remove line number margin space from overall width */
|
|
||||||
width -= dinfo->max_line_number_margin * dinfo->font_width;
|
|
||||||
|
|
||||||
pango_layout_set_width(dinfo->layout, width * PANGO_SCALE);
|
|
||||||
|
|
||||||
/* add test text to get line height */
|
|
||||||
pango_layout_set_text(dinfo->layout, "Test 1", -1);
|
|
||||||
pango_layout_get_size(dinfo->layout, NULL, &layout_h);
|
|
||||||
if (layout_h <= 0)
|
if (layout_h <= 0)
|
||||||
{
|
{
|
||||||
geany_debug("Invalid layout_h (%d). Falling back to default height (%d)",
|
geany_debug("Invalid layout_h (%d). Falling back to default height (%d)",
|
||||||
layout_h, 100 * PANGO_SCALE);
|
layout_h, 100 * PANGO_SCALE);
|
||||||
layout_h = 100 * PANGO_SCALE;
|
layout_h = 100 * PANGO_SCALE;
|
||||||
}
|
}
|
||||||
dinfo->line_height = (gdouble)layout_h / PANGO_SCALE;
|
|
||||||
dinfo->lines_per_page = ceil((height - dinfo->line_height) / dinfo->line_height);
|
|
||||||
#ifdef GEANY_PRINT_DEBUG
|
|
||||||
geany_debug("max lines_per_page: %d", dinfo->lines_per_page);
|
|
||||||
#endif
|
|
||||||
if (printing_prefs.print_page_numbers)
|
|
||||||
dinfo->lines_per_page -= 2;
|
|
||||||
if (printing_prefs.print_page_header)
|
|
||||||
dinfo->lines_per_page -= 3;
|
|
||||||
|
|
||||||
lines_left = dinfo->lines_per_page;
|
return (gdouble)layout_h / PANGO_SCALE;
|
||||||
|
|
||||||
i = 0;
|
|
||||||
for (j = 0; j < dinfo->lines; j++)
|
|
||||||
{
|
|
||||||
gint lines = 1;
|
|
||||||
gint line_width;
|
|
||||||
|
|
||||||
line_buf = sci_get_line(dinfo->doc->editor->sci, j);
|
|
||||||
line_width = (g_utf8_strlen(line_buf, -1) + 1) * dinfo->font_width;
|
|
||||||
if (line_width > width)
|
|
||||||
lines = ceil(line_width / width);
|
|
||||||
#ifdef GEANY_PRINT_DEBUG
|
|
||||||
if (lines != 1) geany_debug("%d %d", j+1, lines);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while (lines_left < lines)
|
|
||||||
{
|
|
||||||
lines -= lines_left;
|
|
||||||
lines_left = dinfo->lines_per_page;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
lines_left -= lines;
|
|
||||||
g_free(line_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return i + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -270,8 +117,7 @@ static void add_page_header(DocInfo *dinfo, cairo_t *cr, gint width, gint page_n
|
|||||||
gint ph_height = dinfo->line_height * 3;
|
gint ph_height = dinfo->line_height * 3;
|
||||||
gchar *data;
|
gchar *data;
|
||||||
gchar *datetime;
|
gchar *datetime;
|
||||||
gchar *tmp_file_name = (dinfo->doc->file_name != NULL) ?
|
const gchar *tmp_file_name = DOC_FILENAME(dinfo->doc);
|
||||||
dinfo->doc->file_name : GEANY_STRING_UNTITLED;
|
|
||||||
gchar *file_name = (printing_prefs.page_header_basename) ?
|
gchar *file_name = (printing_prefs.page_header_basename) ?
|
||||||
g_path_get_basename(tmp_file_name) : g_strdup(tmp_file_name);
|
g_path_get_basename(tmp_file_name) : g_strdup(tmp_file_name);
|
||||||
PangoLayout *layout = dinfo->layout;
|
PangoLayout *layout = dinfo->layout;
|
||||||
@ -285,10 +131,7 @@ static void add_page_header(DocInfo *dinfo, cairo_t *cr, gint width, gint page_n
|
|||||||
/* width - 8: 2px between doc border and frame border, 2px between frame border and text
|
/* width - 8: 2px between doc border and frame border, 2px between frame border and text
|
||||||
* and this on left and right side, so (2 + 2) * 2 */
|
* and this on left and right side, so (2 + 2) * 2 */
|
||||||
pango_layout_set_width(layout, (width - 8) * PANGO_SCALE);
|
pango_layout_set_width(layout, (width - 8) * PANGO_SCALE);
|
||||||
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_MIDDLE);
|
||||||
if ((g_utf8_strlen(file_name, -1) * dinfo->font_width) >= ((width - 4) - (dinfo->font_width * 2)))
|
|
||||||
/* if the filename is wider than the available space on the line, skip parts of it */
|
|
||||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_MIDDLE);
|
|
||||||
|
|
||||||
data = g_strdup_printf("<b>%s</b>", file_name);
|
data = g_strdup_printf("<b>%s</b>", file_name);
|
||||||
pango_layout_set_markup(layout, data, -1);
|
pango_layout_set_markup(layout, data, -1);
|
||||||
@ -298,7 +141,7 @@ static void add_page_header(DocInfo *dinfo, cairo_t *cr, gint width, gint page_n
|
|||||||
g_free(data);
|
g_free(data);
|
||||||
g_free(file_name);
|
g_free(file_name);
|
||||||
|
|
||||||
data = g_strdup_printf(_("<b>Page %d of %d</b>"), page_nr + 1, dinfo->n_pages);
|
data = g_strdup_printf(_("<b>Page %d of %d</b>"), page_nr + 1, dinfo->pages->len);
|
||||||
pango_layout_set_markup(layout, data, -1);
|
pango_layout_set_markup(layout, data, -1);
|
||||||
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
|
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
|
||||||
cairo_move_to(cr, 4, dinfo->line_height * 1.5);
|
cairo_move_to(cr, 4, dinfo->line_height * 1.5);
|
||||||
@ -319,7 +162,7 @@ static void add_page_header(DocInfo *dinfo, cairo_t *cr, gint width, gint page_n
|
|||||||
|
|
||||||
/* reset layout and re-position cairo context */
|
/* reset layout and re-position cairo context */
|
||||||
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
|
pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
|
||||||
pango_layout_set_ellipsize(layout, FALSE);
|
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
|
||||||
pango_layout_set_justify(layout, FALSE);
|
pango_layout_set_justify(layout, FALSE);
|
||||||
pango_layout_set_width(layout, width * PANGO_SCALE);
|
pango_layout_set_width(layout, width * PANGO_SCALE);
|
||||||
cairo_move_to(cr, 0, dinfo->line_height * 3);
|
cairo_move_to(cr, 0, dinfo->line_height * 3);
|
||||||
@ -432,7 +275,44 @@ static void end_print(GtkPrintOperation *operation, GtkPrintContext *context, gp
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
gtk_widget_hide(main_widgets.progressbar);
|
gtk_widget_hide(main_widgets.progressbar);
|
||||||
|
g_object_unref(dinfo->sci);
|
||||||
g_object_unref(dinfo->layout);
|
g_object_unref(dinfo->layout);
|
||||||
|
g_array_free(dinfo->pages, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void setup_range(DocInfo *dinfo, GtkPrintContext *ctx)
|
||||||
|
{
|
||||||
|
dinfo->fr.hdc = dinfo->fr.hdcTarget = gtk_print_context_get_cairo_context(ctx);
|
||||||
|
|
||||||
|
dinfo->fr.rcPage.left = 0;
|
||||||
|
dinfo->fr.rcPage.top = 0;
|
||||||
|
dinfo->fr.rcPage.right = gtk_print_context_get_width(ctx);
|
||||||
|
dinfo->fr.rcPage.bottom = gtk_print_context_get_height(ctx);
|
||||||
|
|
||||||
|
dinfo->fr.rc.left = dinfo->fr.rcPage.left;
|
||||||
|
dinfo->fr.rc.top = dinfo->fr.rcPage.top;
|
||||||
|
dinfo->fr.rc.right = dinfo->fr.rcPage.right;
|
||||||
|
dinfo->fr.rc.bottom = dinfo->fr.rcPage.bottom;
|
||||||
|
#if GTK_CHECK_VERSION(2, 20, 0)
|
||||||
|
{
|
||||||
|
gdouble m_top, m_left, m_right, m_bottom;
|
||||||
|
if (gtk_print_context_get_hard_margins(ctx, &m_top, &m_bottom, &m_left, &m_right))
|
||||||
|
{
|
||||||
|
dinfo->fr.rc.left += m_left;
|
||||||
|
dinfo->fr.rc.top += m_top;
|
||||||
|
dinfo->fr.rc.right -= m_right;
|
||||||
|
dinfo->fr.rc.bottom -= m_bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (printing_prefs.print_page_header)
|
||||||
|
dinfo->fr.rc.top += dinfo->line_height * 3; /* header height */
|
||||||
|
if (printing_prefs.print_page_numbers)
|
||||||
|
dinfo->fr.rc.bottom -= dinfo->line_height * 1; /* footer height */
|
||||||
|
|
||||||
|
dinfo->fr.chrg.cpMin = 0;
|
||||||
|
dinfo->fr.chrg.cpMax = sci_get_length(dinfo->sci);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -440,65 +320,57 @@ static void begin_print(GtkPrintOperation *operation, GtkPrintContext *context,
|
|||||||
{
|
{
|
||||||
DocInfo *dinfo = user_data;
|
DocInfo *dinfo = user_data;
|
||||||
PangoFontDescription *desc;
|
PangoFontDescription *desc;
|
||||||
gint i;
|
|
||||||
gint style_max;
|
|
||||||
|
|
||||||
if (dinfo == NULL)
|
if (dinfo == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
gtk_widget_show(main_widgets.progressbar);
|
gtk_widget_show(main_widgets.progressbar);
|
||||||
|
|
||||||
desc = pango_font_description_from_string(interface_prefs.editor_font);
|
|
||||||
|
|
||||||
/* init dinfo fields */
|
/* init dinfo fields */
|
||||||
dinfo->lines = sci_get_line_count(dinfo->doc->editor->sci);
|
|
||||||
dinfo->lines_per_page = 0;
|
/* setup printing scintilla object */
|
||||||
dinfo->cur_line = 0;
|
dinfo->sci = editor_create_widget(dinfo->doc->editor);
|
||||||
dinfo->cur_pos = 0;
|
scintilla_send_message(dinfo->sci, SCI_SETDOCPOINTER, 0,
|
||||||
dinfo->long_line = FALSE;
|
scintilla_send_message(dinfo->doc->editor->sci, SCI_GETDOCPOINTER, 0, 0));
|
||||||
|
highlighting_set_styles(dinfo->sci, dinfo->doc->file_type);
|
||||||
|
sci_set_line_numbers(dinfo->sci, printing_prefs.print_line_numbers, 0);
|
||||||
|
scintilla_send_message(dinfo->sci, SCI_SETVIEWWS, SCWS_INVISIBLE, 0);
|
||||||
|
scintilla_send_message(dinfo->sci, SCI_SETVIEWEOL, FALSE, 0);
|
||||||
|
scintilla_send_message(dinfo->sci, SCI_SETEDGEMODE, EDGE_NONE, 0);
|
||||||
|
scintilla_send_message(dinfo->sci, SCI_SETPRINTMAGNIFICATION, (uptr_t) -2, 0); /* WTF? */
|
||||||
|
scintilla_send_message(dinfo->sci, SCI_SETPRINTCOLOURMODE, SC_PRINT_COLOURONWHITE, 0);
|
||||||
|
|
||||||
|
dinfo->pages = g_array_new(FALSE, FALSE, sizeof(gint));
|
||||||
|
|
||||||
dinfo->print_time = time(NULL);
|
dinfo->print_time = time(NULL);
|
||||||
dinfo->max_line_number_margin = get_line_numbers_arity(dinfo->lines) + 1;
|
/* create a PangoLayout to be commonly used in add_page_header() and draw_page() */
|
||||||
/* increase font width by 1 (looks better) */
|
desc = pango_font_description_from_string(interface_prefs.editor_font);
|
||||||
dinfo->font_width = get_font_width(context, desc) + 1;
|
|
||||||
/* create a PangoLayout to be commonly used in get_page_count and draw_page */
|
|
||||||
dinfo->layout = setup_pango_layout(context, desc);
|
dinfo->layout = setup_pango_layout(context, desc);
|
||||||
/* this is necessary because of possible line breaks on the printed page and then
|
|
||||||
* lines_per_page differs from document line count */
|
|
||||||
dinfo->n_pages = get_page_count(context, dinfo);
|
|
||||||
|
|
||||||
/* read all styles from Scintilla */
|
|
||||||
style_max = pow(2, scintilla_send_message(dinfo->doc->editor->sci, SCI_GETSTYLEBITS, 0, 0));
|
|
||||||
/* if the lexer uses only the first 32 styles(style bits = 5),
|
|
||||||
* we need to add the pre-defined styles */
|
|
||||||
if (style_max == 32)
|
|
||||||
style_max = STYLE_LASTPREDEFINED;
|
|
||||||
for (i = 0; i < style_max; i++)
|
|
||||||
{
|
|
||||||
dinfo->styles[i][FORE] = ROTATE_RGB(scintilla_send_message(
|
|
||||||
dinfo->doc->editor->sci, SCI_STYLEGETFORE, i, 0));
|
|
||||||
if (i == STYLE_LINENUMBER)
|
|
||||||
{ /* ignore background colour for line number margin to avoid trouble with wrapped lines */
|
|
||||||
dinfo->styles[STYLE_LINENUMBER][BACK] = ROTATE_RGB(scintilla_send_message(
|
|
||||||
dinfo->doc->editor->sci, SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dinfo->styles[i][BACK] = ROTATE_RGB(scintilla_send_message(
|
|
||||||
dinfo->doc->editor->sci, SCI_STYLEGETBACK, i, 0));
|
|
||||||
}
|
|
||||||
/* use white background color unless foreground is white to save ink */
|
|
||||||
if (dinfo->styles[i][FORE] != 0xffffff)
|
|
||||||
dinfo->styles[i][BACK] = 0xffffff;
|
|
||||||
dinfo->styles[i][BOLD] =
|
|
||||||
scintilla_send_message(dinfo->doc->editor->sci, SCI_STYLEGETBOLD, i, 0);
|
|
||||||
dinfo->styles[i][ITALIC] =
|
|
||||||
scintilla_send_message(dinfo->doc->editor->sci, SCI_STYLEGETITALIC, i, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dinfo->n_pages >= 0)
|
|
||||||
gtk_print_operation_set_n_pages(operation, dinfo->n_pages);
|
|
||||||
|
|
||||||
pango_font_description_free(desc);
|
pango_font_description_free(desc);
|
||||||
|
dinfo->line_height = get_line_height(dinfo->layout);
|
||||||
|
/* setup dinfo->fr */
|
||||||
|
setup_range(dinfo, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean paginate(GtkPrintOperation *operation, GtkPrintContext *context, gpointer user_data)
|
||||||
|
{
|
||||||
|
DocInfo *dinfo = user_data;
|
||||||
|
|
||||||
|
/* for whatever reason we get called one more time after we returned TRUE, so avoid adding
|
||||||
|
* an empty page at the end */
|
||||||
|
if (dinfo->fr.chrg.cpMin >= dinfo->fr.chrg.cpMax)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
gtk_progress_bar_pulse(GTK_PROGRESS_BAR(main_widgets.progressbar));
|
||||||
|
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), _("Paginating"));
|
||||||
|
|
||||||
|
g_array_append_val(dinfo->pages, dinfo->fr.chrg.cpMin);
|
||||||
|
dinfo->fr.chrg.cpMin = (gint) scintilla_send_message(dinfo->sci, SCI_FORMATRANGE, FALSE, (sptr_t) &dinfo->fr);
|
||||||
|
|
||||||
|
gtk_print_operation_set_n_pages(operation, dinfo->pages->len);
|
||||||
|
|
||||||
|
return dinfo->fr.chrg.cpMin >= dinfo->fr.chrg.cpMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -506,249 +378,54 @@ static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context,
|
|||||||
gint page_nr, gpointer user_data)
|
gint page_nr, gpointer user_data)
|
||||||
{
|
{
|
||||||
DocInfo *dinfo = user_data;
|
DocInfo *dinfo = user_data;
|
||||||
GeanyEditor *editor;
|
|
||||||
cairo_t *cr;
|
cairo_t *cr;
|
||||||
gdouble width, height;
|
gdouble width, height;
|
||||||
gdouble x = 0.0, y = 0.0;
|
|
||||||
/*gint layout_h;*/
|
|
||||||
gint count;
|
|
||||||
GString *str;
|
|
||||||
|
|
||||||
if (dinfo == NULL || page_nr >= dinfo->n_pages)
|
g_return_if_fail(dinfo != NULL);
|
||||||
return;
|
g_return_if_fail(page_nr < dinfo->pages->len);
|
||||||
|
|
||||||
editor = dinfo->doc->editor;
|
if (dinfo->pages->len > 0)
|
||||||
|
|
||||||
if (dinfo->n_pages > 0)
|
|
||||||
{
|
{
|
||||||
gdouble fraction = (page_nr + 1) / (gdouble) dinfo->n_pages;
|
gdouble fraction = (page_nr + 1) / (gdouble) dinfo->pages->len;
|
||||||
gchar *text = g_strdup_printf(_("Page %d of %d"), page_nr, dinfo->n_pages);
|
gchar *text = g_strdup_printf(_("Page %d of %d"), page_nr + 1, dinfo->pages->len);
|
||||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(main_widgets.progressbar), fraction);
|
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(main_widgets.progressbar), fraction);
|
||||||
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), text);
|
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(main_widgets.progressbar), text);
|
||||||
g_free(text);
|
g_free(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef GEANY_PRINT_DEBUG
|
|
||||||
geany_debug("draw_page = %d, pages = %d, (real) lines_per_page = %d",
|
|
||||||
page_nr, dinfo->n_pages, dinfo->lines_per_page);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
str = g_string_sized_new(256);
|
|
||||||
cr = gtk_print_context_get_cairo_context(context);
|
cr = gtk_print_context_get_cairo_context(context);
|
||||||
width = gtk_print_context_get_width(context);
|
width = gtk_print_context_get_width(context);
|
||||||
height = gtk_print_context_get_height(context);
|
height = gtk_print_context_get_height(context);
|
||||||
|
|
||||||
cairo_set_source_rgb(cr, 0, 0, 0);
|
|
||||||
#ifdef GEANY_PRINT_DEBUG
|
|
||||||
cairo_set_line_width(cr, 0.2);
|
|
||||||
cairo_rectangle(cr, 0, 0, width, height);
|
|
||||||
cairo_stroke(cr);
|
|
||||||
#endif
|
|
||||||
cairo_move_to(cr, 0, 0);
|
|
||||||
|
|
||||||
pango_layout_set_width(dinfo->layout, width * PANGO_SCALE);
|
|
||||||
pango_layout_set_alignment(dinfo->layout, PANGO_ALIGN_LEFT);
|
|
||||||
pango_layout_set_ellipsize(dinfo->layout, FALSE);
|
|
||||||
pango_layout_set_justify(dinfo->layout, FALSE);
|
|
||||||
|
|
||||||
if (printing_prefs.print_page_header)
|
if (printing_prefs.print_page_header)
|
||||||
add_page_header(dinfo, cr, width, page_nr);
|
add_page_header(dinfo, cr, width, page_nr);
|
||||||
|
|
||||||
count = 0; /* the actual line counter for the current page, might be different from
|
dinfo->fr.chrg.cpMin = g_array_index(dinfo->pages, gint, page_nr);
|
||||||
* dinfo->cur_line due to possible line breaks */
|
if (page_nr + 1 < dinfo->pages->len)
|
||||||
while (count < dinfo->lines_per_page)
|
dinfo->fr.chrg.cpMax = g_array_index(dinfo->pages, gint, page_nr + 1) - 1;
|
||||||
{
|
else /* it's the last page, print 'til the end */
|
||||||
gchar c = 'a';
|
dinfo->fr.chrg.cpMax = sci_get_length(dinfo->sci);
|
||||||
gint style = -1;
|
|
||||||
PangoAttrList *layout_attr;
|
|
||||||
PangoAttribute *attr;
|
|
||||||
gint colours[3] = { 0 };
|
|
||||||
gboolean add_linenumber = TRUE;
|
|
||||||
gboolean at_eol;
|
|
||||||
|
|
||||||
while (count < dinfo->lines_per_page && c != '\0')
|
scintilla_send_message(dinfo->sci, SCI_FORMATRANGE, TRUE, (sptr_t) &dinfo->fr);
|
||||||
{
|
|
||||||
at_eol = FALSE;
|
|
||||||
|
|
||||||
g_string_erase(str, 0, str->len); /* clear the string */
|
/* reset color */
|
||||||
|
cairo_set_source_rgb(cr, 0, 0, 0);
|
||||||
/* line numbers */
|
|
||||||
if (printing_prefs.print_line_numbers && add_linenumber)
|
|
||||||
{
|
|
||||||
/* if we had a wrapped line on the last page which needs to be continued, don't
|
|
||||||
* add a line number */
|
|
||||||
if (dinfo->long_line)
|
|
||||||
{
|
|
||||||
add_linenumber = FALSE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gchar *line_number = NULL;
|
|
||||||
gint cur_line_number_margin = get_line_numbers_arity(dinfo->cur_line + 1);
|
|
||||||
gchar *fill = g_strnfill(
|
|
||||||
dinfo->max_line_number_margin - cur_line_number_margin - 1, ' ');
|
|
||||||
|
|
||||||
line_number = g_strdup_printf("%s%d ", fill, dinfo->cur_line + 1);
|
|
||||||
g_string_append(str, line_number);
|
|
||||||
dinfo->cur_line++; /* increase document line */
|
|
||||||
add_linenumber = FALSE;
|
|
||||||
style = STYLE_LINENUMBER;
|
|
||||||
c = 'a'; /* dummy value */
|
|
||||||
g_free(fill);
|
|
||||||
g_free(line_number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* data */
|
|
||||||
else
|
|
||||||
{
|
|
||||||
style = sci_get_style_at(dinfo->doc->editor->sci, dinfo->cur_pos);
|
|
||||||
c = sci_get_char_at(dinfo->doc->editor->sci, dinfo->cur_pos);
|
|
||||||
if (c == '\0' || style == -1)
|
|
||||||
{ /* if c gets 0, we are probably out of document boundaries,
|
|
||||||
* so stop to break out of outer loop */
|
|
||||||
count = dinfo->lines_per_page;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
dinfo->cur_pos++;
|
|
||||||
|
|
||||||
/* convert tabs to spaces which seems to be better than using Pango tabs */
|
|
||||||
if (c == '\t')
|
|
||||||
{
|
|
||||||
gint tab_width = sci_get_tab_width(editor->sci);
|
|
||||||
gchar *s = g_strnfill(tab_width, ' ');
|
|
||||||
g_string_append(str, s);
|
|
||||||
g_free(s);
|
|
||||||
}
|
|
||||||
/* don't add line breaks, they are handled manually below */
|
|
||||||
else if (c == '\r' || c == '\n')
|
|
||||||
{
|
|
||||||
gchar c_next = sci_get_char_at(dinfo->doc->editor->sci, dinfo->cur_pos);
|
|
||||||
at_eol = TRUE;
|
|
||||||
if (c == '\r' && c_next == '\n')
|
|
||||||
dinfo->cur_pos++; /* skip LF part of CR/LF */
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_string_append_c(str, c); /* finally add the character */
|
|
||||||
|
|
||||||
/* handle UTF-8: since we add char by char (better: byte by byte), we need to
|
|
||||||
* keep UTF-8 characters together(e.g. two bytes for one character)
|
|
||||||
* the input is always UTF-8 and c is signed, so all non-Ascii
|
|
||||||
* characters are less than 0 and consist of all bytes less than 0.
|
|
||||||
* style doesn't change since it is only one character with multiple bytes. */
|
|
||||||
while (c < 0)
|
|
||||||
{
|
|
||||||
c = sci_get_char_at(dinfo->doc->editor->sci, dinfo->cur_pos);
|
|
||||||
if (c < 0)
|
|
||||||
{ /* only add the byte when it is part of the UTF-8 character
|
|
||||||
* otherwise we could add e.g. a '\n' and it won't be visible in the
|
|
||||||
* printed document */
|
|
||||||
g_string_append_c(str, c);
|
|
||||||
dinfo->cur_pos++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! at_eol)
|
|
||||||
{
|
|
||||||
/* set text */
|
|
||||||
pango_layout_set_text(dinfo->layout, str->str, -1);
|
|
||||||
/* attributes */
|
|
||||||
layout_attr = pango_attr_list_new();
|
|
||||||
/* foreground colour */
|
|
||||||
get_rgb_values(dinfo->styles[style][FORE], &colours[0], &colours[1], &colours[2]);
|
|
||||||
attr = pango_attr_foreground_new(colours[0], colours[1], colours[2]);
|
|
||||||
ADD_ATTR(layout_attr, attr);
|
|
||||||
/* background colour */
|
|
||||||
get_rgb_values(dinfo->styles[style][BACK], &colours[0], &colours[1], &colours[2]);
|
|
||||||
attr = pango_attr_background_new(colours[0], colours[1], colours[2]);
|
|
||||||
ADD_ATTR(layout_attr, attr);
|
|
||||||
/* bold text */
|
|
||||||
if (dinfo->styles[style][BOLD])
|
|
||||||
{
|
|
||||||
attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
|
|
||||||
ADD_ATTR(layout_attr, attr);
|
|
||||||
}
|
|
||||||
/* italic text */
|
|
||||||
if (dinfo->styles[style][ITALIC])
|
|
||||||
{
|
|
||||||
attr = pango_attr_style_new(PANGO_STYLE_ITALIC);
|
|
||||||
ADD_ATTR(layout_attr, attr);
|
|
||||||
}
|
|
||||||
pango_layout_set_attributes(dinfo->layout, layout_attr);
|
|
||||||
pango_layout_context_changed(dinfo->layout);
|
|
||||||
pango_attr_list_unref(layout_attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_get_current_point(cr, &x, &y);
|
|
||||||
|
|
||||||
|
|
||||||
/* normal line break at eol character in document */
|
|
||||||
if (at_eol)
|
|
||||||
{
|
|
||||||
/*pango_layout_get_size(dinfo->layout, NULL, &layout_h);*/
|
|
||||||
/*cairo_move_to(cr, 0, y + (gdouble)layout_h / PANGO_SCALE);*/
|
|
||||||
cairo_move_to(cr, 0, y + dinfo->line_height);
|
|
||||||
|
|
||||||
count++;
|
|
||||||
/* we added a new document line so request a new line number */
|
|
||||||
add_linenumber = TRUE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gint x_offset = 0;
|
|
||||||
/* maybe we need to force a line break because of too long line */
|
|
||||||
if (x >= (width - dinfo->font_width))
|
|
||||||
{
|
|
||||||
/* don't start the line at horizontal origin because we need to skip the
|
|
||||||
* line number margin */
|
|
||||||
if (printing_prefs.print_line_numbers)
|
|
||||||
{
|
|
||||||
x_offset = (dinfo->max_line_number_margin + 1) * dinfo->font_width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*pango_layout_get_size(dinfo->layout, NULL, &layout_h);*/
|
|
||||||
/*cairo_move_to(cr, x_offset, y + (gdouble)layout_h / PANGO_SCALE);*/
|
|
||||||
/* this is faster but not exactly the same as above */
|
|
||||||
cairo_move_to(cr, x_offset, y + dinfo->line_height);
|
|
||||||
cairo_get_current_point(cr, &x, &y);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
if (count < dinfo->lines_per_page)
|
|
||||||
{
|
|
||||||
/* str->len is counted in bytes not characters, so use g_utf8_strlen() */
|
|
||||||
x_offset = (g_utf8_strlen(str->str, -1) * dinfo->font_width);
|
|
||||||
|
|
||||||
if (dinfo->long_line && count == 0)
|
|
||||||
{
|
|
||||||
x_offset = (dinfo->max_line_number_margin + 1) * dinfo->font_width;
|
|
||||||
dinfo->long_line = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
pango_cairo_show_layout(cr, dinfo->layout);
|
|
||||||
cairo_move_to(cr, x + x_offset, y);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
/* we are on a wrapped line but we are out of lines on this page, so continue
|
|
||||||
* the current line on the next page and remember to continue in current line */
|
|
||||||
dinfo->long_line = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (printing_prefs.print_line_numbers)
|
if (printing_prefs.print_line_numbers)
|
||||||
{ /* print a thin line between the line number margin and the data */
|
{ /* print a thin line between the line number margin and the data */
|
||||||
gint y_start = 0;
|
gint x = scintilla_send_message(dinfo->sci, SCI_GETMARGINWIDTHN, 0, 0) + 1;
|
||||||
|
gint y1 = 0, y2 = height;
|
||||||
|
|
||||||
if (printing_prefs.print_page_header)
|
if (printing_prefs.print_page_header)
|
||||||
y_start = (dinfo->line_height * 3) - 2; /* "- 2": to connect the line number line to
|
y1 += (dinfo->line_height * 3) - 2; /* "- 2": to connect the line number line to
|
||||||
* the page header frame */
|
* the page header frame */
|
||||||
|
|
||||||
|
if (printing_prefs.print_page_numbers)
|
||||||
|
y2 -= (dinfo->line_height * 2) - 2;
|
||||||
|
|
||||||
cairo_set_line_width(cr, 0.3);
|
cairo_set_line_width(cr, 0.3);
|
||||||
cairo_move_to(cr, (dinfo->max_line_number_margin * dinfo->font_width) + 1, y_start);
|
cairo_move_to(cr, x, y1);
|
||||||
cairo_line_to(cr, (dinfo->max_line_number_margin * dinfo->font_width) + 1,
|
cairo_line_to(cr, x, y2);
|
||||||
y + dinfo->line_height); /* y is last added line, we reuse it */
|
|
||||||
cairo_stroke(cr);
|
cairo_stroke(cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,15 +437,7 @@ static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context,
|
|||||||
cairo_move_to(cr, 0, height - dinfo->line_height);
|
cairo_move_to(cr, 0, height - dinfo->line_height);
|
||||||
pango_cairo_show_layout(cr, dinfo->layout);
|
pango_cairo_show_layout(cr, dinfo->layout);
|
||||||
g_free(line);
|
g_free(line);
|
||||||
|
|
||||||
#ifdef GEANY_PRINT_DEBUG
|
|
||||||
cairo_set_line_width(cr, 0.3);
|
|
||||||
cairo_move_to(cr, 0, height - (1.25 * dinfo->line_height));
|
|
||||||
cairo_line_to(cr, width - 1, height - (1.25 * dinfo->line_height));
|
|
||||||
cairo_stroke(cr);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
g_string_free(str, TRUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -807,6 +476,7 @@ static void printing_print_gtk(GeanyDocument *doc)
|
|||||||
|
|
||||||
g_signal_connect(op, "begin-print", G_CALLBACK(begin_print), &dinfo);
|
g_signal_connect(op, "begin-print", G_CALLBACK(begin_print), &dinfo);
|
||||||
g_signal_connect(op, "end-print", G_CALLBACK(end_print), &dinfo);
|
g_signal_connect(op, "end-print", G_CALLBACK(end_print), &dinfo);
|
||||||
|
g_signal_connect(op, "paginate", G_CALLBACK(paginate), &dinfo);
|
||||||
g_signal_connect(op, "draw-page", G_CALLBACK(draw_page), &dinfo);
|
g_signal_connect(op, "draw-page", G_CALLBACK(draw_page), &dinfo);
|
||||||
g_signal_connect(op, "status-changed", G_CALLBACK(status_changed), doc->file_name);
|
g_signal_connect(op, "status-changed", G_CALLBACK(status_changed), doc->file_name);
|
||||||
g_signal_connect(op, "create-custom-widget", G_CALLBACK(create_custom_widget), widgets);
|
g_signal_connect(op, "create-custom-widget", G_CALLBACK(create_custom_widget), widgets);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user