diff --git a/.indent.pro b/.indent.pro index b3c65827..500ca1d7 100644 --- a/.indent.pro +++ b/.indent.pro @@ -65,7 +65,6 @@ -T lxw_hash_table -T lxw_header_footer_options -T lxw_heading_pair --T lxw_image_meta -T lxw_image_options -T lxw_merged_range -T lxw_packager diff --git a/include/xlsxwriter/common.h b/include/xlsxwriter/common.h index 0b562873..01743a40 100644 --- a/include/xlsxwriter/common.h +++ b/include/xlsxwriter/common.h @@ -28,6 +28,7 @@ enum lxw_boolean { #define LXW_SHEETNAME_LEN 65 #define LXW_UINT32_T_LEN 11 /* Length of 4294967296\0. */ #define LXW_IGNORE 1 +#define FILENAME_LEN 128 #define LXW_ERROR(message) \ fprintf(stderr, "[ERROR][%s:%d]: " message "\n", __FILE__, __LINE__) diff --git a/include/xlsxwriter/content_types.h b/include/xlsxwriter/content_types.h index 67a5a59b..aea54719 100644 --- a/include/xlsxwriter/content_types.h +++ b/include/xlsxwriter/content_types.h @@ -45,7 +45,8 @@ void _ct_add_default(lxw_content_types *self, const char *key, const char *value); void _ct_add_override(lxw_content_types *self, const char *key, const char *value); -void _ct_add_worksheet_name(lxw_content_types *self, const char *str); +void _ct_add_worksheet_name(lxw_content_types *self, const char *name); +void _ct_add_drawing_name(lxw_content_types *self, const char *name); void _ct_add_shared_strings(lxw_content_types *self); void _ct_add_calc_chain(lxw_content_types *self); diff --git a/include/xlsxwriter/drawing.h b/include/xlsxwriter/drawing.h index f5d71696..11ea929e 100644 --- a/include/xlsxwriter/drawing.h +++ b/include/xlsxwriter/drawing.h @@ -86,6 +86,8 @@ lxw_drawing *_new_drawing(); void _free_drawing(lxw_drawing *drawing); void _drawing_assemble_xml_file(lxw_drawing *self); void _free_drawing_object(struct lxw_drawing_object *drawing_object); +void _add_drawing_object(lxw_drawing *drawing, + lxw_drawing_object *drawing_object); /* Declarations required for unit testing. */ #ifdef TESTING diff --git a/include/xlsxwriter/packager.h b/include/xlsxwriter/packager.h index 93064c9c..f6f05d35 100644 --- a/include/xlsxwriter/packager.h +++ b/include/xlsxwriter/packager.h @@ -24,7 +24,6 @@ #include "content_types.h" #include "relationships.h" -#define FILENAME_LEN 128 #define LXW_ZIP_BUFFER_SIZE (16384) /* @@ -41,6 +40,8 @@ typedef struct lxw_packager { char *filename; char *buffer; + uint16_t drawing_count; + } lxw_packager; diff --git a/include/xlsxwriter/workbook.h b/include/xlsxwriter/workbook.h index 59692348..4c3d1f25 100644 --- a/include/xlsxwriter/workbook.h +++ b/include/xlsxwriter/workbook.h @@ -200,6 +200,10 @@ typedef struct lxw_workbook { uint16_t fill_count; uint8_t optimize; + uint8_t has_png; + uint8_t has_jpg; + uint8_t has_bmp; + lxw_hash_table *used_xf_formats; } lxw_workbook; diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 3e1f28b0..ef0510f4 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -49,6 +49,7 @@ #include #include "shared_strings.h" +#include "drawing.h" #include "common.h" #include "format.h" #include "utility.h" @@ -465,11 +466,15 @@ typedef struct lxw_worksheet { lxw_col_t *vbreaks; struct lxw_rel_tuples *external_hyperlinks; + struct lxw_rel_tuples *external_drawing_links; + struct lxw_rel_tuples *drawing_links; struct lxw_panes panes; struct lxw_protection protection; + lxw_drawing *drawing; + STAILQ_ENTRY (lxw_worksheet) list_pointers; } lxw_worksheet; diff --git a/src/content_types.c b/src/content_types.c index 270e5095..c18ff4fe 100644 --- a/src/content_types.c +++ b/src/content_types.c @@ -284,15 +284,21 @@ mem_error: * Add the name of a worksheet to the ContentTypes overrides. */ void -_ct_add_worksheet_name(lxw_content_types *self, const char *str) +_ct_add_worksheet_name(lxw_content_types *self, const char *name) { - char name[MAX_ATTRIBUTE_LENGTH]; - lxw_snprintf(name, MAX_ATTRIBUTE_LENGTH, "/xl/worksheets/%s.xml", str); - _ct_add_override(self, name, LXW_APP_DOCUMENT "spreadsheetml.worksheet+xml"); } +/* + * Add the name of a drawing to the ContentTypes overrides. + */ +void +_ct_add_drawing_name(lxw_content_types *self, const char *name) +{ + _ct_add_override(self, name, LXW_APP_DOCUMENT "drawing+xml"); +} + /* * Add the sharedStrings link to the ContentTypes overrides. */ diff --git a/src/drawing.c b/src/drawing.c index 8a830cc9..30065d65 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -86,6 +86,16 @@ _free_drawing(lxw_drawing *drawing) free(drawing); } +/* + * Add a drawing object to the drawing collection. + */ +void +_add_drawing_object(lxw_drawing *drawing, lxw_drawing_object *drawing_object) +{ + STAILQ_INSERT_TAIL(drawing->drawing_objects, drawing_object, + list_pointers); +} + /***************************************************************************** * * XML functions. diff --git a/src/packager.c b/src/packager.c index d4a53b3a..2dfffdc9 100644 --- a/src/packager.c +++ b/src/packager.c @@ -169,6 +169,71 @@ _write_worksheet_files(lxw_packager *self) return 0; } +/* + * Write the /xl/media/image?.xml files. + */ +STATIC uint8_t +_write_image_files(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_worksheet *worksheet; + lxw_image_options *image; + + char filename[FILENAME_LEN] = { 0 }; + uint16_t index = 1; + + STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + + if (STAILQ_EMPTY(worksheet->images)) + continue; + + STAILQ_FOREACH(image, worksheet->images, list_pointers) { + + lxw_snprintf(filename, FILENAME_LEN, + "xl/media/image%d.png", index++); + + rewind(image->stream); + + _add_file_to_zip(self, image->stream, filename); + + fclose(image->stream); + } + } + + return 0; +} + +/* + * Write the drawing files. + */ +STATIC uint8_t +_write_drawing_files(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_worksheet *worksheet; + lxw_drawing *drawing; + char filename[FILENAME_LEN] = { 0 }; + uint16_t index = 1; + + STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + drawing = worksheet->drawing; + + if (drawing) { + lxw_snprintf(filename, FILENAME_LEN, + "xl/drawings/drawing%d.xml", index++); + + drawing->file = lxw_tmpfile(); + _drawing_assemble_xml_file(drawing); + _add_file_to_zip(self, drawing->file, filename); + fclose(drawing->file); + + self->drawing_count++; + } + } + + return 0; +} + /* * Write the sharedStrings.xml file. */ @@ -340,14 +405,24 @@ _write_content_types_file(lxw_packager *self) lxw_content_types *content_types = _new_content_types(); lxw_workbook *workbook = self->workbook; lxw_worksheet *worksheet; - char sheetname[FILENAME_LEN] = { 0 }; + char filename[MAX_ATTRIBUTE_LENGTH] = { 0 }; uint16_t index = 1; content_types->file = lxw_tmpfile(); + if (workbook->has_png) + _ct_add_default(content_types, "png", "image/png"); + STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - lxw_snprintf(sheetname, FILENAME_LEN, "sheet%d", index++); - _ct_add_worksheet_name(content_types, sheetname); + lxw_snprintf(filename, FILENAME_LEN, + "/xl/worksheets/sheet%d.xml", index++); + _ct_add_worksheet_name(content_types, filename); + } + + for (index = 1; index <= self->drawing_count; index++) { + lxw_snprintf(filename, FILENAME_LEN, + "/xl/drawings/drawing%d.xml", index++); + _ct_add_drawing_name(content_types, filename); } if (workbook->sst->string_count) @@ -409,7 +484,7 @@ STATIC uint8_t _write_worksheet_rels_file(lxw_packager *self) { lxw_relationships *rels; - lxw_rel_tuple *hlink; + lxw_rel_tuple *rel; lxw_workbook *workbook = self->workbook; lxw_worksheet *worksheet; char sheetname[FILENAME_LEN] = { 0 }; @@ -417,16 +492,21 @@ _write_worksheet_rels_file(lxw_packager *self) STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - if (STAILQ_EMPTY(worksheet->external_hyperlinks)) + if (STAILQ_EMPTY(worksheet->external_hyperlinks) && + STAILQ_EMPTY(worksheet->external_drawing_links)) continue; rels = _new_relationships(); rels->file = lxw_tmpfile(); - STAILQ_FOREACH(hlink, worksheet->external_hyperlinks, list_pointers) { - _add_worksheet_relationship(rels, hlink->type, hlink->target, - hlink->target_mode); + STAILQ_FOREACH(rel, worksheet->external_hyperlinks, list_pointers) { + _add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + } + STAILQ_FOREACH(rel, worksheet->external_drawing_links, list_pointers) { + _add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); } lxw_snprintf(sheetname, FILENAME_LEN, @@ -443,6 +523,48 @@ _write_worksheet_rels_file(lxw_packager *self) return 0; } +/* + * Write the drawing .rels files for worksheets that contain charts or + * drawings. + */ +STATIC uint8_t +_write_drawing_rels_file(lxw_packager *self) +{ + lxw_relationships *rels; + lxw_rel_tuple *rel; + lxw_workbook *workbook = self->workbook; + lxw_worksheet *worksheet; + char sheetname[FILENAME_LEN] = { 0 }; + uint16_t index = 1; + + STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + + if (STAILQ_EMPTY(worksheet->drawing_links)) + continue; + + rels = _new_relationships(); + rels->file = lxw_tmpfile(); + + STAILQ_FOREACH(rel, worksheet->drawing_links, list_pointers) { + _add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + + } + + lxw_snprintf(sheetname, FILENAME_LEN, + "xl/drawings/_rels/drawing%d.xml.rels", index++); + + _relationships_assemble_xml_file(rels); + + _add_file_to_zip(self, rels->file, sheetname); + + fclose(rels->file); + _free_relationships(rels); + } + + return 0; +} + /* * Write the _rels/.rels xml file. */ @@ -543,6 +665,7 @@ _create_package(lxw_packager *self) _write_worksheet_files(self); _write_workbook_file(self); + _write_drawing_files(self); _write_shared_strings_file(self); _write_app_file(self); _write_core_file(self); @@ -551,6 +674,8 @@ _create_package(lxw_packager *self) _write_content_types_file(self); _write_workbook_rels_file(self); _write_worksheet_rels_file(self); + _write_drawing_rels_file(self); + _write_image_files(self); _write_root_rels_file(self); zipClose(self->zipfile, NULL); diff --git a/src/workbook.c b/src/workbook.c index 3d6ee1b1..9515be5a 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -691,11 +691,13 @@ _prepare_drawings(lxw_workbook *self) if (_get_image_properties(image_options) != 0) continue; + if (image_options->image_type == IMAGE_PNG) + self->has_png = LXW_TRUE; + image_ref_id++; - /*sheet->_prepare_image(index, image_ref_id, drawing_id, width, - height, name, type); - */ + _worksheet_prepare_image(worksheet, image_ref_id, drawing_id, + image_options); } } diff --git a/src/worksheet.c b/src/worksheet.c index a96f5fc7..8ea63ee8 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -12,7 +12,6 @@ #include "xlsxwriter/xmlwriter.h" #include "xlsxwriter/worksheet.h" #include "xlsxwriter/format.h" -#include "xlsxwriter/drawing.h" #include "xlsxwriter/utility.h" #include "xlsxwriter/relationships.h" @@ -86,12 +85,21 @@ _new_worksheet(lxw_worksheet_init_data *init_data) worksheet->external_hyperlinks = calloc(1, sizeof(struct lxw_rel_tuples)); GOTO_LABEL_ON_MEM_ERROR(worksheet->external_hyperlinks, mem_error); + worksheet->external_drawing_links = + calloc(1, sizeof(struct lxw_rel_tuples)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->external_drawing_links, mem_error); + + worksheet->drawing_links = calloc(1, sizeof(struct lxw_rel_tuples)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_links, mem_error); + RB_INIT(worksheet->table); RB_INIT(worksheet->hyperlinks); STAILQ_INIT(worksheet->merged_ranges); STAILQ_INIT(worksheet->images); STAILQ_INIT(worksheet->selections); STAILQ_INIT(worksheet->external_hyperlinks); + STAILQ_INIT(worksheet->external_drawing_links); + STAILQ_INIT(worksheet->drawing_links); if (init_data && init_data->optimize) { worksheet->optimize_tmpfile = lxw_tmpfile(); @@ -225,7 +233,7 @@ _free_worksheet(lxw_worksheet *worksheet) lxw_merged_range *merged_range; lxw_image_options *image; lxw_selection *selection; - lxw_rel_tuple *external_hyperlink; + lxw_rel_tuple *relationship; if (!worksheet) return; @@ -295,17 +303,37 @@ _free_worksheet(lxw_worksheet *worksheet) free(worksheet->selections); } + /* TODO. Add function for freeing the relationship lists. */ while (!STAILQ_EMPTY(worksheet->external_hyperlinks)) { - external_hyperlink = STAILQ_FIRST(worksheet->external_hyperlinks); + relationship = STAILQ_FIRST(worksheet->external_hyperlinks); STAILQ_REMOVE_HEAD(worksheet->external_hyperlinks, list_pointers); - free(external_hyperlink->type); - free(external_hyperlink->target); - free(external_hyperlink->target_mode); - free(external_hyperlink); + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); } - free(worksheet->external_hyperlinks); + while (!STAILQ_EMPTY(worksheet->external_drawing_links)) { + relationship = STAILQ_FIRST(worksheet->external_drawing_links); + STAILQ_REMOVE_HEAD(worksheet->external_drawing_links, list_pointers); + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + free(worksheet->external_drawing_links); + + while (!STAILQ_EMPTY(worksheet->drawing_links)) { + relationship = STAILQ_FIRST(worksheet->drawing_links); + STAILQ_REMOVE_HEAD(worksheet->drawing_links, list_pointers); + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + free(worksheet->drawing_links); + if (worksheet->array) { for (col = 0; col < LXW_COL_MAX; col++) { _free_cell(worksheet->array[col]); @@ -316,6 +344,9 @@ _free_worksheet(lxw_worksheet *worksheet) if (worksheet->optimize_row) free(worksheet->optimize_row); + if (worksheet->drawing) + _free_drawing(worksheet->drawing); + free(worksheet->name); free(worksheet->quoted_name); @@ -1700,12 +1731,39 @@ _worksheet_prepare_image(lxw_worksheet *self, lxw_image_options *image) { lxw_drawing_object *drawing_object; + lxw_rel_tuple *relationship; double width; double height; + char filename[FILENAME_LEN]; + + if (!self->drawing) { + self->drawing = _new_drawing(); + self->drawing->embedded = LXW_TRUE; + RETURN_VOID_ON_MEM_ERROR(self->drawing); + + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/drawing"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, FILENAME_LEN, "../drawings/drawing%d.xml", + drawing_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + STAILQ_INSERT_TAIL(self->external_drawing_links, relationship, + list_pointers); + } drawing_object = calloc(1, sizeof(lxw_drawing_object)); RETURN_VOID_ON_MEM_ERROR(drawing_object); + drawing_object->anchor_type = LXW_ANCHOR_TYPE_IMAGE; + drawing_object->edit_as = LXW_ANCHOR_EDIT_AS_ONE_CELL; + drawing_object->description = lxw_strdup(image->short_name); + /* Scale to user scale. */ width = image->width * image->x_scale; height = image->height * image->y_scale; @@ -1721,12 +1779,33 @@ _worksheet_prepare_image(lxw_worksheet *self, _worksheet_position_object_emus(self, image, drawing_object); /* Convert from pixels to emus. */ - image->width *= 9525; - image->height *= 9525; + drawing_object->width = image->width * 9525; + drawing_object->height = image->height * 9525; - image_ref_id++; - drawing_id++; + _add_drawing_object(self->drawing, drawing_object); + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/image"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../media/image%d.png", image_ref_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers); + + return; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } } /***************************************************************************** diff --git a/test/functional/src/images/red.png b/test/functional/src/images/red.png new file mode 100644 index 00000000..867a2f12 Binary files /dev/null and b/test/functional/src/images/red.png differ diff --git a/test/functional/src/test_image01.c b/test/functional/src/test_image01.c new file mode 100644 index 00000000..992d41c8 --- /dev/null +++ b/test/functional/src/test_image01.c @@ -0,0 +1,20 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_image01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_insert_image(worksheet, CELL("E9"), "images/red.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/test_image.py b/test/functional/test_image.py new file mode 100644 index 00000000..1fcca1a3 --- /dev/null +++ b/test/functional/test_image.py @@ -0,0 +1,18 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2015, John McNamara, jmcnamara@cpan.org +# + +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_image01(self): + self.run_exe_test('test_image01') + diff --git a/test/functional/xlsx_files/image01.xlsx b/test/functional/xlsx_files/image01.xlsx new file mode 100644 index 00000000..b7314d72 Binary files /dev/null and b/test/functional/xlsx_files/image01.xlsx differ diff --git a/test/unit/content_types/test_content_types.c b/test/unit/content_types/test_content_types.c index d6ab328d..41dcaf01 100644 --- a/test/unit/content_types/test_content_types.c +++ b/test/unit/content_types/test_content_types.c @@ -37,7 +37,7 @@ CTEST(content_types, content_types01) { lxw_content_types *content_types = _new_content_types(); content_types->file = testfile; - _ct_add_worksheet_name(content_types, "sheet1"); + _ct_add_worksheet_name(content_types, "/xl/worksheets/sheet1.xml"); _ct_add_default(content_types, "jpeg", "image/jpeg"); _ct_add_shared_strings(content_types); _ct_add_calc_chain(content_types); diff --git a/test/unit/drawing/test_drawing_image.c b/test/unit/drawing/test_drawing_image.c index e8bfd329..1a0d8970 100644 --- a/test/unit/drawing/test_drawing_image.c +++ b/test/unit/drawing/test_drawing_image.c @@ -5,7 +5,7 @@ * */ -#include ; +#include #include "../ctest.h" #include "../helper.h" @@ -88,7 +88,7 @@ CTEST(drawing, drawing_image01) { drawing_object->width = 1142857; drawing_object->height = 1142857; - STAILQ_INSERT_TAIL(drawing->drawing_objects, drawing_object, list_pointers); + _add_drawing_object(drawing, drawing_object); _drawing_assemble_xml_file(drawing);