diff --git a/.gitignore b/.gitignore index 83b2da20..50299015 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ main.py docs/_build tests/failures/*.html + +~$*.docx diff --git a/pydocx/constants.py b/pydocx/constants.py index 9f7c1788..6d11673e 100644 --- a/pydocx/constants.py +++ b/pydocx/constants.py @@ -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' diff --git a/pydocx/export/base.py b/pydocx/export/base.py index 67b002bc..31adfa8d 100644 --- a/pydocx/export/base.py +++ b/pydocx/export/base.py @@ -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: @@ -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) diff --git a/pydocx/export/html.py b/pydocx/export/html.py index 18e3ea53..c75ab5de 100644 --- a/pydocx/export/html.py +++ b/pydocx/export/html.py @@ -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, @@ -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: @@ -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) diff --git a/pydocx/openxml/wordprocessing/run_properties.py b/pydocx/openxml/wordprocessing/run_properties.py index 63587a57..3682b6e4 100644 --- a/pydocx/openxml/wordprocessing/run_properties.py +++ b/pydocx/openxml/wordprocessing/run_properties.py @@ -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: diff --git a/pydocx/openxml/wordprocessing/table_cell_properties.py b/pydocx/openxml/wordprocessing/table_cell_properties.py index 2a94c9b0..19f7abd2 100644 --- a/pydocx/openxml/wordprocessing/table_cell_properties.py +++ b/pydocx/openxml/wordprocessing/table_cell_properties.py @@ -6,6 +6,7 @@ ) from pydocx.models import XmlModel, XmlChild +from pydocx.constants import COLOR_FOR_DARK_BACKGROUND class TableCellProperties(XmlModel): @@ -13,6 +14,8 @@ class TableCellProperties(XmlModel): 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): @@ -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 diff --git a/pydocx/test/document_builder.py b/pydocx/test/document_builder.py index 589fec16..082c8e86 100644 --- a/pydocx/test/document_builder.py +++ b/pydocx/test/document_builder.py @@ -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 diff --git a/pydocx/util/color.py b/pydocx/util/color.py new file mode 100644 index 00000000..0c712f97 --- /dev/null +++ b/pydocx/util/color.py @@ -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) diff --git a/tests/export/test_xml.py b/tests/export/test_xml.py index 2ad17a88..38b5a306 100644 --- a/tests/export/test_xml.py +++ b/tests/export/test_xml.py @@ -296,6 +296,35 @@ def get_xml(self): return xml +class TableWithCellBackgroundColor(TranslationTestCase): + expected_output = ''' +
AAA | +BBB | +
CCC | ++ DDD + | +
BBB
CCC | ++ CCC + |
DDD
++ + DDD + +
++ AAA + BBB + CCC + DDD +
diff --git a/tests/templates/tc.xml b/tests/templates/tc.xml index eff9ce0d..b60b7e4e 100644 --- a/tests/templates/tc.xml +++ b/tests/templates/tc.xml @@ -14,7 +14,7 @@