gds-render/output-renderers/cairo-renderer.c

504 lines
14 KiB
C

/*
* GDSII-Converter
* Copyright (C) 2018 Mario Hüttel <mario.huettel@gmx.net>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/**
* @file cairo-renderer.c
* @brief Output renderer for Cairo PDF export
* @author Mario Hüttel <mario.huettel@gmx.net>
*/
/** @addtogroup Cairo-Renderer
* @{
*/
#include <math.h>
#include <stdlib.h>
#include <cairo.h>
#include <cairo-pdf.h>
#include <cairo-svg.h>
#include <glib/gi18n.h>
#include <gds-render/output-renderers/cairo-renderer.h>
#include <sys/wait.h>
#include <unistd.h>
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;
}
/** @} */