diff --git a/examples/headers_footers.py b/examples/headers_footers.py index 3006860b4..ca4dfd4b2 100644 --- a/examples/headers_footers.py +++ b/examples/headers_footers.py @@ -127,3 +127,25 @@ worksheet6.write("A1", preview) workbook.close() + + +###################################################################### +# +# Example of inserting a different first page header/footer. +# +worksheet7 = workbook.add_worksheet("Different first page") +header7 = "&CCentered first page header" +footer7 = "&LLeft first page footer" + "&RRight first page footer" + +worksheet7.set_first_header(header7) +worksheet7.set_first_footer(footer7) + +worksheet7.set_header("&CCentered header") +worksheet7.set_footer("&C&IPage &P") + +worksheet7.set_column("A:A", 50) + +worksheet7.set_column("A:A", 50) +worksheet7.write("A1", preview) + +workbook.close() diff --git a/xlsxwriter/workbook.py b/xlsxwriter/workbook.py index 4fb27dcec..5e1790c53 100644 --- a/xlsxwriter/workbook.py +++ b/xlsxwriter/workbook.py @@ -1161,6 +1161,8 @@ def _prepare_drawings(self): header_image_count = len(sheet.header_images) footer_image_count = len(sheet.footer_images) + first_header_image_count = len(sheet.first_header_images) + first_footer_image_count = len(sheet.first_footer_images) has_background = sheet.background_image has_drawing = False @@ -1170,6 +1172,8 @@ def _prepare_drawings(self): or shape_count or header_image_count or footer_image_count + or first_header_image_count + or first_footer_image_count or has_background ): continue @@ -1324,6 +1328,78 @@ def _prepare_drawings(self): digest, ) + # Prepare the first page header images. + for index in range(first_header_image_count): + filename = sheet.first_header_images[index][0] + image_data = sheet.first_header_images[index][1] + position = sheet.first_header_images[index][2] + + ( + image_type, + width, + height, + name, + x_dpi, + y_dpi, + digest, + ) = self._get_image_properties(filename, image_data) + + if digest in header_image_ids: + ref_id = header_image_ids[digest] + else: + image_ref_id += 1 + ref_id = image_ref_id + header_image_ids[digest] = image_ref_id + self.images.append([filename, image_type, image_data]) + + sheet._prepare_header_image( + ref_id, + width, + height, + name, + image_type, + position, + x_dpi, + y_dpi, + digest, + ) + + # Prepare the first page footer images. + for index in range(first_footer_image_count): + filename = sheet.first_footer_images[index][0] + image_data = sheet.first_footer_images[index][1] + position = sheet.first_footer_images[index][2] + + ( + image_type, + width, + height, + name, + x_dpi, + y_dpi, + digest, + ) = self._get_image_properties(filename, image_data) + + if digest in header_image_ids: + ref_id = header_image_ids[digest] + else: + image_ref_id += 1 + ref_id = image_ref_id + header_image_ids[digest] = image_ref_id + self.images.append([filename, image_type, image_data]) + + sheet._prepare_header_image( + ref_id, + width, + height, + name, + image_type, + position, + x_dpi, + y_dpi, + digest, + ) + if has_drawing: drawing = sheet.drawing self.drawings.append(drawing) diff --git a/xlsxwriter/worksheet.py b/xlsxwriter/worksheet.py index eb2bc2cdd..b6ad11260 100644 --- a/xlsxwriter/worksheet.py +++ b/xlsxwriter/worksheet.py @@ -266,6 +266,14 @@ def __init__(self): self.footer_images = [] self.header_images_list = [] + self.first_header = '' + self.first_footer = '' + self.first_header_footer_aligns = True + self.first_header_footer_scales = True + self.first_header_images = [] + self.first_footer_images = [] + self.first_header_images_list = [] + self.margin_left = 0.7 self.margin_right = 0.7 self.margin_top = 0.75 @@ -329,6 +337,7 @@ def __init__(self): self.has_vml = False self.has_header_vml = False + self.has_first_header_vml = False self.has_comments = False self.comments = defaultdict(dict) self.comments_list = [] @@ -4150,6 +4159,83 @@ def set_header(self, header="", options=None, margin=None): if image_count: self.has_header_vml = True + def set_first_header(self, header="", options=None, margin=None): + """ + Set the first page header caption and optional margin. + + Args: + header: Header string. + margin: Header margin. + options: Header options, mainly for images. + + Returns: + Nothing. + + """ + header_orig = header + header = header.replace("&[Picture]", "&G") + + if len(header) > 255: + warn("Header string cannot be longer than Excel's limit of 255 characters") + return + + if options is not None: + # For backward compatibility allow options to be the margin. + if not isinstance(options, dict): + options = {"margin": options} + else: + options = {} + + # Copy the user defined options so they aren't modified. + options = options.copy() + + # For backward compatibility. + if margin is not None: + options["margin"] = margin + + # Reset the list in case the function is called more than once. + self.first_header_images = [] + + if options.get("image_left"): + self.first_header_images.append( + [options.get("image_left"), options.get("image_data_left"), "LH"] + ) + + if options.get("image_center"): + self.first_header_images.append( + [options.get("image_center"), options.get("image_data_center"), "CH"] + ) + + if options.get("image_right"): + self.first_header_images.append( + [options.get("image_right"), options.get("image_data_right"), "RH"] + ) + + placeholder_count = header.count("&G") + image_count = len(self.first_header_images) + + if placeholder_count != image_count: + warn( + "Number of header images (%s) doesn't match placeholder " + "count (%s) in string: %s" + % (image_count, placeholder_count, header_orig) + ) + self.first_header_images = [] + return + + if "align_with_margins" in options: + self.first_header_footer_aligns = options["align_with_margins"] + + if "scale_with_doc" in options: + self.first_header_footer_scales = options["scale_with_doc"] + + self.first_header = header + self.margin_first_header = options.get("margin", 0.3) + self.header_footer_changed = True + + if image_count: + self.has_first_header_vml = True + def set_footer(self, footer="", options=None, margin=None): """ Set the page footer caption and optional margin. @@ -4227,6 +4313,83 @@ def set_footer(self, footer="", options=None, margin=None): if image_count: self.has_header_vml = True + def set_first_footer(self, footer="", options=None, margin=None): + """ + Set the first page footer caption and optional margin. + + Args: + footer: Footer string. + margin: Footer margin. + options: Footer options, mainly for images. + + Returns: + Nothing. + + """ + footer_orig = footer + footer = footer.replace("&[Picture]", "&G") + + if len(footer) > 255: + warn("Footer string cannot be longer than Excel's limit of 255 characters") + return + + if options is not None: + # For backward compatibility allow options to be the margin. + if not isinstance(options, dict): + options = {"margin": options} + else: + options = {} + + # Copy the user defined options so they aren't modified. + options = options.copy() + + # For backward compatibility. + if margin is not None: + options["margin"] = margin + + # Reset the list in case the function is called more than once. + self.first_footer_images = [] + + if options.get("image_left"): + self.first_footer_images.append( + [options.get("image_left"), options.get("image_data_left"), "LF"] + ) + + if options.get("image_center"): + self.first_footer_images.append( + [options.get("image_center"), options.get("image_data_center"), "CF"] + ) + + if options.get("image_right"): + self.first_footer_images.append( + [options.get("image_right"), options.get("image_data_right"), "RF"] + ) + + placeholder_count = footer.count("&G") + image_count = len(self.first_footer_images) + + if placeholder_count != image_count: + warn( + "Number of footer images (%s) doesn't match placeholder " + "count (%s) in string: %s" + % (image_count, placeholder_count, footer_orig) + ) + self.first_footer_images = [] + return + + if "align_with_margins" in options: + self.first_header_footer_aligns = options["align_with_margins"] + + if "scale_with_doc" in options: + self.first_header_footer_scales = options["scale_with_doc"] + + self.first_footer = footer + self.first_margin_footer = options.get("margin", 0.3) + self.header_footer_changed = True + + if image_count: + self.has_first_header_vml = True + def repeat_rows(self, first_row, last_row=None): """ Set the rows to repeat at the top of each printed page. @@ -6505,12 +6668,19 @@ def _write_header_footer(self): if not self.header_footer_aligns: attributes.append(("alignWithMargins", 0)) + if self.first_header or self.first_footer: + attributes.append(('differentFirst', 1)) + if self.header_footer_changed: self._xml_start_tag("headerFooter", attributes) if self.header: self._write_odd_header() if self.footer: self._write_odd_footer() + if self.first_header: + self._write_first_header() + if self.first_footer: + self._write_first_footer() self._xml_end_tag("headerFooter") elif self.excel2003_style: self._xml_empty_tag("headerFooter", attributes) @@ -6523,6 +6693,14 @@ def _write_odd_footer(self): # Write the element. self._xml_data_element("oddFooter", self.footer) + def _write_first_header(self): + # Write the element. + self._xml_data_element('firstHeader', self.first_header) + + def _write_first_footer(self): + # Write the element. + self._xml_data_element('firstFooter', self.first_footer) + def _write_rows(self): # Write out the worksheet data as a series of rows and cells. self._calculate_spans()