From d204d6d555ac8e79da330a93e96fb070c8ee8f8c Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Sun, 26 Oct 2014 23:49:18 +0000 Subject: Clean up saving as PDF This removes the global PDF_Password interface that had completely bitrotted and moves it into the standard misc operation table with a sensible defualt implementation. This also cleans up and fixes the gtk frontend implementation of the pdf password callback so it compiles. Finally this moves the implementation to a sensible place alongside the other exporters reducing target makefile complexity futher. --- desktop/save_pdf.c | 990 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 990 insertions(+) create mode 100644 desktop/save_pdf.c (limited to 'desktop/save_pdf.c') 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 + * Copyright 2009 John Tytgat + * + * 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 . + */ + + /** \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 +#include +#include + +#include + +#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 ; ix0, 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 */ -- cgit v1.2.3