Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fist page header/footer implementation #1005

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/headers_footers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
76 changes: 76 additions & 0 deletions xlsxwriter/workbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
178 changes: 178 additions & 0 deletions xlsxwriter/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -6523,6 +6693,14 @@ def _write_odd_footer(self):
# Write the <headerFooter> element.
self._xml_data_element("oddFooter", self.footer)

def _write_first_header(self):
# Write the <headerFooter> element.
self._xml_data_element('firstHeader', self.first_header)

def _write_first_footer(self):
# Write the <headerFooter> 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()
Expand Down