Skip to content

Commit

Permalink
Merge pull request #61 from posit-dev/remove-libsass
Browse files Browse the repository at this point in the history
refactor!: replace libsass code with webcolors
  • Loading branch information
machow authored Dec 7, 2023
2 parents d17fe1a + d9e1381 commit d3576be
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 88 deletions.
126 changes: 126 additions & 0 deletions great_tables/_scss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from __future__ import annotations

import pkg_resources
import re
import webcolors as wc

from dataclasses import fields
from functools import partial
from typing import Optional
from string import Template

from ._gt_data import GTData
from ._utils import _as_css_font_family_attr, _unique_set

DEFAULTS_TABLE_BACKGROUND = (
"heading_background_color",
"column_labels_background_color",
"row_group_background_color",
"stub_background_color",
"stub_row_group_background_color",
"summary_row_background_color",
"grand_summary_row_background_color",
"footnotes_background_color",
"source_notes_background_color",
)

FONT_COLOR_VARS = (
"table_background_color",
"heading_background_color",
"column_labels_background_color",
"column_labels_background_color",
"row_group_background_color",
"stub_background_color",
"stub_row_group_background_color",
"summary_row_background_color",
"grand_summary_row_background_color",
"footnotes_background_color",
"source_notes_background_color",
)


def font_color(color: str, table_font_color: str, table_font_color_light: str):
if color.startswith("#"):
rgb = wc.hex_to_rgb(color)
elif color.startswith("rgb") and "%" in color:
# TODO: rgb_percent_to_rgb() expects a tuple
raise NotImplementedError()
rgb = wc.rgb_percent_to_rgb(color)
else:
rgb = wc.name_to_rgb(color)

if (rgb.red * 0.299 + rgb.green * 0.587 + rgb.blue * 0.114) > 186:
return table_font_color

return table_font_color_light


def compile_scss(data: GTData, id: Optional[str]) -> str:
"""Return CSS for styling a table, based on options set."""

# Obtain the SCSS options dictionary
options = {field.name: getattr(data._options, field.name) for field in fields(data._options)}

# Get collection of parameters that pertain to SCSS ----
params = {k: opt.value for k, opt in options.items() if opt.scss and opt.value is not None}
scss_defaults = {k: params.get("table_background_color") for k in DEFAULTS_TABLE_BACKGROUND}
scss_params = {**scss_defaults, **params}

# font color variables
# TODO: at this stage, the params below (e.g. table_font_color) have to exist, right?
p_font_color = partial(
font_color,
table_font_color=params["table_font_color"],
table_font_color_light=params["table_font_color_light"],
)

font_params = {f"font_color_{k}": p_font_color(scss_params[k]) for k in FONT_COLOR_VARS}

final_params = {**scss_params, **font_params}

# Handle table id ----
# Determine whether the table has an ID
has_id = id is not None

# Obtain the `table_id` value (might be set, might be None)
# table_id = data._options._get_option_value(option="table_id")

# TODO: need to implement a function to normalize color (`html_color()`)

# Handle fonts ----
# Get the unique list of fonts from `gt_options_dict`
font_list = _unique_set(data._options.table_font_names.value)

# Generate a `font-family` string
if font_list is not None:
font_family_attr = _as_css_font_family_attr(fonts=font_list)
else:
font_family_attr = ""

# Generate styles ----
gt_table_open_str = f"#{id} table" if has_id else ".gt_table"

gt_table_class_str = f"""{gt_table_open_str} {{
{font_family_attr}
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}}"""

gt_styles_default_file = open(
pkg_resources.resource_filename("great_tables", "css/gt_styles_default.scss")
)

gt_styles_default = gt_styles_default_file.read()
gt_styles_default = re.sub(r"\s+", " ", gt_styles_default, 0, re.MULTILINE)
gt_styles_default = re.sub(r"}", "}\n", gt_styles_default, 0, re.MULTILINE)

compiled_css = Template(gt_styles_default).substitute(final_params)

if has_id:
compiled_css = re.sub(r"\.gt_", f"#{id} .gt_", compiled_css, 0, re.MULTILINE)
compiled_css = re.sub(r"thead", f"#{id} thead", compiled_css, 0, re.MULTILINE)
compiled_css = re.sub(r"^p \{", f"#{id} p " + "{", compiled_css, 0, re.MULTILINE)

finalized_css = f"{gt_table_class_str}\n\n{compiled_css}"

return finalized_css
26 changes: 13 additions & 13 deletions great_tables/css/gt_styles_default.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ p {
line-height: normal;
margin-left: $table_margin_left;
margin-right: $table_margin_right;
color: font-color($table_background_color);
color: $font_color_table_background_color;
font-size: $table_font_size;
font-weight: $table_font_weight;
font-style: $table_font_style;
Expand All @@ -40,7 +40,7 @@ p {
}

.gt_title {
color: font-color($heading_background_color);
color: $font_color_heading_background_color;
font-size: $heading_title_font_size;
font-weight: $heading_title_font_weight;
padding-top: $heading_padding;
Expand All @@ -52,7 +52,7 @@ p {
}

.gt_subtitle {
color: font-color($heading_background_color);
color: $font_color_heading_background_color;
font-size: $heading_subtitle_font_size;
font-weight: $heading_subtitle_font_weight;
padding-top: $heading_padding - 1;
Expand Down Expand Up @@ -97,7 +97,7 @@ p {
}

.gt_col_heading {
color: font-color($column_labels_background_color);
color: $font_color_column_labels_background_color;
background-color: $column_labels_background_color;
font-size: $column_labels_font_size;
font-weight: $column_labels_font_weight;
Expand All @@ -117,7 +117,7 @@ p {
}

.gt_column_spanner_outer {
color: font-color($column_labels_background_color);
color: $font_color_column_labels_background_color;
background-color: $column_labels_background_color;
font-size: $column_labels_font_size;
font-weight: $column_labels_font_weight;
Expand Down Expand Up @@ -155,7 +155,7 @@ p {
padding-bottom: $row_group_padding;
padding-left: $row_group_padding_horizontal;
padding-right: $row_group_padding_horizontal;
color: font-color($row_group_background_color);
color: $font_color_row_group_background_color;
background-color: $row_group_background_color;
font-size: $row_group_font_size;
font-weight: $row_group_font_weight;
Expand All @@ -178,7 +178,7 @@ p {

.gt_empty_group_heading {
padding: 0.5px;
color: font-color($row_group_background_color);
color: $font_color_row_group_background_color;
background-color: $row_group_background_color;
font-size: $row_group_font_size;
font-weight: $row_group_font_weight;
Expand Down Expand Up @@ -219,7 +219,7 @@ p {
}

.gt_stub {
color: font-color($stub_background_color);
color: $font_color_stub_background_color;
background-color: $stub_background_color;
font-size: $stub_font_size;
font-weight: $stub_font_weight;
Expand All @@ -232,7 +232,7 @@ p {
}

.gt_stub_row_group {
color: font-color($stub_row_group_background_color);
color: $font_color_stub_row_group_background_color;
background-color: $stub_row_group_background_color;
font-size: $stub_row_group_font_size;
font-weight: $stub_row_group_font_weight;
Expand All @@ -254,7 +254,7 @@ p {
}

.gt_summary_row {
color: font-color($summary_row_background_color);
color: $font_color_summary_row_background_color;
background-color: $summary_row_background_color;
text-transform: $summary_row_text_transform;
padding-top: $summary_row_padding;
Expand Down Expand Up @@ -283,7 +283,7 @@ p {
}

.gt_grand_summary_row {
color: font-color($grand_summary_row_background_color);
color: $font_color_grand_summary_row_background_color;
background-color: $grand_summary_row_background_color;
text-transform: $grand_summary_row_text_transform;
padding-top: $grand_summary_row_padding;
Expand Down Expand Up @@ -326,7 +326,7 @@ p {
}

.gt_footnotes {
color: font-color($footnotes_background_color);
color: $font_color_footnotes_background_color;
background-color: $footnotes_background_color;
border-bottom-style: $footnotes_border_bottom_style;
border-bottom-width: $footnotes_border_bottom_width;
Expand All @@ -349,7 +349,7 @@ p {
}

.gt_sourcenotes {
color: font-color($source_notes_background_color);
color: $font_color_source_notes_background_color;
background-color: $source_notes_background_color;
border-bottom-style: $source_notes_border_bottom_style;
border-bottom-width: $source_notes_border_bottom_width;
Expand Down
77 changes: 4 additions & 73 deletions great_tables/gt.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
from __future__ import annotations

from typing import Any, List, Optional
from typing import Any, List
from typing_extensions import Self
from dataclasses import fields

import pkg_resources

import sass
import re
import copy

from great_tables._gt_data import GTData
Expand Down Expand Up @@ -48,7 +43,6 @@
from great_tables._stub import reorder_stub_df
from great_tables._stubhead import tab_stubhead
from great_tables._tbl_data import n_rows, _get_cell
from great_tables._utils import _as_css_font_family_attr, _unique_set
from great_tables._utils_render_html import (
create_heading_component_h,
create_columns_component_h,
Expand Down Expand Up @@ -299,7 +293,9 @@ def _render_as_html(self) -> str:
id = table_id

# Compile the SCSS as CSS
css = _compile_scss(data=self, id=id)
from ._scss import compile_scss

css = compile_scss(data=self, id=id)

# Obtain options set for overflow and container dimensions

Expand Down Expand Up @@ -374,68 +370,3 @@ def _get_column_of_values(gt: GT, column_name: str, context: str) -> List[str]:
cell_values.append(cell_str)

return cell_values


# =============================================================================
# SCSS Compilation
# =============================================================================


def _compile_scss(data: GT, id: Optional[str]) -> str:
# Obtain the SCSS options dictionary
options = {field.name: getattr(data._options, field.name) for field in fields(data._options)}

# Get collection of parameters that pertain to SCSS
scss_params_raw = {k: opt for k, opt in options.items() if opt.scss and opt.value is not None}
scss_params = [f"${k}: {opt.value};" for k, opt in scss_params_raw.items()]
scss_params_str = "\n".join(scss_params) + "\n"

# Determine whether the table has an ID
has_id = id is not None

# Obtain the `table_id` value (might be set, might be None)
# table_id = data._options._get_option_value(option="table_id")

# TODO: need to implement a function to normalize color (`html_color()`)

# Get the unique list of fonts from `gt_options_dict`
font_list = _unique_set(data._options.table_font_names.value)

# Generate a `font-family` string
if font_list is not None:
font_family_attr = _as_css_font_family_attr(fonts=font_list)
else:
font_family_attr = ""

gt_table_open_str = f"#{id} table" if has_id else ".gt_table"

gt_table_class_str = f"""{gt_table_open_str} {{
{font_family_attr}
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}}"""

gt_styles_default_file = open(
pkg_resources.resource_filename("great_tables", "css/gt_styles_default.scss")
)

gt_styles_default = gt_styles_default_file.read()
gt_styles_default = re.sub(r"\s+", " ", gt_styles_default, 0, re.MULTILINE)
gt_styles_default = re.sub(r"}", "}\n", gt_styles_default, 0, re.MULTILINE)

gt_colors_file = open(pkg_resources.resource_filename("great_tables", "css/gt_colors.scss"))

gt_colors = gt_colors_file.read()

scss = scss_params_str + gt_colors + gt_styles_default

compiled_css = sass.compile(string=scss)

if has_id:
compiled_css = re.sub(r"\.gt_", f"#{id} .gt_", compiled_css, 0, re.MULTILINE)
compiled_css = re.sub(r"thead", f"#{id} thead", compiled_css, 0, re.MULTILINE)
compiled_css = re.sub(r"^p \{", f"#{id} p " + "{", compiled_css, 0, re.MULTILINE)

finalized_css = f"{gt_table_class_str}\n\n{compiled_css}"

return finalized_css
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ dependencies = [
"packaging>=20.9",
"pandas>=1.4.2",
"numpy>=1.22.4",
"libsass>=0.22.0",
"Babel>=2.13.1"
"Babel>=2.13.1",
"webcolors",
]
requires-python = ">=3.7"

Expand Down
16 changes: 16 additions & 0 deletions tests/test_scss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest

from great_tables._scss import font_color


@pytest.mark.parametrize(
"src,dst",
[
("#FFFFFF", "dark"),
("#000000", "light"),
("white", "dark"),
],
)
def test_font_color(src, dst):
res = font_color(src, "dark", "light")
assert res == dst

0 comments on commit d3576be

Please sign in to comment.