Skip to content

Add table cell background color #231

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ main.py

docs/_build
tests/failures/*.html

~$*.docx
5 changes: 5 additions & 0 deletions pydocx/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
# https://en.wikipedia.org/wiki/Twip
TWIPS_PER_POINT = 20

# According to this: http://alienryderflex.com/hsp.html
BRIGHTNESS_DARK_COLOR = 50

COLOR_FOR_DARK_BACKGROUND = 'FFFFFF'

# TODO These alignment values are for traditional conformance. Strict
# conformance uses different values
JUSTIFY_CENTER = 'center'
Expand Down
8 changes: 8 additions & 0 deletions pydocx/export/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ def get_run_styles_to_apply(self, run):
(properties.hidden, self.export_run_property_hidden),
(properties.vertical_align, self.export_run_property_vertical_align),
(properties.color, self.export_run_property_color),
(properties.highlight_color, self.export_run_property_highlight_color),
(not properties.color, self.export_run_property_parent_background_color),
]
for actual_value, handler in property_rules:
if actual_value:
Expand Down Expand Up @@ -407,6 +409,12 @@ def export_run_property_vertical_align(self, run, results):
def export_run_property_color(self, run, results):
return results

def export_run_property_highlight_color(self, run, results):
return results

def export_run_property_parent_background_color(self, run, results):
return results

def export_hyperlink(self, hyperlink):
return self.yield_nested(hyperlink.children, self.export_node)

Expand Down
47 changes: 46 additions & 1 deletion pydocx/export/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@
POINTS_PER_EM,
PYDOCX_STYLES,
TWIPS_PER_POINT,
EMUS_PER_PIXEL
EMUS_PER_PIXEL,
BRIGHTNESS_DARK_COLOR,
COLOR_FOR_DARK_BACKGROUND
)
from pydocx.export.base import PyDocXExporter
from pydocx.export.numbering_span import NumberingItem
from pydocx.openxml import wordprocessing
from pydocx.openxml.wordprocessing.paragraph import Paragraph
from pydocx.openxml.wordprocessing.table_cell import TableCell
from pydocx.util import color
from pydocx.util.uri import uri_is_external
from pydocx.util.xml import (
convert_dictionary_to_html_attributes,
Expand Down Expand Up @@ -572,6 +577,40 @@ def export_run_property_color(self, run, results):
tag = HtmlTag('span', **attrs)
return self.export_run_property(tag, run, results)

def export_run_property_highlight_color(self, run, results):
if run.properties is None or run.properties.highlight_color is None:
return results

attrs = {
'style': 'background-color: %s' % run.properties.highlight_color
}
tag = HtmlTag('span', **attrs)

return self.export_run_property(tag, run, results)

def export_run_property_parent_background_color(self, run, results):
background_color = None

if isinstance(run.parent, (Paragraph,)):
paragraph = run.parent
if isinstance(paragraph.parent, (TableCell,)) and paragraph.parent.properties:
table_cell_prop = paragraph.parent.properties
background_color = table_cell_prop.background_color

if background_color:
brightness = color.brightness(background_color)
# We need to change the text color if background color is dark
# and text run does not have custom color.
# Office Word does this automatically.
if brightness < BRIGHTNESS_DARK_COLOR:
attrs = {
'style': 'color: #%s' % COLOR_FOR_DARK_BACKGROUND
}
tag = HtmlTag('span', **attrs)
results = self.export_run_property(tag, run, results)

return results

def export_text(self, text):
results = super(PyDocXHTMLExporter, self).export_text(text)
for result in results:
Expand Down Expand Up @@ -668,6 +707,12 @@ def export_table_cell(self, table_cell):
attrs['colspan'] = colspan
if rowspan > 1:
attrs['rowspan'] = rowspan

if table_cell.properties:
background_color = table_cell.properties.background_color
if background_color:
attrs['style'] = 'background-color: #%s' % background_color

tag = HtmlTag('td', **attrs)

numbering_spans = self.yield_numbering_spans(table_cell.children)
Expand Down
11 changes: 9 additions & 2 deletions pydocx/openxml/wordprocessing/run_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,24 @@ class RunProperties(XmlModel):
sz = XmlChild(name='sz', attrname='val')
clr = XmlChild(name='color', attrname='val')
r_fonts = XmlChild(type=RFonts)
highlight_clr = XmlChild(name='highlight', attrname='val')

@property
def color(self):
if self.clr is None:
return
# TODO: When we support background colors, remove FFFFFF check
if self.clr == '000000' or self.clr == 'FFFFFF':
if self.clr == '000000':
return

return self.clr

@property
def highlight_color(self):
if self.highlight_clr is None:
return

return self.highlight_clr

@property
def position(self):
if self.pos is None:
Expand Down
11 changes: 11 additions & 0 deletions pydocx/openxml/wordprocessing/table_cell_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
)

from pydocx.models import XmlModel, XmlChild
from pydocx.constants import COLOR_FOR_DARK_BACKGROUND


class TableCellProperties(XmlModel):
XML_TAG = 'tcPr'

grid_span = XmlChild(name='gridSpan', attrname='val')

background_fill = XmlChild(name='shd', attrname='fill')

vertical_merge = XmlChild(name='vMerge', type=lambda el: dict(el.attrib)) # noqa

def should_close_previous_vertical_merge(self):
Expand All @@ -25,3 +28,11 @@ def should_close_previous_vertical_merge(self):
if merge != 'continue':
return True
return False

@property
def background_color(self):
# There is no need to set white background color
if self.background_fill not in ('auto', COLOR_FOR_DARK_BACKGROUND):
return self.background_fill

return None
3 changes: 2 additions & 1 deletion pydocx/test/document_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,14 @@ def li(self, text, ilvl, numId, bold=False):
)

@classmethod
def table_cell(self, paragraph, merge=False, merge_continue=False):
def table_cell(self, paragraph, merge=False, merge_continue=False, fill_color='auto'):
template = env.get_template(templates['tc'])
return template_render(
template,
paragraph=paragraph,
merge=merge,
merge_continue=merge_continue,
fill_color=fill_color
)

@classmethod
Expand Down
26 changes: 26 additions & 0 deletions pydocx/util/color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# coding: utf-8
from __future__ import (
absolute_import,
print_function,
unicode_literals,
)

import math


def hex_to_rgb(value):
"""Return (red, green, blue) for the color given as #rrggbb."""

value = value.lstrip('#')
lv = len(value)
return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))


def brightness(hex_color):
"""A trick to determine the brightness of the color.
More info about this here: http://alienryderflex.com/hsp.html
"""

r, g, b = hex_to_rgb(hex_color)

return math.sqrt(math.pow(r, 2) * .241 + math.pow(g, 2) * .691 + math.pow(b, 2) * .068)
29 changes: 29 additions & 0 deletions tests/export/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,35 @@ def get_xml(self):
return xml


class TableWithCellBackgroundColor(TranslationTestCase):
expected_output = '''
<table border="1">
<tr>
<td style="background-color: #FF00FF">AAA</td>
<td>BBB</td>
</tr>
<tr>
<td>CCC</td>
<td style="background-color: #000000">
<span style="color: #FFFFFF">DDD</span>
</td>
</tr>
</table>
'''

def get_xml(self):
cell1 = DXB.table_cell(paragraph=DXB.p_tag('AAA'), fill_color='FF00FF')
cell2 = DXB.table_cell(paragraph=DXB.p_tag('BBB'), fill_color='FFFFFF')
cell3 = DXB.table_cell(paragraph=DXB.p_tag('CCC'), fill_color='auto')
# for dark color we get white text color
cell4 = DXB.table_cell(paragraph=DXB.p_tag('DDD'), fill_color='000000')
rows = [DXB.table_row([cell1, cell2]), DXB.table_row([cell3, cell4])]
table = DXB.table(rows)
body = table
xml = DXB.xml(body)
return xml


class SimpleListTestCase(TranslationTestCase):
expected_output = '''
<ol class="pydocx-list-style-type-lowerLetter">
Expand Down
Binary file modified tests/fixtures/styled_color.docx
Binary file not shown.
16 changes: 14 additions & 2 deletions tests/fixtures/styled_color.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@
<p><span style="color:#31849B">BBB</span></p>
<table border="1">
<tr>
<td>CCC</td>
<td style="background-color: #5F497A">
<span style="color:#FFFFFF">CCC</span>
</td>
</tr>
</table>
<p>DDD</p>
<p>
<span style="background-color: red">
<span style="color:#FFFFFF">DDD</span>
</span>
</p>
<p>
<span style="background-color: yellow">AAA</span>
<span style="background-color: green">BBB</span>
<span style="background-color: blue">CCC</span>
DDD
</p>
2 changes: 1 addition & 1 deletion tests/templates/tc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<w:left w:color="000000" w:space="0" w:sz="2" w:val="single"/>
<w:bottom w:color="000000" w:space="0" w:sz="2" w:val="single"/>
</w:tcBorders>
<w:shd w:fill="auto" w:val="clear"/>
<w:shd w:fill="{{ fill_color }}" w:val="clear"/>
<w:tcMar>
<w:top w:type="dxa" w:w="55"/>
<w:left w:type="dxa" w:w="55"/>
Expand Down