summaryrefslogtreecommitdiff
path: root/desktop/save_pdf.c
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/save_pdf.c')
-rw-r--r--desktop/save_pdf.c990
1 files changed, 990 insertions, 0 deletions
diff --git a/desktop/save_pdf.c b/desktop/save_pdf.c
new file mode 100644
index 000000000..e3f9cbc34
--- /dev/null
+++ b/desktop/save_pdf.c
@@ -0,0 +1,990 @@
+/*
+ * Copyright 2008 Adam Blokus <adamblokus@gmail.com>
+ * Copyright 2009 John Tytgat <joty@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf 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; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ /** \file
+ * Target independent PDF plotting using Haru Free PDF Library.
+ */
+
+/* TODO
+ * - finish all graphic primitives
+ * - allow adding raw bitmaps
+ * - make image-aware (embed the image in its native/original type if possible)
+ *
+ * - adjust content width to page width
+ * - divide output into multiple pages (not just the first one)
+ * - rearrange file structure
+ *
+ * - separate print-plotting as much as possible from window redrawing
+ * - add text-scaling (if not yet using the original font - make the default one
+ * have the same width)
+ * - add a save file.. dialogue
+ * - add utf support to Haru ( doable? )
+ * - wait for browser to end fetching?
+ * - analyze and deal with performance issues(huge file hangs some pdf viewers,
+ * for example kpdf when viewing plotted http://www.onet.pl)
+ * - deal with to wide pages - when window layouting adds a horizontal
+ * scrollbar, we should treat it otherwise - either print
+ * horizontal or scale or, better, find a new layout.
+ */
+
+#include "utils/config.h"
+
+#include "desktop/save_pdf.h"
+
+#ifdef WITH_PDF_EXPORT
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hpdf.h>
+
+#include "content/hlcache.h"
+#include "utils/nsoption.h"
+#include "desktop/plotters.h"
+#include "desktop/print.h"
+#include "desktop/printer.h"
+#include "image/bitmap.h"
+#include "utils/log.h"
+#include "utils/utils.h"
+#include "utils/useragent.h"
+
+#include "font_haru.h"
+
+/* #define PDF_DEBUG */
+/* #define PDF_DEBUG_DUMPGRID */
+
+static bool pdf_plot_rectangle(int x0, int y0, int x1, int y1, const plot_style_t *style);
+static bool pdf_plot_line(int x0, int y0, int x1, int y1, const plot_style_t *pstyle);
+static bool pdf_plot_polygon(const int *p, unsigned int n, const plot_style_t *style);
+static bool pdf_plot_clip(const struct rect *clip);
+static bool pdf_plot_text(int x, int y, const char *text, size_t length,
+ const plot_font_style_t *fstyle);
+static bool pdf_plot_disc(int x, int y, int radius, const plot_style_t *style);
+static bool pdf_plot_arc(int x, int y, int radius, int angle1, int angle2,
+ const plot_style_t *style);
+static bool pdf_plot_bitmap_tile(int x, int y, int width, int height,
+ struct bitmap *bitmap, colour bg,
+ bitmap_flags_t flags);
+static bool pdf_plot_path(const float *p, unsigned int n, colour fill, float width,
+ colour c, const float transform[6]);
+
+static HPDF_Image pdf_extract_image(struct bitmap *bitmap);
+
+static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no,
+ void *user_data);
+
+#ifdef PDF_DEBUG_DUMPGRID
+static void pdf_plot_grid(int x_dist,int y_dist,unsigned int colour);
+#endif
+
+typedef enum {
+ DashPattern_eNone,
+ DashPattern_eDash,
+ DashPattern_eDotted
+} DashPattern_e;
+
+/* Wrapper routines to minimize gstate updates in the produced PDF file. */
+static void pdfw_gs_init(void);
+static void pdfw_gs_save(HPDF_Page page);
+static void pdfw_gs_restore(HPDF_Page page);
+static void pdfw_gs_fillcolour(HPDF_Page page, colour col);
+static void pdfw_gs_strokecolour(HPDF_Page page, colour col);
+static void pdfw_gs_linewidth(HPDF_Page page, float lineWidth);
+static void pdfw_gs_font(HPDF_Page page, HPDF_Font font, HPDF_REAL font_size);
+static void pdfw_gs_dash(HPDF_Page page, DashPattern_e dash);
+
+/**
+ * Our PDF gstate mirror which we use to minimize gstate updates
+ * in the PDF file.
+ */
+typedef struct {
+ colour fillColour; /**< Current fill colour. */
+ colour strokeColour; /**< Current stroke colour. */
+ float lineWidth; /**< Current line width. */
+ HPDF_Font font; /**< Current font. */
+ HPDF_REAL font_size; /**< Current font size. */
+ DashPattern_e dash; /**< Current dash state. */
+} PDFW_GState;
+
+static void apply_clip_and_mode(bool selectTextMode, colour fillCol,
+ colour strokeCol, float lineWidth, DashPattern_e dash);
+
+#define PDFW_MAX_GSTATES 4
+static PDFW_GState pdfw_gs[PDFW_MAX_GSTATES];
+static unsigned int pdfw_gs_level;
+
+static HPDF_Doc pdf_doc; /**< Current PDF document. */
+static HPDF_Page pdf_page; /**< Current page. */
+
+/*PDF Page size*/
+static HPDF_REAL page_height, page_width;
+
+static bool in_text_mode; /**< true if we're currently in text mode or not. */
+static bool clip_update_needed; /**< true if pdf_plot_clip was invoked for
+ current page and not yet synced with PDF output. */
+static int last_clip_x0, last_clip_y0, last_clip_x1, last_clip_y1;
+
+static const struct print_settings *settings;
+
+static const struct plotter_table pdf_plotters = {
+ .rectangle = pdf_plot_rectangle,
+ .line = pdf_plot_line,
+ .polygon = pdf_plot_polygon,
+ .clip = pdf_plot_clip,
+ .text = pdf_plot_text,
+ .disc = pdf_plot_disc,
+ .arc = pdf_plot_arc,
+ .bitmap = pdf_plot_bitmap_tile,
+ .path = pdf_plot_path,
+ .option_knockout = false,
+};
+
+const struct printer pdf_printer = {
+ &pdf_plotters,
+ pdf_begin,
+ pdf_next_page,
+ pdf_end
+};
+
+static char *owner_pass;
+static char *user_pass;
+
+bool pdf_plot_rectangle(int x0, int y0, int x1, int y1, const plot_style_t *pstyle)
+{
+ DashPattern_e dash;
+#ifdef PDF_DEBUG
+ LOG(("%d %d %d %d %f %X", x0, y0, x1, y1, page_height - y0, pstyle->fill_colour));
+#endif
+
+ if (pstyle->fill_type != PLOT_OP_TYPE_NONE) {
+
+ apply_clip_and_mode(false, pstyle->fill_colour, NS_TRANSPARENT, 0., DashPattern_eNone);
+
+ /* Normalize boundaries of the area - to prevent
+ overflows. It is needed only in a few functions,
+ where integers are subtracted. When the whole
+ browser window is meant min and max int values are
+ used what must be handled in paged output.
+ */
+ x0 = min(max(x0, 0), page_width);
+ y0 = min(max(y0, 0), page_height);
+ x1 = min(max(x1, 0), page_width);
+ y1 = min(max(y1, 0), page_height);
+
+ HPDF_Page_Rectangle(pdf_page, x0, page_height - y1, x1 - x0, y1 - y0);
+ HPDF_Page_Fill(pdf_page);
+
+ }
+
+ if (pstyle->stroke_type != PLOT_OP_TYPE_NONE) {
+
+ switch (pstyle->stroke_type) {
+ case PLOT_OP_TYPE_DOT:
+ dash = DashPattern_eDotted;
+ break;
+
+ case PLOT_OP_TYPE_DASH:
+ dash = DashPattern_eDash;
+ break;
+
+ default:
+ dash = DashPattern_eNone;
+ break;
+
+ }
+
+ apply_clip_and_mode(false,
+ NS_TRANSPARENT,
+ pstyle->stroke_colour,
+ pstyle->stroke_width,
+ dash);
+
+ HPDF_Page_Rectangle(pdf_page, x0, page_height - y0, x1 - x0, -(y1 - y0));
+ HPDF_Page_Stroke(pdf_page);
+ }
+
+ return true;
+}
+
+bool pdf_plot_line(int x0, int y0, int x1, int y1, const plot_style_t *pstyle)
+{
+ DashPattern_e dash;
+
+ switch (pstyle->stroke_type) {
+ case PLOT_OP_TYPE_DOT:
+ dash = DashPattern_eDotted;
+ break;
+
+ case PLOT_OP_TYPE_DASH:
+ dash = DashPattern_eDash;
+ break;
+
+ default:
+ dash = DashPattern_eNone;
+ break;
+
+ }
+
+ apply_clip_and_mode(false,
+ NS_TRANSPARENT,
+ pstyle->stroke_colour,
+ pstyle->stroke_width,
+ dash);
+
+ HPDF_Page_MoveTo(pdf_page, x0, page_height - y0);
+ HPDF_Page_LineTo(pdf_page, x1, page_height - y1);
+ HPDF_Page_Stroke(pdf_page);
+
+ return true;
+}
+
+bool pdf_plot_polygon(const int *p, unsigned int n, const plot_style_t *style)
+{
+ unsigned int i;
+#ifdef PDF_DEBUG
+ int pmaxx = p[0], pmaxy = p[1];
+ int pminx = p[0], pminy = p[1];
+ LOG(("."));
+#endif
+ if (n == 0)
+ return true;
+
+ apply_clip_and_mode(false, style->fill_colour, NS_TRANSPARENT, 0., DashPattern_eNone);
+
+ HPDF_Page_MoveTo(pdf_page, p[0], page_height - p[1]);
+ for (i = 1 ; i<n ; i++) {
+ HPDF_Page_LineTo(pdf_page, p[i*2], page_height - p[i*2+1]);
+#ifdef PDF_DEBUG
+ pmaxx = max(pmaxx, p[i*2]);
+ pmaxy = max(pmaxy, p[i*2+1]);
+ pminx = min(pminx, p[i*2]);
+ pminy = min(pminy, p[i*2+1]);
+#endif
+ }
+
+#ifdef PDF_DEBUG
+ LOG(("%d %d %d %d %f", pminx, pminy, pmaxx, pmaxy, page_height - pminy));
+#endif
+
+ HPDF_Page_Fill(pdf_page);
+
+ return true;
+}
+
+
+/**here the clip is only queried */
+bool pdf_plot_clip(const struct rect *clip)
+{
+#ifdef PDF_DEBUG
+ LOG(("%d %d %d %d", clip->x0, clip->y0, clip->x1, clip->y1));
+#endif
+
+ /*Normalize cllipping area - to prevent overflows.
+ See comment in pdf_plot_fill.
+ */
+ last_clip_x0 = min(max(clip->x0, 0), page_width);
+ last_clip_y0 = min(max(clip->y0, 0), page_height);
+ last_clip_x1 = min(max(clip->x1, 0), page_width);
+ last_clip_y1 = min(max(clip->y1, 0), page_height);
+
+ clip_update_needed = true;
+
+ return true;
+}
+
+bool pdf_plot_text(int x, int y, const char *text, size_t length,
+ const plot_font_style_t *fstyle)
+{
+#ifdef PDF_DEBUG
+ LOG((". %d %d %.*s", x, y, (int)length, text));
+#endif
+ char *word;
+ HPDF_Font pdf_font;
+ HPDF_REAL size;
+
+ if (length == 0)
+ return true;
+
+ apply_clip_and_mode(true, fstyle->foreground, NS_TRANSPARENT, 0.,
+ DashPattern_eNone);
+
+ haru_nsfont_apply_style(fstyle, pdf_doc, pdf_page, &pdf_font, &size);
+ pdfw_gs_font(pdf_page, pdf_font, size);
+
+ /* FIXME: UTF-8 to current font encoding needs to done. Or the font
+ * encoding needs to be UTF-8 or other Unicode encoding. */
+ word = (char *)malloc( sizeof(char) * (length+1) );
+ if (word == NULL)
+ return false;
+ memcpy(word, text, length);
+ word[length] = '\0';
+
+ HPDF_Page_TextOut (pdf_page, x, page_height - y, word);
+
+ free(word);
+
+ return true;
+}
+
+bool pdf_plot_disc(int x, int y, int radius, const plot_style_t *style)
+{
+#ifdef PDF_DEBUG
+ LOG(("."));
+#endif
+ if (style->fill_type != PLOT_OP_TYPE_NONE) {
+ apply_clip_and_mode(false,
+ style->fill_colour,
+ NS_TRANSPARENT,
+ 1., DashPattern_eNone);
+
+ HPDF_Page_Circle(pdf_page, x, page_height - y, radius);
+
+ HPDF_Page_Fill(pdf_page);
+ }
+
+ if (style->stroke_type != PLOT_OP_TYPE_NONE) {
+ /* FIXME: line width 1 is ok ? */
+ apply_clip_and_mode(false,
+ NS_TRANSPARENT,
+ style->stroke_colour,
+ 1., DashPattern_eNone);
+
+ HPDF_Page_Circle(pdf_page, x, page_height - y, radius);
+
+ HPDF_Page_Stroke(pdf_page);
+ }
+
+ return true;
+}
+
+bool pdf_plot_arc(int x, int y, int radius, int angle1, int angle2, const plot_style_t *style)
+{
+#ifdef PDF_DEBUG
+ LOG(("%d %d %d %d %d %X", x, y, radius, angle1, angle2, style->stroke_colour));
+#endif
+
+ /* FIXME: line width 1 is ok ? */
+ apply_clip_and_mode(false, NS_TRANSPARENT, style->fill_colour, 1., DashPattern_eNone);
+
+ /* Normalize angles */
+ angle1 %= 360;
+ angle2 %= 360;
+ if (angle1 > angle2)
+ angle1 -= 360;
+
+ HPDF_Page_Arc(pdf_page, x, page_height - y, radius, angle1, angle2);
+
+ HPDF_Page_Stroke(pdf_page);
+ return true;
+}
+
+
+bool pdf_plot_bitmap_tile(int x, int y, int width, int height,
+ struct bitmap *bitmap, colour bg,
+ bitmap_flags_t flags)
+{
+ HPDF_Image image;
+ HPDF_REAL current_x, current_y ;
+ HPDF_REAL max_width, max_height;
+
+#ifdef PDF_DEBUG
+ LOG(("%d %d %d %d %p 0x%x", x, y, width, height,
+ bitmap, bg));
+#endif
+ if (width == 0 || height == 0)
+ return true;
+
+ apply_clip_and_mode(false, NS_TRANSPARENT, NS_TRANSPARENT, 0., DashPattern_eNone);
+
+ image = pdf_extract_image(bitmap);
+ if (!image)
+ return false;
+
+ /*The position of the next tile*/
+ max_width = (flags & BITMAPF_REPEAT_X) ? page_width : width;
+ max_height = (flags & BITMAPF_REPEAT_Y) ? page_height : height;
+
+ for (current_y = 0; current_y < max_height; current_y += height)
+ for (current_x = 0; current_x < max_width; current_x += width)
+ HPDF_Page_DrawImage(pdf_page, image,
+ current_x + x,
+ page_height - current_y - y - height,
+ width, height);
+
+ return true;
+}
+
+HPDF_Image pdf_extract_image(struct bitmap *bitmap)
+{
+ HPDF_Image image = NULL;
+ hlcache_handle *content = NULL;
+
+ /* TODO - get content from bitmap pointer */
+
+ if (content) {
+ const char *source_data;
+ unsigned long source_size;
+
+ /*Not sure if I don't have to check if downloading has been
+ finished.
+ Other way - lock pdf plotting while fetching a website
+ */
+ source_data = content_get_source_data(content, &source_size);
+
+ switch(content_get_type(content)){
+ /*Handle "embeddable" types of images*/
+ case CONTENT_JPEG:
+ image = HPDF_LoadJpegImageFromMem(pdf_doc,
+ (const HPDF_BYTE *) source_data,
+ source_size);
+ break;
+
+ /*Disabled until HARU PNG support will be more stable.
+
+ case CONTENT_PNG:
+ image = HPDF_LoadPngImageFromMem(pdf_doc,
+ (const HPDF_BYTE *)content->source_data,
+ content->total_size);
+ break;*/
+ default:
+ break;
+ }
+ }
+
+ if (!image) {
+ HPDF_Image smask;
+ unsigned char *img_buffer, *rgb_buffer, *alpha_buffer;
+ int img_width, img_height, img_rowstride;
+ int i, j;
+
+ /*Handle pixmaps*/
+ img_buffer = bitmap_get_buffer(bitmap);
+ img_width = bitmap_get_width(bitmap);
+ img_height = bitmap_get_height(bitmap);
+ img_rowstride = bitmap_get_rowstride(bitmap);
+
+ rgb_buffer = (unsigned char *)malloc(3 * img_width * img_height);
+ alpha_buffer = (unsigned char *)malloc(img_width * img_height);
+ if (rgb_buffer == NULL || alpha_buffer == NULL) {
+ LOG(("Not enough memory to create RGB buffer"));
+ free(rgb_buffer);
+ free(alpha_buffer);
+ return NULL;
+ }
+
+ for (i = 0; i < img_height; i++)
+ for (j = 0; j < img_width; j++) {
+ rgb_buffer[((i * img_width) + j) * 3] =
+ img_buffer[(i * img_rowstride) + (j * 4)];
+
+ rgb_buffer[(((i * img_width) + j) * 3) + 1] =
+ img_buffer[(i * img_rowstride) + (j * 4) + 1];
+
+ rgb_buffer[(((i * img_width) + j) * 3) + 2] =
+ img_buffer[(i * img_rowstride) + (j * 4) + 2];
+
+ alpha_buffer[(i * img_width)+j] =
+ img_buffer[(i * img_rowstride) + (j * 4) + 3];
+ }
+
+ smask = HPDF_LoadRawImageFromMem(pdf_doc, alpha_buffer,
+ img_width, img_height,
+ HPDF_CS_DEVICE_GRAY, 8);
+
+ image = HPDF_LoadRawImageFromMem(pdf_doc, rgb_buffer,
+ img_width, img_height,
+ HPDF_CS_DEVICE_RGB, 8);
+
+ if (HPDF_Image_AddSMask(image, smask) != HPDF_OK)
+ image = NULL;
+
+ free(rgb_buffer);
+ free(alpha_buffer);
+ }
+
+ return image;
+}
+
+/**
+ * Enter/leave text mode and update PDF gstate for its clip, fill & stroke
+ * colour, line width and dash pattern parameters.
+ * \param selectTextMode true if text mode needs to be entered if required;
+ * false otherwise.
+ * \param fillCol Desired fill colour, use NS_TRANSPARENT if no update is
+ * required.
+ * \param strokeCol Desired stroke colour, use NS_TRANSPARENT if no update is
+ * required.
+ * \param lineWidth Desired line width. Only taken into account when strokeCol
+ * is different from NS_TRANSPARENT.
+ * \param dash Desired dash pattern. Only taken into account when strokeCol
+ * is different from NS_TRANSPARENT.
+ */
+static void apply_clip_and_mode(bool selectTextMode, colour fillCol,
+ colour strokeCol, float lineWidth, DashPattern_e dash)
+{
+ /* Leave text mode when
+ * 1) we're not setting text anymore
+ * 2) or we need to update the current clippath
+ * 3) or we need to update any fill/stroke colour, linewidth or dash.
+ * Note: the test on stroke parameters (stroke colour, line width and
+ * dash) is commented out as if these need updating we want to be
+ * outside the text mode anyway (i.e. selectTextMode is false).
+ */
+ if (in_text_mode && (!selectTextMode || clip_update_needed
+ || (fillCol != NS_TRANSPARENT
+ && fillCol != pdfw_gs[pdfw_gs_level].fillColour)
+ /* || (strokeCol != NS_TRANSPARENT
+ && (strokeCol != pdfw_gs[pdfw_gs_level].strokeColour
+ || lineWidth != pdfw_gs[pdfw_gs_level].lineWidth
+ || dash != pdfw_gs[pdfw_gs_level].dash)) */)) {
+ HPDF_Page_EndText(pdf_page);
+ in_text_mode = false;
+ }
+
+ if (clip_update_needed)
+ pdfw_gs_restore(pdf_page);
+
+ /* Update fill/stroke colour, linewidth and dash when needed. */
+ if (fillCol != NS_TRANSPARENT)
+ pdfw_gs_fillcolour(pdf_page, fillCol);
+ if (strokeCol != NS_TRANSPARENT) {
+ pdfw_gs_strokecolour(pdf_page, strokeCol);
+ pdfw_gs_linewidth(pdf_page, lineWidth);
+ pdfw_gs_dash(pdf_page, dash);
+ }
+
+ if (clip_update_needed) {
+ pdfw_gs_save(pdf_page);
+
+ HPDF_Page_Rectangle(pdf_page, last_clip_x0,
+ page_height - last_clip_y1,
+ last_clip_x1 - last_clip_x0,
+ last_clip_y1 - last_clip_y0);
+ HPDF_Page_Clip(pdf_page);
+ HPDF_Page_EndPath(pdf_page);
+
+ clip_update_needed = false;
+ }
+
+ if (selectTextMode && !in_text_mode) {
+ HPDF_Page_BeginText(pdf_page);
+ in_text_mode = true;
+ }
+}
+
+static inline float transform_x(const float transform[6], float x, float y)
+{
+ return transform[0] * x + transform[2] * y + transform[4];
+}
+
+static inline float transform_y(const float transform[6], float x, float y)
+{
+ return page_height
+ - (transform[1] * x + transform[3] * y + transform[5]);
+}
+
+bool pdf_plot_path(const float *p, unsigned int n, colour fill, float width,
+ colour c, const float transform[6])
+{
+ unsigned int i;
+ bool empty_path;
+
+#ifdef PDF_DEBUG
+ LOG(("."));
+#endif
+
+ if (n == 0)
+ return true;
+
+ if (c == NS_TRANSPARENT && fill == NS_TRANSPARENT)
+ return true;
+
+ if (p[0] != PLOTTER_PATH_MOVE)
+ return false;
+
+ apply_clip_and_mode(false, fill, c, width, DashPattern_eNone);
+
+ empty_path = true;
+ for (i = 0 ; i < n ; ) {
+ if (p[i] == PLOTTER_PATH_MOVE) {
+ HPDF_Page_MoveTo(pdf_page,
+ transform_x(transform, p[i+1], p[i+2]),
+ transform_y(transform, p[i+1], p[i+2]));
+ i+= 3;
+ } else if (p[i] == PLOTTER_PATH_CLOSE) {
+ if (!empty_path)
+ HPDF_Page_ClosePath(pdf_page);
+ i++;
+ } else if (p[i] == PLOTTER_PATH_LINE) {
+ HPDF_Page_LineTo(pdf_page,
+ transform_x(transform, p[i+1], p[i+2]),
+ transform_y(transform, p[i+1], p[i+2]));
+ i+=3;
+ empty_path = false;
+ } else if (p[i] == PLOTTER_PATH_BEZIER) {
+ HPDF_Page_CurveTo(pdf_page,
+ transform_x(transform, p[i+1], p[i+2]),
+ transform_y(transform, p[i+1], p[i+2]),
+ transform_x(transform, p[i+3], p[i+4]),
+ transform_y(transform, p[i+3], p[i+4]),
+ transform_x(transform, p[i+5], p[i+6]),
+ transform_y(transform, p[i+5], p[i+6]));
+ i += 7;
+ empty_path = false;
+ } else {
+ LOG(("bad path command %f", p[i]));
+ return false;
+ }
+ }
+
+ if (empty_path) {
+ HPDF_Page_EndPath(pdf_page);
+ return true;
+ }
+
+ if (fill != NS_TRANSPARENT) {
+ if (c != NS_TRANSPARENT)
+ HPDF_Page_FillStroke(pdf_page);
+ else
+ HPDF_Page_Fill(pdf_page);
+ }
+ else
+ HPDF_Page_Stroke(pdf_page);
+
+ return true;
+}
+
+/**
+ * Begin pdf plotting - initialize a new document
+ * \param path Output file path
+ * \param pg_width page width
+ * \param pg_height page height
+ */
+bool pdf_begin(struct print_settings *print_settings)
+{
+ pdfw_gs_init();
+
+ if (pdf_doc != NULL)
+ HPDF_Free(pdf_doc);
+ pdf_doc = HPDF_New(error_handler, NULL);
+ if (!pdf_doc) {
+ LOG(("Error creating pdf_doc"));
+ return false;
+ }
+
+ settings = print_settings;
+
+ page_width = settings->page_width -
+ FIXTOFLT(FSUB(settings->margins[MARGINLEFT],
+ settings->margins[MARGINRIGHT]));
+
+ page_height = settings->page_height -
+ FIXTOFLT(settings->margins[MARGINTOP]);
+
+
+#ifndef PDF_DEBUG
+ if (option_enable_PDF_compression)
+ HPDF_SetCompressionMode(pdf_doc, HPDF_COMP_ALL); /*Compression on*/
+#endif
+ HPDF_SetInfoAttr(pdf_doc, HPDF_INFO_CREATOR, user_agent_string());
+
+ pdf_page = NULL;
+
+#ifdef PDF_DEBUG
+ LOG(("pdf_begin finishes"));
+#endif
+ return true;
+}
+
+
+bool pdf_next_page(void)
+{
+#ifdef PDF_DEBUG
+ LOG(("pdf_next_page begins"));
+#endif
+ clip_update_needed = false;
+ if (pdf_page != NULL) {
+ apply_clip_and_mode(false, NS_TRANSPARENT, NS_TRANSPARENT, 0.,
+ DashPattern_eNone);
+ pdfw_gs_restore(pdf_page);
+ }
+
+#ifdef PDF_DEBUG_DUMPGRID
+ if (pdf_page != NULL) {
+ pdf_plot_grid(10, 10, 0xCCCCCC);
+ pdf_plot_grid(100, 100, 0xCCCCFF);
+ }
+#endif
+ pdf_page = HPDF_AddPage(pdf_doc);
+ if (pdf_page == NULL)
+ return false;
+
+ HPDF_Page_SetWidth (pdf_page, settings->page_width);
+ HPDF_Page_SetHeight(pdf_page, settings->page_height);
+
+ HPDF_Page_Concat(pdf_page, 1, 0, 0, 1,
+ FIXTOFLT(settings->margins[MARGINLEFT]), 0);
+
+ pdfw_gs_save(pdf_page);
+
+#ifdef PDF_DEBUG
+ LOG(("%f %f", page_width, page_height));
+#endif
+
+ return true;
+}
+
+
+void pdf_end(void)
+{
+#ifdef PDF_DEBUG
+ LOG(("pdf_end begins"));
+#endif
+ clip_update_needed = false;
+ if (pdf_page != NULL) {
+ apply_clip_and_mode(false, NS_TRANSPARENT, NS_TRANSPARENT, 0.,
+ DashPattern_eNone);
+ pdfw_gs_restore(pdf_page);
+ }
+
+#ifdef PDF_DEBUG_DUMPGRID
+ if (pdf_page != NULL) {
+ pdf_plot_grid(10, 10, 0xCCCCCC);
+ pdf_plot_grid(100, 100, 0xCCCCFF);
+ }
+#endif
+
+ assert(settings->output != NULL);
+
+ /*Encryption on*/
+ if (option_enable_PDF_password)
+ guit->browser->pdf_password(&owner_pass, &user_pass,
+ (void *)settings->output);
+ else
+ save_pdf(settings->output);
+#ifdef PDF_DEBUG
+ LOG(("pdf_end finishes"));
+#endif
+}
+
+/** saves the pdf with optional encryption */
+void save_pdf(const char *path)
+{
+ bool success = false;
+
+ if (option_enable_PDF_password && owner_pass != NULL ) {
+ HPDF_SetPassword(pdf_doc, owner_pass, user_pass);
+ HPDF_SetEncryptionMode(pdf_doc, HPDF_ENCRYPT_R3, 16);
+ free(owner_pass);
+ free(user_pass);
+ }
+
+ if (path != NULL) {
+ if (HPDF_SaveToFile(pdf_doc, path) != HPDF_OK)
+ remove(path);
+ else
+ success = true;
+ }
+
+ if (!success)
+ warn_user("Unable to save PDF file.", 0);
+
+ HPDF_Free(pdf_doc);
+ pdf_doc = NULL;
+}
+
+
+/**
+ * Haru error handler
+ * for debugging purposes - it immediately exits the program on the first error,
+ * as it would otherwise flood the user with all resulting complications,
+ * covering the most important error source.
+*/
+static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no,
+ void *user_data)
+{
+ LOG(("ERROR:\n\terror_no=%x\n\tdetail_no=%d\n", (HPDF_UINT)error_no,
+ (HPDF_UINT)detail_no));
+#ifdef PDF_DEBUG
+ exit(1);
+#endif
+}
+
+/**
+ * This function plots a grid - used for debug purposes to check if all
+ * elements' final coordinates are correct.
+*/
+#ifdef PDF_DEBUG_DUMPGRID
+void pdf_plot_grid(int x_dist, int y_dist, unsigned int colour)
+{
+ for (int i = x_dist ; i < page_width ; i += x_dist)
+ pdf_plot_line(i, 0, i, page_height, 1, colour, false, false);
+
+ for (int i = y_dist ; i < page_height ; i += x_dist)
+ pdf_plot_line(0, i, page_width, i, 1, colour, false, false);
+}
+#endif
+
+/**
+ * Initialize the gstate wrapper code.
+ */
+void pdfw_gs_init()
+{
+ pdfw_gs_level = 0;
+ pdfw_gs[0].fillColour = 0x00000000; /* Default PDF fill colour is black. */
+ pdfw_gs[0].strokeColour = 0x00000000; /* Default PDF stroke colour is black. */
+ pdfw_gs[0].lineWidth = 1.0; /* Default PDF line width is 1. */
+ pdfw_gs[0].font = NULL;
+ pdfw_gs[0].font_size = 0.;
+ pdfw_gs[0].dash = DashPattern_eNone; /* Default dash state is a solid line. */
+}
+
+/**
+ * Increase gstate level.
+ * \param page PDF page where the update needs to happen.
+ */
+void pdfw_gs_save(HPDF_Page page)
+{
+ if (pdfw_gs_level == PDFW_MAX_GSTATES)
+ abort();
+ pdfw_gs[pdfw_gs_level + 1] = pdfw_gs[pdfw_gs_level];
+ ++pdfw_gs_level;
+ HPDF_Page_GSave(page);
+}
+
+/**
+ * Decrease gstate level and restore the gstate to its value at last save
+ * operation.
+ * \param page PDF page where the update needs to happen.
+ */
+void pdfw_gs_restore(HPDF_Page page)
+{
+ if (pdfw_gs_level == 0)
+ abort();
+ --pdfw_gs_level;
+ HPDF_Page_GRestore(page);
+}
+
+#define RBYTE(x) (((x) & 0x0000FF) >> 0)
+#define GBYTE(x) (((x) & 0x00FF00) >> 8)
+#define BBYTE(x) (((x) & 0xFF0000) >> 16)
+#define R(x) (RBYTE(x) / 255.)
+#define G(x) (GBYTE(x) / 255.)
+#define B(x) (BBYTE(x) / 255.)
+
+/**
+ * Checks if given fill colour is already set in PDF gstate and if not,
+ * update the gstate accordingly.
+ * \param page PDF page where the update needs to happen.
+ * \param col Wanted fill colour.
+ */
+void pdfw_gs_fillcolour(HPDF_Page page, colour col)
+{
+ if (col == pdfw_gs[pdfw_gs_level].fillColour)
+ return;
+ pdfw_gs[pdfw_gs_level].fillColour = col;
+ if (RBYTE(col) == GBYTE(col) && GBYTE(col) == BBYTE(col))
+ HPDF_Page_SetGrayFill(pdf_page, R(col));
+ else
+ HPDF_Page_SetRGBFill(pdf_page, R(col), G(col), B(col));
+}
+
+/**
+ * Checks if given stroke colour is already set in PDF gstate and if not,
+ * update the gstate accordingly.
+ * \param page PDF page where the update needs to happen.
+ * \param col Wanted stroke colour.
+ */
+void pdfw_gs_strokecolour(HPDF_Page page, colour col)
+{
+ if (col == pdfw_gs[pdfw_gs_level].strokeColour)
+ return;
+ pdfw_gs[pdfw_gs_level].strokeColour = col;
+ if (RBYTE(col) == GBYTE(col) && GBYTE(col) == BBYTE(col))
+ HPDF_Page_SetGrayStroke(pdf_page, R(col));
+ else
+ HPDF_Page_SetRGBStroke(pdf_page, R(col), G(col), B(col));
+}
+
+/**
+ * Checks if given line width is already set in PDF gstate and if not, update
+ * the gstate accordingly.
+ * \param page PDF page where the update needs to happen.
+ * \param lineWidth Wanted line width.
+ */
+void pdfw_gs_linewidth(HPDF_Page page, float lineWidth)
+{
+ if (lineWidth == pdfw_gs[pdfw_gs_level].lineWidth)
+ return;
+ pdfw_gs[pdfw_gs_level].lineWidth = lineWidth;
+ HPDF_Page_SetLineWidth(page, lineWidth);
+}
+
+/**
+ * Checks if given font and font size is already set in PDF gstate and if not,
+ * update the gstate accordingly.
+ * \param page PDF page where the update needs to happen.
+ * \param font Wanted PDF font.
+ * \param font_size Wanted PDF font size.
+ */
+void pdfw_gs_font(HPDF_Page page, HPDF_Font font, HPDF_REAL font_size)
+{
+ if (font == pdfw_gs[pdfw_gs_level].font
+ && font_size == pdfw_gs[pdfw_gs_level].font_size)
+ return;
+ pdfw_gs[pdfw_gs_level].font = font;
+ pdfw_gs[pdfw_gs_level].font_size = font_size;
+ HPDF_Page_SetFontAndSize(page, font, font_size);
+}
+
+/**
+ * Checks if given dash pattern is already set in PDF gstate and if not,
+ * update the gstate accordingly.
+ * \param page PDF page where the update needs to happen.
+ * \param dash Wanted dash pattern.
+ */
+void pdfw_gs_dash(HPDF_Page page, DashPattern_e dash)
+{
+ if (dash == pdfw_gs[pdfw_gs_level].dash)
+ return;
+ pdfw_gs[pdfw_gs_level].dash = dash;
+ switch (dash) {
+ case DashPattern_eNone: {
+ HPDF_Page_SetDash(page, NULL, 0, 0);
+ break;
+ }
+ case DashPattern_eDash: {
+ const HPDF_UINT16 dash_ptn[] = {3};
+ HPDF_Page_SetDash(page, dash_ptn, 1, 1);
+ break;
+ }
+ case DashPattern_eDotted: {
+ const HPDF_UINT16 dash_ptn[] = {1};
+ HPDF_Page_SetDash(page, dash_ptn, 1, 1);
+ break;
+ }
+ }
+}
+
+#else
+void save_pdf(const char *path)
+{
+}
+#endif /* WITH_PDF_EXPORT */