Skip to content

Commit dc953d4

Browse files
committed
Font size and border width
1 parent 7db59c0 commit dc953d4

File tree

2 files changed

+340
-95
lines changed

2 files changed

+340
-95
lines changed

pandas/formats/format.py

+128-46
Original file line numberDiff line numberDiff line change
@@ -1838,18 +1838,29 @@ def __call__(self, declarations_str, inherited=None):
18381838
em_pt = float(em_pt[:-2])
18391839
else:
18401840
em_pt = None
1841-
font_size = self.font_size_to_pt(props['font-size'], em_pt)
1842-
if font_size == int(font_size):
1843-
size_fmt = '%d'
1844-
else:
1845-
size_fmt = '%f'
1846-
props['font-size'] = (size_fmt + 'pt') % font_size
1841+
props['font-size'] = self.size_to_pt(
1842+
props['font-size'], em_pt, conversions=self.FONT_SIZE_RATIOS)
1843+
1844+
font_size = float(props['font-size'][:-2])
1845+
else:
1846+
font_size = None
18471847

18481848
# 3. TODO: resolve other font-relative units
1849-
# 4. TODO: resolve other relative styles (e.g. ?)
1849+
for side in self.SIDES:
1850+
prop = 'border-%s-width' % side
1851+
if prop in props:
1852+
props[prop] = self.size_to_pt(
1853+
props[prop], em_pt=font_size, conversions=self.BORDER_WIDTH_RATIOS)
1854+
for prop in ['margin-%s' % side, 'padding-%s' % side]:
1855+
if prop in props:
1856+
# TODO: support %
1857+
props[prop] = self.size_to_pt(
1858+
props[prop], em_pt=font_size,
1859+
conversions=self.MARGIN_RATIOS)
1860+
18501861
return props
18511862

1852-
UNIT_CONVERSIONS = {
1863+
UNIT_RATIOS = {
18531864
'rem': ('pt', 12),
18541865
'ex': ('em', .5),
18551866
# 'ch':
@@ -1859,11 +1870,12 @@ def __call__(self, declarations_str, inherited=None):
18591870
'cm': ('in', 1 / 2.54),
18601871
'mm': ('in', 1 / 25.4),
18611872
'q': ('mm', .25),
1873+
'!!default': ('em', 0),
18621874
}
18631875

1864-
FONT_SIZE_CONVERSIONS = UNIT_CONVERSIONS.copy()
1865-
FONT_SIZE_CONVERSIONS.update({
1866-
'%': ('em', 1),
1876+
FONT_SIZE_RATIOS = UNIT_RATIOS.copy()
1877+
FONT_SIZE_RATIOS.update({
1878+
'%': ('em', .01),
18671879
'xx-small': ('rem', .5),
18681880
'x-small': ('rem', .625),
18691881
'small': ('rem', .8),
@@ -1873,11 +1885,26 @@ def __call__(self, declarations_str, inherited=None):
18731885
'xx-large': ('rem', 2),
18741886
'smaller': ('em', 1 / 1.2),
18751887
'larger': ('em', 1.2),
1888+
'!!default': ('em', 1),
1889+
})
1890+
1891+
MARGIN_RATIOS = UNIT_RATIOS.copy()
1892+
MARGIN_RATIOS.update({
1893+
'none': ('pt', 0),
18761894
})
18771895

1878-
def font_size_to_pt(self, val, em_pt=None):
1896+
BORDER_WIDTH_RATIOS = UNIT_RATIOS.copy()
1897+
BORDER_WIDTH_RATIOS.update({
1898+
'none': ('pt', 0),
1899+
'thick': ('px', 4),
1900+
'medium': ('px', 2),
1901+
'thin': ('px', 1),
1902+
# Default: medium only if solid
1903+
})
1904+
1905+
def size_to_pt(self, val, em_pt=None, conversions=UNIT_RATIOS):
18791906
try:
1880-
val, unit = re.match('(.*?)([a-zA-Z%].*)', val).groups()
1907+
val, unit = re.match('(.*?)([a-zA-Z%!].*)', val).groups()
18811908
except AttributeError:
18821909
warnings.warn('Unhandled font size: %r' % val, CSSWarning)
18831910
return
@@ -1900,9 +1927,19 @@ def font_size_to_pt(self, val, em_pt=None):
19001927
unit = 'pt'
19011928
continue
19021929

1903-
unit, mul = self.FONT_SIZE_CONVERSIONS[unit]
1930+
try:
1931+
unit, mul = conversions[unit]
1932+
except KeyError:
1933+
warnings.warn('Unknown size unit: %r' % unit, CSSWarning)
1934+
return self.size_to_pt('1!!default', conversions=conversions)
19041935
val *= mul
1905-
return val
1936+
1937+
val = round(val, 5)
1938+
if int(val) == val:
1939+
size_fmt = '%d'
1940+
else:
1941+
size_fmt = '%f'
1942+
return (size_fmt + 'pt') % val
19061943

19071944
def atomize(self, declarations):
19081945
for prop, value in declarations:
@@ -1915,33 +1952,33 @@ def atomize(self, declarations):
19151952
for prop, value in expand(prop, value):
19161953
yield prop, value
19171954

1918-
DIRECTION_SHORTHANDS = {
1955+
SIDE_SHORTHANDS = {
19191956
1: [0, 0, 0, 0],
19201957
2: [0, 1, 0, 1],
19211958
3: [0, 1, 2, 1],
19221959
4: [0, 1, 2, 3],
19231960
}
1924-
DIRECTIONS = ('top', 'right', 'bottom', 'left')
1961+
SIDES = ('top', 'right', 'bottom', 'left')
19251962

1926-
def _direction_expander(prop_fmt):
1963+
def _side_expander(prop_fmt):
19271964
def expand(self, prop, value):
19281965
tokens = value.split()
19291966
try:
1930-
mapping = self.DIRECTION_SHORTHANDS[len(tokens)]
1967+
mapping = self.SIDE_SHORTHANDS[len(tokens)]
19311968
except KeyError:
19321969
warnings.warn('Could not expand "%s: %s"' % (prop, value),
19331970
CSSWarning)
19341971
return
1935-
for key, idx in zip(self.DIRECTIONS, mapping):
1972+
for key, idx in zip(self.SIDES, mapping):
19361973
yield prop_fmt % key, tokens[idx]
19371974

19381975
return expand
19391976

1940-
expand_border_color = _direction_expander('border-%s-color')
1941-
expand_border_style = _direction_expander('border-%s-style')
1942-
expand_border_width = _direction_expander('border-%s-width')
1943-
expand_margin = _direction_expander('margin-%s')
1944-
expand_padding = _direction_expander('padding-%s')
1977+
expand_border_color = _side_expander('border-%s-color')
1978+
expand_border_style = _side_expander('border-%s-style')
1979+
expand_border_width = _side_expander('border-%s-width')
1980+
expand_margin = _side_expander('margin-%s')
1981+
expand_padding = _side_expander('padding-%s')
19451982

19461983
def parse(self, declarations_str):
19471984
"""Generates (prop, value) pairs from declarations
@@ -1999,6 +2036,12 @@ def __call__(self, declarations_str):
19992036
declarations_str : str
20002037
List of CSS declarations.
20012038
e.g. "font-weight: bold; background: blue"
2039+
2040+
Returns
2041+
-------
2042+
xlstyle : dict
2043+
A style as interpreted by ExcelWriter when found in
2044+
ExcelCell.style.
20022045
"""
20032046
# TODO: memoize?
20042047
properties = self.compute_css(declarations_str, self.inherited)
@@ -2045,28 +2088,65 @@ def build_alignment(self, props):
20452088
}
20462089

20472090
def build_border(self, props):
2091+
print(props)
20482092
return {side: {
2049-
# TODO: convert styles and widths to openxml, one of:
2050-
# 'dashDot'
2051-
# 'dashDotDot'
2052-
# 'dashed'
2053-
# 'dotted'
2054-
# 'double'
2055-
# 'hair'
2056-
# 'medium'
2057-
# 'mediumDashDot'
2058-
# 'mediumDashDotDot'
2059-
# 'mediumDashed'
2060-
# 'slantDashDot'
2061-
# 'thick'
2062-
# 'thin'
2063-
'style': ('medium'
2064-
if props.get('border-%s-style' % side) == 'solid'
2065-
else None),
2093+
'style': self._border_style(props.get('border-%s-style' % side),
2094+
props.get('border-%s-width' % side)),
20662095
'color': self.color_to_excel(
20672096
props.get('border-%s-color' % side)),
20682097
} for side in ['top', 'right', 'bottom', 'left']}
20692098

2099+
def _border_style(self, style, width):
2100+
# TODO: convert styles and widths to openxml, one of:
2101+
# 'dashDot'
2102+
# 'dashDotDot'
2103+
# 'dashed'
2104+
# 'dotted'
2105+
# 'double'
2106+
# 'hair'
2107+
# 'medium'
2108+
# 'mediumDashDot'
2109+
# 'mediumDashDotDot'
2110+
# 'mediumDashed'
2111+
# 'slantDashDot'
2112+
# 'thick'
2113+
# 'thin'
2114+
if width is None and style is None:
2115+
return None
2116+
if style == 'none' or style == 'hidden':
2117+
return None
2118+
2119+
if width is None:
2120+
width = '2pt'
2121+
width = float(width[:-2])
2122+
if width < 1e-5:
2123+
return None
2124+
if width < 1:
2125+
width_name = 'hair'
2126+
elif width < 2:
2127+
width_name = 'thin'
2128+
elif width < 3.5:
2129+
width_name = 'medium'
2130+
else:
2131+
width_name = 'thick'
2132+
2133+
if style in (None, 'groove', 'ridge', 'inset', 'outset'):
2134+
# not handled
2135+
style = 'solid'
2136+
2137+
if style == 'double':
2138+
return 'double'
2139+
if style == 'solid':
2140+
return width_name
2141+
if style == 'dotted':
2142+
if width_name in ('hair', 'thin'):
2143+
return 'dotted'
2144+
return 'mediumDashDotDot'
2145+
if style == 'dashed':
2146+
if width_name in ('hair', 'thin'):
2147+
return 'dashed'
2148+
return 'mediumDashed'
2149+
20702150
def build_fill(self, props):
20712151
# TODO: perhaps allow for special properties
20722152
# -excel-pattern-bgcolor and -excel-pattern-type
@@ -2087,9 +2167,11 @@ def build_font(self, props):
20872167
size = props.get('font-size')
20882168
if size is not None:
20892169
assert size.endswith('pt')
2090-
size = int(round(size[:-2]))
2170+
size = float(size[:-2])
20912171

2092-
font_names = props.get('font-family', '').split()
2172+
font_names = [name.strip()
2173+
for name in props.get('font-family', '').split(',')
2174+
if name.strip()]
20932175
family = None
20942176
for name in font_names:
20952177
if name == 'serif':
@@ -2155,9 +2237,9 @@ def color_to_excel(self, val):
21552237
if val is None:
21562238
return None
21572239
if val.startswith('#') and len(val) == 7:
2158-
return val[1:]
2240+
return val[1:].upper()
21592241
if val.startswith('#') and len(val) == 4:
2160-
return val[1] * 2 + val[2] * 2 + val[3] * 2
2242+
return (val[1] * 2 + val[2] * 2 + val[3] * 2).upper()
21612243
try:
21622244
return self.NAMED_COLORS[val]
21632245
except KeyError:

0 commit comments

Comments
 (0)