diff --git a/dev/release/fix_example_docs.pl b/dev/release/fix_example_docs.pl
index c64ad06c..09d3d369 100644
--- a/dev/release/fix_example_docs.pl
+++ b/dev/release/fix_example_docs.pl
@@ -25,6 +25,7 @@ my @examples = (
[ 'utf8.c', 'A example of some UTF-8 text' ],
[ 'constant_memory.c', 'Write a large file with constant memory usage' ],
[ 'merge1.c', 'Create a merged range of cells' ],
+ [ 'headers_footers.c', 'Example of adding worksheet headers/footers' ],
[ 'defined_name.c', 'Example of how to create defined names' ],
);
diff --git a/docs/images/headers_footers.png b/docs/images/headers_footers.png
new file mode 100644
index 00000000..d6ad0490
Binary files /dev/null and b/docs/images/headers_footers.png differ
diff --git a/docs/src/examples.dox b/docs/src/examples.dox
index 7200c4b1..74df0eb2 100644
--- a/docs/src/examples.dox
+++ b/docs/src/examples.dox
@@ -104,10 +104,17 @@ Next example: @ref merge1.c
@example merge1.c
Example of merging cells in a worksheet.
-Next example: @ref defined_name.c
+Next example: @ref headers_footers.c
@image html merge1.png
+@example headers_footers.c
+Example of adding worksheet headers and footers to worksheets.
+
+Next example: @ref defined_name.c
+@image html headers_footers.png
+
+
@example defined_name.c
Example of how to create defined names (named ranges) using libxlsxwriter.
diff --git a/examples/headers_footers.c b/examples/headers_footers.c
new file mode 100644
index 00000000..187e4556
--- /dev/null
+++ b/examples/headers_footers.c
@@ -0,0 +1,101 @@
+/*
+ * This program shows several examples of how to set up headers and
+ * footers with libxlsxwriter.
+ *
+ * The control characters used in the header/footer strings are:
+ *
+ * Control Category Description
+ * ======= ======== ===========
+ * &L Justification Left
+ * &C Center
+ * &R Right
+ * &P Information Page number
+ * &N Total number of pages
+ * &D Date
+ * &T Time
+ * &F File name
+ * &A Worksheet name
+ * &fontsize Font Font size
+ * &"font,style" Font name and style
+ * &U Single underline
+ * &E Double underline
+ * &S Strikethrough
+ * &X Superscript
+ * &Y Subscript
+ * &[Picture] Images Image placeholder
+ * &G Same as &[Picture]
+ * && Miscellaneous Literal ampersand &
+ *
+ * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+
+int main() {
+
+ lxw_workbook *workbook = new_workbook("headers_footers.xlsx");
+
+ char preview[] = "Select Print Preview to see the header and footer";
+
+ /* A simple example to start */
+ lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, "Simple");
+ char header1[] = "&CHere is some centred text.";
+ char footer1[] = "&LHere is some left aligned text.";
+
+ worksheet_set_header(worksheet1, header1);
+ worksheet_set_footer(worksheet1, footer1);
+
+ worksheet_set_column(worksheet1, 0, 0, 50, NULL, NULL);
+ worksheet_write_string(worksheet1, 0, 0, preview, NULL);
+
+
+ /* This is an example of some of the header/footer variables. */
+ lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, "Variables");
+ char header2[] = "&LPage &P of &N" "&CFilename: &F" "&RSheetname: &A";
+ char footer2[] = "&LCurrent date: &D" "&RCurrent time: &T";
+
+ worksheet_set_header(worksheet2, header2);
+ worksheet_set_footer(worksheet2, footer2);
+
+ worksheet_set_column(worksheet2, 0, 0, 50, NULL, NULL);
+ worksheet_write_string(worksheet2, 0, 0, preview, NULL);
+
+
+ /* This example shows how to use more than one font. */
+ lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, "Mixed fonts");
+ char header3[] = "&C&\"Courier New,Bold\"Hello &\"Arial,Italic\"World";
+ char footer3[] = "&C&\"Symbol\"e&\"Arial\" = mc&X2";
+
+ worksheet_set_header(worksheet3, header3);
+ worksheet_set_footer(worksheet3, footer3);
+
+ worksheet_set_column(worksheet3, 0, 0, 50, NULL, NULL);
+ worksheet_write_string(worksheet3, 0, 0, preview, NULL);
+
+
+ /*Example of line wrapping. */
+ lxw_worksheet *worksheet4 = workbook_add_worksheet(workbook, "Word wrap");
+ char header4[] = "&CHeading 1\nHeading 2";
+
+ worksheet_set_header(worksheet4, header4);
+
+ worksheet_set_column(worksheet4, 0, 0, 50, NULL, NULL);
+ worksheet_write_string(worksheet4, 0, 0, preview, NULL);
+
+
+ /* Example of inserting a literal ampersand & */
+ lxw_worksheet *worksheet5 = workbook_add_worksheet(workbook, "Ampersand");
+ char header5[] = "&CCuriouser && Curiouser - Attorneys at Law";
+
+ worksheet_set_header(worksheet5, header5);
+
+ worksheet_set_column(worksheet5, 0, 0, 50, NULL, NULL);
+ worksheet_write_string(worksheet5, 0, 0, preview, NULL);
+
+
+ workbook_close(workbook);
+
+ return 0;
+}
diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h
index 54094ecc..d1953476 100644
--- a/include/xlsxwriter/worksheet.h
+++ b/include/xlsxwriter/worksheet.h
@@ -54,6 +54,7 @@
#include "utility.h"
#define LXW_COL_META_MAX 128
+#define LXW_HEADER_FOOTER_MAX 255
/** Default column width in Excel */
#define LXW_DEF_COL_WIDTH 8.43
@@ -134,6 +135,18 @@ typedef struct lxw_merged_range {
STAILQ_ENTRY (lxw_merged_range) list_pointers;
} lxw_merged_range;
+/**
+ * @brief Header and footer options.
+ *
+ * Optional parameters used in the worksheet_set_header_opt() and
+ * worksheet_set_footer_opt() functions.
+ *
+ */
+typedef struct lxw_header_footer_options {
+ /** Header or footer margin in inches. Excel default is 0.3. */
+ double margin;
+} lxw_header_footer_options;
+
/**
* @brief Struct to represent an Excel worksheet.
*
@@ -195,6 +208,10 @@ typedef struct lxw_worksheet {
double margin_header;
double margin_footer;
+ uint8_t header_footer_changed;
+ char header[LXW_HEADER_FOOTER_MAX];
+ char footer[LXW_HEADER_FOOTER_MAX];
+
uint16_t merged_range_count;
STAILQ_ENTRY (lxw_worksheet) list_pointers;
@@ -938,6 +955,242 @@ void worksheet_set_paper(lxw_worksheet *worksheet, uint8_t paper_type);
void worksheet_set_margins(lxw_worksheet *worksheet, double left,
double right, double top, double bottom);
+/**
+ * @brief Set the printed page header caption.
+ *
+ * @param worksheet Pointer to a lxw_worksheet instance to be updated.
+ * @param string The header string.
+ *
+ * @return 0 for success, non-zero on error.
+ *
+ * Headers and footers are generated using a string which is a combination of
+ * plain text and control characters.
+ *
+ * The available control character are:
+ *
+ *
+ * | Control | Category | Description |
+ * | --------------- | ------------- | --------------------- |
+ * | `&L` | Justification | Left |
+ * | `&C` | | Center |
+ * | `&R` | | Right |
+ * | `&P` | Information | Page number |
+ * | `&N` | | Total number of pages |
+ * | `&D` | | Date |
+ * | `&T` | | Time |
+ * | `&F` | | File name |
+ * | `&A` | | Worksheet name |
+ * | `&Z` | | Workbook path |
+ * | `&fontsize` | Font | Font size |
+ * | `&"font,style"` | | Font name and style |
+ * | `&U` | | Single underline |
+ * | `&E` | | Double underline |
+ * | `&S` | | Strikethrough |
+ * | `&X` | | Superscript |
+ * | `&Y` | | Subscript |
+ *
+ *
+ * Text in headers and footers can be justified (aligned) to the left, center
+ * and right by prefixing the text with the control characters `&L`, `&C` and
+ * `&R`.
+ *
+ * For example (with ASCII art representation of the results):
+ *
+ * @code
+ * worksheet_set_header(worksheet, "&LHello");
+ *
+ * ---------------------------------------------------------------
+ * | |
+ * | Hello |
+ * | |
+ *
+ *
+ * worksheet_set_header(worksheet, "&CHello");
+ *
+ * ---------------------------------------------------------------
+ * | |
+ * | Hello |
+ * | |
+ *
+ *
+ * worksheet_set_header(worksheet, "&RHello");
+ *
+ * ---------------------------------------------------------------
+ * | |
+ * | Hello |
+ * | |
+ *
+ *
+ * @endcode
+ *
+ * For simple text, if you do not specify any justification the text will be
+ * centred. However, you must prefix the text with `&C` if you specify a font
+ * name or any other formatting:
+ *
+ * @code
+ * worksheet_set_header(worksheet, "Hello");
+ *
+ * ---------------------------------------------------------------
+ * | |
+ * | Hello |
+ * | |
+ *
+ * @endcode
+ *
+ * You can have text in each of the justification regions:
+ *
+ * @code
+ * worksheet_set_header(worksheet, "&LCiao&CBello&RCielo");
+ *
+ * ---------------------------------------------------------------
+ * | |
+ * | Ciao Bello Cielo |
+ * | |
+ *
+ * @endcode
+ *
+ * The information control characters act as variables that Excel will update
+ * as the workbook or worksheet changes. Times and dates are in the users
+ * default format:
+ *
+ * @code
+ * worksheet_set_header(worksheet, "&CPage &P of &N");
+ *
+ * ---------------------------------------------------------------
+ * | |
+ * | Page 1 of 6 |
+ * | |
+ *
+ * worksheet_set_header(worksheet, "&CUpdated at &T");
+ *
+ * ---------------------------------------------------------------
+ * | |
+ * | Updated at 12:30 PM |
+ * | |
+ *
+ * @endcode
+ *
+ * You can specify the font size of a section of the text by prefixing it with
+ * the control character `&n` where `n` is the font size:
+ *
+ * @code
+ * worksheet_set_header(worksheet1, "&C&30Hello Big");
+ * worksheet_set_header(worksheet2, "&C&10Hello Small");
+ *
+ * @endcode
+ *
+ * You can specify the font of a section of the text by prefixing it with the
+ * control sequence `&"font,style"` where `fontname` is a font name such as
+ * Windows font descriptions: "Regular", "Italic", "Bold" or "Bold Italic":
+ * "Courier New" or "Times New Roman" and `style` is one of the standard
+ *
+ * @code
+ * worksheet_set_header(worksheet1, "&C&\"Courier New,Italic\"Hello");
+ * worksheet_set_header(worksheet2, "&C&\"Courier New,Bold Italic\"Hello");
+ * worksheet_set_header(worksheet3, "&C&\"Times New Roman,Regular\"Hello");
+ *
+ * @endcode
+ *
+ * It is possible to combine all of these features together to create
+ * sophisticated headers and footers. As an aid to setting up complicated
+ * headers and footers you can record a page set-up as a macro in Excel and
+ * look at the format strings that VBA produces. Remember however that VBA
+ * uses two double quotes `""` to indicate a single double quote. For the last
+ * example above the equivalent VBA code looks like this:
+ *
+ * @code
+ * .LeftHeader = ""
+ * .CenterHeader = "&""Times New Roman,Regular""Hello"
+ * .RightHeader = ""
+ *
+ * @endcode
+ *
+ * Alternatively you can inspect the header and footer strings in an Excel
+ * file by unzipping it and grepping the XML sub-files. The following shows
+ * how to do that using libxml's xmllint to format the XML for clarity:
+ *
+ * @code
+ *
+ * $ unzip myfile.xlsm -d myfile
+ * $ xmllint --format `find myfile -name "*.xml" | xargs` \
+ * | egrep "Header|Footer"
+ *
+ *
+ * &L&P
+ *
+ *
+ * @endcode
+ *
+ * Note that in this case you need to unescape the Html. In the above example
+ * the header string would be `&L&P`.
+ *
+ * To include a single literal ampersand `&` in a header or footer you should
+ * use a double ampersand `&&`:
+ *
+ * @code
+ * worksheet_set_header(worksheet, "&CCuriouser && Curiouser - Attorneys at Law");
+ * @endcode
+ *
+ * Note, the header or footer string must be less than 255 characters. Strings
+ * longer than this will not be written.
+ *
+ */
+uint8_t worksheet_set_header(lxw_worksheet *worksheet, char *string);
+
+/**
+ * @brief Set the printed page footer caption.
+ *
+ * @param worksheet Pointer to a lxw_worksheet instance to be updated.
+ * @param string The footer string.
+ *
+ * @return 0 for success, non-zero on error.
+ *
+ * The syntax of this function is the same as worksheet_set_header().
+ *
+ */
+uint8_t worksheet_set_footer(lxw_worksheet *worksheet, char *string);
+
+/**
+ * @brief Set the printed page header caption with additional options.
+ *
+ * @param worksheet Pointer to a lxw_worksheet instance to be updated.
+ * @param string The header string.
+ * @param options Header options.
+ *
+ * @return 0 for success, non-zero on error.
+ *
+ * The syntax of this function is the same as worksheet_set_header() with an
+ * additional parameter to specify options for the header.
+ *
+ * Currently, the only available option is the header margin:
+ *
+ * @code
+ *
+ * lxw_header_footer_options header_options = { 0.2 };
+ *
+ * worksheet_set_header_opt(worksheet, "Some text", &header_options);
+ *
+ * @endcode
+ *
+ */
+uint8_t worksheet_set_header_opt(lxw_worksheet *worksheet, char *string,
+ lxw_header_footer_options * options);
+
+/**
+ * @brief Set the printed page footer caption with additional options.
+ *
+ * @param worksheet Pointer to a lxw_worksheet instance to be updated.
+ * @param string The footer string.
+ * @param options Footer options.
+ *
+ * @return 0 for success, non-zero on error.
+ *
+ * The syntax of this function is the same as worksheet_set_header_opt().
+ *
+ */
+uint8_t worksheet_set_footer_opt(lxw_worksheet *worksheet, char *string,
+ lxw_header_footer_options * options);
+
/**
* @brief Set the order in which pages are printed.
*
@@ -991,6 +1244,10 @@ STATIC void _write_merge_cell(lxw_worksheet *worksheet,
lxw_merged_range * merged_range);
STATIC void _write_merge_cells(lxw_worksheet *worksheet);
+STATIC void _worksheet_write_odd_header(lxw_worksheet *worksheet);
+STATIC void _worksheet_write_odd_footer(lxw_worksheet *worksheet);
+STATIC void _worksheet_write_header_footer(lxw_worksheet *worksheet);
+
#endif /* TESTING */
/* *INDENT-OFF* */
diff --git a/src/worksheet.c b/src/worksheet.c
index 69df42a8..9f5078d5 100644
--- a/src/worksheet.c
+++ b/src/worksheet.c
@@ -1235,6 +1235,44 @@ _write_merge_cells(lxw_worksheet *self)
}
}
+/*
+ * Write the element.
+ */
+STATIC void
+_worksheet_write_odd_header(lxw_worksheet *self)
+{
+ _xml_data_element(self->file, "oddHeader", self->header, NULL);
+}
+
+/*
+ * Write the element.
+ */
+STATIC void
+_worksheet_write_odd_footer(lxw_worksheet *self)
+{
+ _xml_data_element(self->file, "oddFooter", self->footer, NULL);
+}
+
+/*
+ * Write the element.
+ */
+STATIC void
+_worksheet_write_header_footer(lxw_worksheet *self)
+{
+ if (!self->header_footer_changed)
+ return;
+
+ _xml_start_tag(self->file, "headerFooter", NULL);
+
+ if (self->header[0] != '\0')
+ _worksheet_write_odd_header(self);
+
+ if (self->footer[0] != '\0')
+ _worksheet_write_odd_footer(self);
+
+ _xml_end_tag(self->file, "headerFooter");
+}
+
/*
* Check that row and col are within the allowed Excel range and store max
* and min values for use in other methods/elements.
@@ -1316,6 +1354,9 @@ _worksheet_assemble_xml_file(lxw_worksheet *self)
/* Write the worksheet page setup. */
_worksheet_write_page_setup(self);
+ /* Write the headerFooter element. */
+ _worksheet_write_header_footer(self);
+
/* Close the worksheet tag. */
_xml_end_tag(self->file, "worksheet");
}
@@ -1830,3 +1871,69 @@ worksheet_set_margins(lxw_worksheet *self, double left, double right,
if (bottom >= 0)
self->margin_bottom = bottom;
}
+
+/*
+ * Set the page header caption and options.
+ */
+uint8_t
+worksheet_set_header_opt(lxw_worksheet *self, char *string,
+ lxw_header_footer_options * options)
+{
+ if (options) {
+ if (options->margin > 0)
+ self->margin_header = options->margin;
+ }
+
+ if (!string)
+ return 1;
+
+ if (strlen(string) >= LXW_HEADER_FOOTER_MAX)
+ return 1;
+
+ strcpy(self->header, string);
+ self->header_footer_changed = 1;
+
+ return 0;
+}
+
+/*
+ * Set the page footer caption and options.
+ */
+uint8_t
+worksheet_set_footer_opt(lxw_worksheet *self, char *string,
+ lxw_header_footer_options * options)
+{
+ if (options) {
+ if (options->margin > 0)
+ self->margin_footer = options->margin;
+ }
+
+ if (!string)
+ return 1;
+
+ if (strlen(string) >= LXW_HEADER_FOOTER_MAX)
+ return 1;
+
+ strcpy(self->footer, string);
+ self->header_footer_changed = 1;
+
+ return 0;
+}
+
+/*
+ * Set the page header caption.
+ */
+uint8_t
+worksheet_set_header(lxw_worksheet *self, char *string)
+{
+ return worksheet_set_header_opt(self, string, NULL);
+}
+
+/*
+ * Set the page footer caption.
+ */
+uint8_t
+worksheet_set_footer(lxw_worksheet *self, char *string)
+{
+ return worksheet_set_footer_opt(self, string, NULL);
+}
diff --git a/test/unit/worksheet/test_worksheet_write_header_footer.c b/test/unit/worksheet/test_worksheet_write_header_footer.c
new file mode 100644
index 00000000..2c4c2173
--- /dev/null
+++ b/test/unit/worksheet/test_worksheet_write_header_footer.c
@@ -0,0 +1,108 @@
+/*
+ * Tests for the lib_xlsx_writer library.
+ *
+ * Copyright 2014, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "../ctest.h"
+#include "../helper.h"
+
+#include "xlsxwriter/worksheet.h"
+
+// Test the header and footer functions.
+CTEST(worksheet, write_odd_header) {
+
+ char* got;
+ char exp[] = "Page &P of &N";
+ FILE* testfile = tmpfile();
+
+ lxw_worksheet *worksheet = _new_worksheet(NULL);
+ worksheet->file = testfile;
+
+ worksheet_set_header(worksheet, "Page &P of &N");
+
+ _worksheet_write_odd_header(worksheet);
+
+ RUN_XLSX_STREQ(exp, got);
+
+ _free_worksheet(worksheet);
+}
+
+// Test the header and footer functions.
+CTEST(worksheet, write_odd_footer) {
+
+ char* got;
+ char exp[] = "&F";
+ FILE* testfile = tmpfile();
+
+ lxw_worksheet *worksheet = _new_worksheet(NULL);
+ worksheet->file = testfile;
+
+ worksheet_set_footer(worksheet, "&F");
+
+ _worksheet_write_odd_footer(worksheet);
+
+ RUN_XLSX_STREQ(exp, got);
+
+ _free_worksheet(worksheet);
+}
+
+
+// Test the header and footer functions.
+CTEST(worksheet, _worksheet_write_header_footer1) {
+
+ char* got;
+ char exp[] = "Page &P of &N";
+ FILE* testfile = tmpfile();
+
+ lxw_worksheet *worksheet = _new_worksheet(NULL);
+ worksheet->file = testfile;
+
+ worksheet_set_header(worksheet, "Page &P of &N");
+
+ _worksheet_write_header_footer(worksheet);
+
+ RUN_XLSX_STREQ(exp, got);
+
+ _free_worksheet(worksheet);
+}
+
+// Test the header and footer functions.
+CTEST(worksheet, _worksheet_write_header_footer2) {
+
+ char* got;
+ char exp[] = "&F";
+ FILE* testfile = tmpfile();
+
+ lxw_worksheet *worksheet = _new_worksheet(NULL);
+ worksheet->file = testfile;
+
+ worksheet_set_footer(worksheet, "&F");
+
+ _worksheet_write_header_footer(worksheet);
+
+ RUN_XLSX_STREQ(exp, got);
+
+ _free_worksheet(worksheet);
+}
+
+// Test the header and footer functions.
+CTEST(worksheet, _worksheet_write_header_footer3) {
+
+ char* got;
+ char exp[] = "Page &P of &N&F";
+ FILE* testfile = tmpfile();
+
+ lxw_worksheet *worksheet = _new_worksheet(NULL);
+ worksheet->file = testfile;
+
+ worksheet_set_header(worksheet, "Page &P of &N");
+ worksheet_set_footer(worksheet, "&F");
+
+ _worksheet_write_header_footer(worksheet);
+
+ RUN_XLSX_STREQ(exp, got);
+
+ _free_worksheet(worksheet);
+}
diff --git a/test/unit/worksheet/test_worksheet_write_page_margin.c b/test/unit/worksheet/test_worksheet_write_page_margin.c
index 25728b02..4e374621 100644
--- a/test/unit/worksheet/test_worksheet_write_page_margin.c
+++ b/test/unit/worksheet/test_worksheet_write_page_margin.c
@@ -116,3 +116,26 @@ CTEST(worksheet, write_page_margin06) {
_free_worksheet(worksheet);
}
+
+/* Test the _write_page_margins() method. */
+CTEST(worksheet, write_page_margin07) {
+ char* got;
+ char exp[] = "";
+ FILE* testfile = tmpfile();
+ lxw_header_footer_options header_options = {0.2};
+ lxw_header_footer_options footer_options = {0.4};
+
+ lxw_worksheet *worksheet = _new_worksheet(NULL);
+ worksheet->file = testfile;
+
+ worksheet_set_header_opt(worksheet, "", &header_options);
+ worksheet_set_footer_opt(worksheet, "", &footer_options);
+
+ _worksheet_write_page_margins(worksheet);
+
+ RUN_XLSX_STREQ(exp, got);
+
+ _free_worksheet(worksheet);
+}
+