/* * 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 "utils/errors.h" #include "desktop/save_pdf.h" #ifdef WITH_PDF_EXPORT #include #include #include #include #include "utils/log.h" #include "utils/utils.h" #include "utils/useragent.h" #include "content/hlcache.h" #include "utils/nsoption.h" #include "netsurf/bitmap.h" #include "netsurf/plotters.h" #include "desktop/print.h" #include "desktop/printer.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->misc->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 */ nserror save_pdf(const char *path) { nserror res = NSERROR_OK; 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); res = NSERROR_SAVE_FAILED; } } HPDF_Free(pdf_doc); pdf_doc = NULL; return res; } /** * 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 nserror save_pdf(const char *path) { return NSERROR_NOT_IMPLEMENTED; } #endif /* WITH_PDF_EXPORT */