DOCX: Improve style cascading

1) Add cascading for colors
    2) Remove unnecessary text-decoration:none entries
    3) Only cascade font properties if they are the same for all runs in a
    block.
    4) Set sensible defaults for cascaded properties that are never
    specified.
This commit is contained in:
Kovid Goyal 2013-06-12 16:26:12 +05:30
parent cc81908994
commit 9961ada770
3 changed files with 54 additions and 40 deletions

View File

@ -300,7 +300,7 @@ class ParagraphStyle(object):
# Misc. # Misc.
'text_indent', 'text_align', 'line_height', 'direction', 'background_color', 'text_indent', 'text_align', 'line_height', 'direction', 'background_color',
'numbering', 'font_family', 'font_size', 'frame', 'numbering', 'font_family', 'font_size', 'color', 'frame',
) )
def __init__(self, pPr=None): def __init__(self, pPr=None):
@ -324,7 +324,7 @@ class ParagraphStyle(object):
for s in XPath('./w:pStyle[@w:val]')(pPr): for s in XPath('./w:pStyle[@w:val]')(pPr):
self.linked_style = get(s, 'w:val') self.linked_style = get(s, 'w:val')
self.font_family = self.font_size = inherit self.font_family = self.font_size = self.color = inherit
self._css = None self._css = None
@ -368,7 +368,7 @@ class ParagraphStyle(object):
if self.line_height not in {inherit, '1'}: if self.line_height not in {inherit, '1'}:
c['line-height'] = self.line_height c['line-height'] = self.line_height
for x in ('text_indent', 'text_align', 'background_color', 'font_family', 'font_size'): for x in ('text_indent', 'text_align', 'background_color', 'font_family', 'font_size', 'color'):
val = getattr(self, x) val = getattr(self, x)
if val is not inherit: if val is not inherit:
if x == 'font_size': if x == 'font_size':

View File

@ -317,51 +317,63 @@ class Styles(object):
def cascade(self, layers): def cascade(self, layers):
self.body_font_family = 'serif' self.body_font_family = 'serif'
self.body_font_size = '10pt' self.body_font_size = '10pt'
self.body_color = 'black'
def promote_property(char_styles, block_style, prop):
vals = {getattr(s, prop) for s in char_styles}
if len(vals) == 1:
# All the character styles have the same value
for s in char_styles:
setattr(s, prop, inherit)
setattr(block_style, prop, next(iter(vals)))
for p, runs in layers.iteritems(): for p, runs in layers.iteritems():
has_links = '1' in {r.get('is-link', None) for r in runs}
char_styles = [self.resolve_run(r) for r in runs] char_styles = [self.resolve_run(r) for r in runs]
block_style = self.resolve_paragraph(p) block_style = self.resolve_paragraph(p)
c = Counter() for prop in ('font_family', 'font_size', 'color'):
if has_links and prop == 'color':
# We cannot promote color as browser rendering engines will
# override the link color setting it to blue, unless the
# color is specified on the link element itself
continue
promote_property(char_styles, block_style, prop)
for s in char_styles: for s in char_styles:
if s.font_family is not inherit: if s.text_decoration == 'none':
c[s.font_family] += 1 # The default text decoration is 'none'
s.text_decoration = inherit
def promote_most_common(block_styles, prop, default):
c = Counter()
for s in block_styles:
val = getattr(s, prop)
if val is not inherit:
c[val] += 1
val = None
if c: if c:
family = c.most_common(1)[0][0] val = c.most_common(1)[0][0]
block_style.font_family = family for s in block_styles:
for s in char_styles: oval = getattr(s, prop)
if s.font_family == family: if oval is inherit:
s.font_family = inherit if default != val:
setattr(s, prop, default)
elif oval == val:
setattr(s, prop, inherit)
return val
sizes = [s.font_size for s in char_styles if s.font_size is not inherit] block_styles = tuple(self.resolve_paragraph(p) for p in layers)
if sizes:
sz = block_style.font_size = sizes[0]
for s in char_styles:
if s.font_size == sz:
s.font_size = inherit
block_styles = [self.resolve_paragraph(p) for p in layers] ff = promote_most_common(block_styles, 'font_family', self.body_font_family)
c = Counter() if ff is not None:
for s in block_styles: self.body_font_family = ff
if s.font_family is not inherit:
c[s.font_family] += 1
if c: fs = promote_most_common(block_styles, 'font_size', int(self.body_font_size[:2]))
self.body_font_family = family = c.most_common(1)[0][0] if fs is not None:
for s in block_styles: self.body_font_size = '%.3gpt' % fs
if s.font_family == family:
s.font_family = inherit
c = Counter() color = promote_most_common(block_styles, 'color', self.body_color)
for s in block_styles: if color is not None:
if s.font_size is not inherit: self.body_color = color
c[s.font_size] += 1
if c:
sz = c.most_common(1)[0][0]
for s in block_styles:
if s.font_size == sz:
s.font_size = inherit
self.body_font_size = '%.3gpt' % sz
def resolve_numbering(self, numbering): def resolve_numbering(self, numbering):
# When a numPr element appears inside a paragraph style, the lvl info # When a numPr element appears inside a paragraph style, the lvl info
@ -403,7 +415,7 @@ class Styles(object):
ef = self.fonts.embed_fonts(dest_dir, docx) ef = self.fonts.embed_fonts(dest_dir, docx)
prefix = textwrap.dedent( prefix = textwrap.dedent(
'''\ '''\
body { font-family: %s; font-size: %s } body { font-family: %s; font-size: %s; color: %s }
ul, ol, p { margin: 0; padding: 0 } ul, ol, p { margin: 0; padding: 0 }
@ -419,7 +431,7 @@ class Styles(object):
dl.notes dd:last-of-type { page-break-after: avoid } dl.notes dd:last-of-type { page-break-after: avoid }
''') % (self.body_font_family, self.body_font_size) ''') % (self.body_font_family, self.body_font_size, self.body_color)
if ef: if ef:
prefix = ef + '\n' + prefix prefix = ef + '\n' + prefix
@ -430,3 +442,4 @@ class Styles(object):
ans.append('.%s {\n%s\n}\n' % (cls, b.rstrip(';'))) ans.append('.%s {\n%s\n}\n' % (cls, b.rstrip(';')))
return prefix + '\n' + '\n'.join(ans) return prefix + '\n' + '\n'.join(ans)

View File

@ -325,6 +325,7 @@ class Convert(object):
try: try:
hl = hl_xpath(x)[0] hl = hl_xpath(x)[0]
self.link_map[hl].append(span) self.link_map[hl].append(span)
x.set('is-link', '1')
except IndexError: except IndexError:
current_hyperlink = None current_hyperlink = None
dest.append(span) dest.append(span)