/* * GDSII-Converter * Copyright (C) 2018 Mario Hüttel * * This file is part of GDSII-Converter. * * GDSII-Converter is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * GDSII-Converter 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 GDSII-Converter. If not, see . */ /** * @file cairo-renderer.c * @brief Output renderer for Cairo PDF export * @author Mario Hüttel */ /** @addtogroup Cairo-Renderer * @{ */ #include #include #include #include #include #include #include #include #include struct _CairoRenderer { GdsOutputRenderer parent; gboolean svg; /**< @brief TRUE: SVG output, FALSE: PDF output */ }; G_DEFINE_TYPE(CairoRenderer, cairo_renderer, GDS_RENDER_TYPE_OUTPUT_RENDERER) /** * @brief The cairo_layer struct * Each rendered layer is represented by this struct. */ struct cairo_layer { cairo_t *cr; /**< @brief cairo context for layer*/ cairo_surface_t *rec; /**< @brief Recording surface to hold the layer */ struct layer_info *linfo; /**< @brief Reference to layer information */ }; /** * @brief Revert the last transformation on all layers * @param layers Pointer to #cairo_layer structures */ static void revert_inherited_transform(struct cairo_layer *layers) { int i; for (i = 0; i < MAX_LAYERS; i++) { if (layers[i].cr == NULL) continue; cairo_restore(layers[i].cr); } } /** * @brief Applies transformation to all layers * @param layers Array of layers * @param origin Origin translation * @param magnification Scaling * @param flipping Mirror image on x-axis before rotating * @param rotation Rotation in degrees * @param scale Scale the image down by. Only used for sclaing origin coordinates. Not applied to layer. */ static void apply_inherited_transform_to_all_layers(struct cairo_layer *layers, const struct gds_point *origin, double magnification, gboolean flipping, double rotation, double scale) { int i; cairo_t *temp_layer_cr; for (i = 0; i < MAX_LAYERS; i++) { temp_layer_cr = layers[i].cr; if (temp_layer_cr == NULL) continue; /* Save the state and apply transformation */ cairo_save(temp_layer_cr); cairo_translate(temp_layer_cr, (double)origin->x/scale, (double)origin->y/scale); cairo_rotate(temp_layer_cr, M_PI*rotation/180.0); cairo_scale(temp_layer_cr, magnification, (flipping == TRUE ? -magnification : magnification)); } } /** * @brief render_cell Render a cell with its sub-cells * @param cell Cell to render * @param layers Cell will be rendered into these layers * @param scale sclae image down by this factor */ static void render_cell(struct gds_cell *cell, struct cairo_layer *layers, double scale) { GList *instance_list; struct gds_cell *temp_cell; struct gds_cell_instance *cell_instance; GList *gfx_list; struct gds_graphics *gfx; GList *vertex_list; struct gds_point *vertex; cairo_t *cr; /* Render child cells */ for (instance_list = cell->child_cells; instance_list != NULL; instance_list = instance_list->next) { cell_instance = (struct gds_cell_instance *)instance_list->data; temp_cell = cell_instance->cell_ref; if (temp_cell != NULL) { apply_inherited_transform_to_all_layers(layers, &cell_instance->origin, cell_instance->magnification, cell_instance->flipped, cell_instance->angle, scale); render_cell(temp_cell, layers, scale); revert_inherited_transform(layers); } } /* Render graphics */ for (gfx_list = cell->graphic_objs; gfx_list != NULL; gfx_list = gfx_list->next) { gfx = (struct gds_graphics *)gfx_list->data; /* Get layer renderer */ if (gfx->layer >= MAX_LAYERS) continue; cr = layers[gfx->layer].cr; if (cr == NULL) continue; /* Apply settings */ cairo_set_line_width(cr, (gfx->width_absolute ? gfx->width_absolute/scale : 1)); switch (gfx->path_render_type) { case PATH_FLUSH: cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT); break; case PATH_ROUNDED: cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); break; case PATH_SQUARED: cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); break; } /* Add vertices */ for (vertex_list = gfx->vertices; vertex_list != NULL; vertex_list = vertex_list->next) { vertex = (struct gds_point *)vertex_list->data; /* If first point -> move to, else line to */ if (vertex_list->prev == NULL) cairo_move_to(cr, vertex->x/scale, vertex->y/scale); else cairo_line_to(cr, vertex->x/scale, vertex->y/scale); } /* Create graphics object */ switch (gfx->gfx_type) { case GRAPHIC_PATH: cairo_stroke(cr); break; case GRAPHIC_BOX: /* Expected fallthrough */ case GRAPHIC_POLYGON: cairo_set_line_width(cr, 0.1/scale); cairo_close_path(cr); cairo_stroke_preserve(cr); // Prevent graphic glitches cairo_fill(cr); break; } } /* for gfx list */ } /** * @brief Read a line from a file descriptor * * In case of a broken pipe / closed writing end, it will terminate * * @param fd File descriptor to read from * @param buff Buffer to write data in * @param buff_size Buffer size * @return length of read data */ static int read_line_from_fd(int fd, char *buff, size_t buff_size) { ssize_t cnt; char c; unsigned int buff_cnt = 0; while ((cnt = read(fd, &c, 1)) == 1) { if (buff_cnt < (buff_size-1)) { buff[buff_cnt++] = c; if (c == '\n') break; } else { break; } } buff[buff_cnt] = 0; return (int)buff_cnt; } /** * @brief Render \p cell to a PDF file specified by \p pdf_file * @param renderer The current renderer this function is running from * @param cell Toplevel cell to @ref Cairo-Renderer * @param layer_infos List of layer information. Specifies color and layer stacking * @param pdf_file PDF output file. Set to NULL if no PDF file has to be generated * @param svg_file SVG output file. Set to NULL if no SVG file has to be generated * @param scale Scale the output image down by \p scale * @return Error */ static int cairo_renderer_render_cell_to_vector_file(GdsOutputRenderer *renderer, struct gds_cell *cell, GList *layer_infos, const char *pdf_file, const char *svg_file, double scale) { cairo_surface_t *pdf_surface = NULL, *svg_surface = NULL; cairo_t *pdf_cr = NULL, *svg_cr = NULL; struct layer_info *linfo; struct cairo_layer *layers; struct cairo_layer *lay; GList *info_list; int i; double rec_x0, rec_y0, rec_width, rec_height; double xmin = INT32_MAX, xmax = INT32_MIN, ymin = INT32_MAX, ymax = INT32_MIN; pid_t process_id; int comm_pipe[2]; char receive_message[200]; if (pdf_file == NULL && svg_file == NULL) { /* No output specified */ return -1; } /* Generate communication pipe for status updates */ if (pipe(comm_pipe) == -1) return -2; /* Fork to a new child process. This ensures the memory leaks (see issue #16) in Cairo don't * brick everything. * * And by the way: This now bricks all Windows compatibility. Deal with it. */ process_id = fork(); //process_id = -1; if (process_id < 0) { /* This should not happen */ fprintf(stderr, _("Fatal error: Cairo Renderer: Could not spawn child process!")); exit(-2); } else if (process_id > 0) { /* Woohoo... Successfully dumped the shitty code to an unknowing victim */ goto ret_parent; } /* Close stdin and (stdout and stderr may live on) */ close(0); //close(1); close(comm_pipe[0]); layers = (struct cairo_layer *)calloc(MAX_LAYERS, sizeof(struct cairo_layer)); /* Clear layers */ for (i = 0; i < MAX_LAYERS; i++) { layers[i].cr = NULL; layers[i].rec = NULL; } /* Create recording surface for each layer */ for (info_list = layer_infos; info_list != NULL; info_list = g_list_next(info_list)) { linfo = (struct layer_info *)info_list->data; if (linfo->layer < MAX_LAYERS) { /* Layer shall not be rendered */ if (!linfo->render) continue; lay = &(layers[(unsigned int)linfo->layer]); lay->linfo = linfo; lay->rec = cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL); lay->cr = cairo_create(layers[(unsigned int)linfo->layer].rec); cairo_scale(lay->cr, 1, -1); // Fix coordinate system cairo_set_source_rgb(lay->cr, linfo->color.red, linfo->color.green, linfo->color.blue); } else { printf("Layer number (%d) too high!\n", linfo->layer); goto ret_clear_layers; } } dprintf(comm_pipe[1], "Rendering layers\n"); render_cell(cell, layers, scale); /* get size of image and top left coordinate */ for (info_list = layer_infos; info_list != NULL; info_list = g_list_next(info_list)) { linfo = (struct layer_info *)info_list->data; if (linfo->layer >= MAX_LAYERS) { printf(_("Layer number too high / outside of spec.\n")); continue; } if (!linfo->render) continue; /* Print size */ cairo_recording_surface_ink_extents(layers[linfo->layer].rec, &rec_x0, &rec_y0, &rec_width, &rec_height); dprintf(comm_pipe[1], _("Size of layer %d%s%s%s: <%lf x %lf> @ (%lf | %lf)\n"), linfo->layer, (linfo->name && linfo->name[0] ? " (" : ""), (linfo->name && linfo->name[0] ? linfo->name : ""), (linfo->name && linfo->name[0] ? ")" : ""), rec_width, rec_height, rec_x0, rec_y0); /* update bounding box */ xmin = MIN(xmin, rec_x0); xmax = MAX(xmax, rec_x0); ymin = MIN(ymin, rec_y0); ymax = MAX(ymax, rec_y0); xmin = MIN(xmin, rec_x0+rec_width); xmax = MAX(xmax, rec_x0+rec_width); ymin = MIN(ymin, rec_y0+rec_height); ymax = MAX(ymax, rec_y0+rec_height); } /* printf("Cell bounding box: (%lf | %lf) -- (%lf | %lf)\n", xmin, ymin, xmax, ymax); */ if (pdf_file) { pdf_surface = cairo_pdf_surface_create(pdf_file, xmax-xmin, ymax-ymin); pdf_cr = cairo_create(pdf_surface); } if (svg_file) { svg_surface = cairo_svg_surface_create(svg_file, xmax-xmin, ymax-ymin); svg_cr = cairo_create(svg_surface); } /* Write layers to PDF */ for (info_list = layer_infos; info_list != NULL; info_list = g_list_next(info_list)) { linfo = (struct layer_info *)info_list->data; dprintf(comm_pipe[1], _("Exporting layer %d to file\n"), linfo->layer); if (linfo->layer >= MAX_LAYERS) { printf(_("Layer outside of spec.\n")); continue; } if (!linfo->render) continue; if (pdf_file && pdf_cr) { cairo_set_source_surface(pdf_cr, layers[linfo->layer].rec, -xmin, -ymin); cairo_paint_with_alpha(pdf_cr, linfo->color.alpha); } if (svg_file && svg_cr) { cairo_set_source_surface(svg_cr, layers[linfo->layer].rec, -xmin, -ymin); cairo_paint_with_alpha(svg_cr, linfo->color.alpha); } } if (pdf_file) { cairo_show_page(pdf_cr); cairo_destroy(pdf_cr); cairo_surface_destroy(pdf_surface); } if (svg_file) { cairo_show_page(svg_cr); cairo_destroy(svg_cr); cairo_surface_destroy(svg_surface); } ret_clear_layers: for (i = 0; i < MAX_LAYERS; i++) { lay = &layers[i]; if (lay->cr) { cairo_destroy(lay->cr); cairo_surface_destroy(lay->rec); } } free(layers); printf(_("Cairo export finished. It might still be buggy!\n")); /* Suspend child process */ exit(0); ret_parent: close(comm_pipe[1]); while (read_line_from_fd(comm_pipe[0], receive_message, sizeof(receive_message)) > 0) { /* Strip \n from string and replace with ' ' */ for (i = 0; receive_message[i] != '\0'; i++) { if (receive_message[i] == '\n') receive_message[i] = ' '; } /* Update asyc progress*/ gds_output_renderer_update_async_progress(renderer, receive_message); } waitpid(process_id, NULL, 0); close(comm_pipe[0]); return 0; } static void cairo_renderer_init(CairoRenderer *self) { /* PDF default */ self->svg = FALSE; } static int cairo_renderer_render_output(GdsOutputRenderer *renderer, struct gds_cell *cell, double scale) { CairoRenderer *c_renderer = GDS_RENDER_CAIRO_RENDERER(renderer); const char *pdf_file = NULL; const char *svg_file = NULL; LayerSettings *settings; GList *layer_infos = NULL; const char *output_file; int ret; if (!c_renderer) return -2000; output_file = gds_output_renderer_get_output_file(renderer); settings = gds_output_renderer_get_and_ref_layer_settings(renderer); /* Set layer info list. In case of failure it remains NULL */ if (settings) layer_infos = layer_settings_get_layer_info_list(settings); if (c_renderer->svg == TRUE) svg_file = output_file; else pdf_file = output_file; gds_output_renderer_update_async_progress(renderer, _("Rendering Cairo Output...")); ret = cairo_renderer_render_cell_to_vector_file(renderer, cell, layer_infos, pdf_file, svg_file, scale); if (settings) g_object_unref(settings); return ret; } static void cairo_renderer_class_init(CairoRendererClass *klass) { GdsOutputRendererClass *renderer_class = GDS_RENDER_OUTPUT_RENDERER_CLASS(klass); renderer_class->render_output = cairo_renderer_render_output; } CairoRenderer *cairo_renderer_new_pdf() { CairoRenderer *renderer; renderer = GDS_RENDER_CAIRO_RENDERER(g_object_new(GDS_RENDER_TYPE_CAIRO_RENDERER, NULL)); renderer->svg = FALSE; return renderer; } CairoRenderer *cairo_renderer_new_svg() { CairoRenderer *renderer; renderer = GDS_RENDER_CAIRO_RENDERER(g_object_new(GDS_RENDER_TYPE_CAIRO_RENDERER, NULL)); renderer->svg = TRUE; return renderer; } /** @} */