quizcomp.parser.style

Style constants and base functionality.

  1"""
  2Style constants and base functionality.
  3"""
  4
  5import typing
  6
  7KEY_CONTENT_ALIGN: str = 'content-align'
  8KEY_FONT_SIZE: str = 'font-size'
  9KEY_IMAGE_WIDTH: str = 'image-width'
 10KEY_TABLE_BORDER_CELLS: str = 'table-border-cells'
 11KEY_TABLE_BORDER_TABLE: str = 'table-border-table'
 12KEY_TABLE_CELL_HEIGHT: str = 'table-cell-height'
 13KEY_TABLE_CELL_WIDTH: str = 'table-cell-width'
 14KEY_TABLE_HEAD_BOLD: str = 'table-head-bold'
 15KEY_TABLE_HEAD_RULE: str = 'table-head-rule'
 16KEY_TEXT_ALIGN: str = 'text-align'
 17
 18DEFAULT_IMAGE_WIDTH: float = 1.0
 19DEFAULT_TABLE_BORDER_CELLS: bool = False
 20DEFAULT_TABLE_BORDER_TABLE: bool = False
 21DEFAULT_TABLE_CELL_HEIGHT: float = 1.5
 22DEFAULT_TABLE_CELL_WIDTH: float = 1.5
 23DEFAULT_TABLE_HEAD_BOLD: bool = True
 24DEFAULT_TABLE_HEAD_RULE: bool = True
 25
 26ALLOWED_VALUES_ALIGNMENT_LEFT: str = 'left'
 27ALLOWED_VALUES_ALIGNMENT_CENTER: str = 'center'
 28ALLOWED_VALUES_ALIGNMENT_RIGHT: str = 'right'
 29ALLOWED_VALUES_ALIGNMENT: typing.List[str] = [
 30    ALLOWED_VALUES_ALIGNMENT_LEFT,
 31    ALLOWED_VALUES_ALIGNMENT_CENTER,
 32    ALLOWED_VALUES_ALIGNMENT_RIGHT
 33]
 34
 35FLEXBOX_ALIGNMENT: typing.Dict[str, str] = {
 36    ALLOWED_VALUES_ALIGNMENT_LEFT: 'flex-start',
 37    ALLOWED_VALUES_ALIGNMENT_CENTER: 'center',
 38    ALLOWED_VALUES_ALIGNMENT_RIGHT: 'flex-end',
 39}
 40
 41TEX_BLOCK_ALIGNMENT: typing.Dict[str, str] = {
 42    ALLOWED_VALUES_ALIGNMENT_LEFT: 'flushleft',
 43    ALLOWED_VALUES_ALIGNMENT_CENTER: 'center',
 44    ALLOWED_VALUES_ALIGNMENT_RIGHT: 'flushright',
 45}
 46
 47def get_alignment(style: typing.Dict[str, typing.Any], key: str, default_value: typing.Union[str, None] = None) -> typing.Union[str, None]:
 48    """ Get the specified alignment, or None if there is no specified alignment (or default). """
 49
 50    alignment = style.get(key, None)
 51    if (alignment is None):
 52        return default_value
 53
 54    alignment = str(alignment).lower()
 55    if (alignment not in ALLOWED_VALUES_ALIGNMENT):
 56        raise ValueError(f"Unknown value for '{key}' style key '{alignment}'. Allowed values: {ALLOWED_VALUES_ALIGNMENT}.")
 57
 58    return alignment
 59
 60def get_boolean_style_key(style: typing.Dict[str, typing.Any], key: str, default_value: bool = False) -> bool:
 61    """ Get a boolean style key. """
 62
 63    value = style.get(key, default_value)
 64    if (value is None):
 65        return default_value
 66
 67    return (value is True)
 68
 69def get_image_width(style: typing.Dict[str, typing.Any]) -> float:
 70    """ Get the image width. """
 71
 72    width = style.get(KEY_IMAGE_WIDTH, None)
 73    if (width is None):
 74        width = DEFAULT_IMAGE_WIDTH
 75
 76    return float(width)
 77
 78def compute_html_style_string(style: typing.Dict[str, typing.Any]) -> str:
 79    """
 80    Compute the attribute style string for an HTML tag.
 81    """
 82
 83    attributes = []
 84
 85    content_align = get_alignment(style, KEY_CONTENT_ALIGN)
 86    if (content_align is not None):
 87        attributes.append("display: flex")
 88        attributes.append("flex-direction: column")
 89        attributes.append("justify-content: flex-start")
 90        attributes.append(f"align-items: {FLEXBOX_ALIGNMENT[content_align]}")
 91
 92    text_align = get_alignment(style, KEY_TEXT_ALIGN)
 93    if (text_align is not None):
 94        attributes.append(f"text-align: {text_align}")
 95
 96    font_size = style.get(KEY_FONT_SIZE, None)
 97    if (font_size is not None):
 98        attributes.append(f"font-size: {float(font_size):0.2f}pt")
 99
100    if (len(attributes) == 0):
101        return ''
102
103    return '; '.join(attributes)
104
105def compute_tex_fixes(style: typing.Dict[str, typing.Any]) -> typing.Tuple[typing.List[str], typing.List[str]]:
106    """
107    Compute the fixes (prefixes, suffixes) for a portion of TeX.
108    These are things like `\\begin{center}`/`\\end{center}`.
109    The returned lists have matching indexes.
110    """
111
112    # The beginning and ends of groups.
113    # These will match 1-1.
114    prefixes = []
115    suffixes = []
116
117    content_align = get_alignment(style, KEY_CONTENT_ALIGN)
118    if (content_align is not None):
119        env_name = TEX_BLOCK_ALIGNMENT[content_align]
120        prefixes.append("\\begin{%s}" % (env_name))
121        suffixes.append("\\end{%s}" % (env_name))
122
123    font_size = style.get(KEY_FONT_SIZE, None)
124    if (font_size is not None):
125        font_size = float(font_size)
126        # 1.2 is the default size for baseline skip relative to font size.
127        # See: https://ctan.math.illinois.edu/macros/latex/contrib/fontsize/fontsize.pdf
128        baseline_skip = 1.2 * font_size
129
130        prefixes.append('\\begingroup\\fontsize{%.2fpt}{%.2fpt}\\selectfont' % (font_size, baseline_skip))
131        suffixes.append('\\endgroup')
132
133    return prefixes, suffixes
KEY_CONTENT_ALIGN: str = 'content-align'
KEY_FONT_SIZE: str = 'font-size'
KEY_IMAGE_WIDTH: str = 'image-width'
KEY_TABLE_BORDER_CELLS: str = 'table-border-cells'
KEY_TABLE_BORDER_TABLE: str = 'table-border-table'
KEY_TABLE_CELL_HEIGHT: str = 'table-cell-height'
KEY_TABLE_CELL_WIDTH: str = 'table-cell-width'
KEY_TABLE_HEAD_BOLD: str = 'table-head-bold'
KEY_TABLE_HEAD_RULE: str = 'table-head-rule'
KEY_TEXT_ALIGN: str = 'text-align'
DEFAULT_IMAGE_WIDTH: float = 1.0
DEFAULT_TABLE_BORDER_CELLS: bool = False
DEFAULT_TABLE_BORDER_TABLE: bool = False
DEFAULT_TABLE_CELL_HEIGHT: float = 1.5
DEFAULT_TABLE_CELL_WIDTH: float = 1.5
DEFAULT_TABLE_HEAD_BOLD: bool = True
DEFAULT_TABLE_HEAD_RULE: bool = True
ALLOWED_VALUES_ALIGNMENT_LEFT: str = 'left'
ALLOWED_VALUES_ALIGNMENT_CENTER: str = 'center'
ALLOWED_VALUES_ALIGNMENT_RIGHT: str = 'right'
ALLOWED_VALUES_ALIGNMENT: List[str] = ['left', 'center', 'right']
FLEXBOX_ALIGNMENT: Dict[str, str] = {'left': 'flex-start', 'center': 'center', 'right': 'flex-end'}
TEX_BLOCK_ALIGNMENT: Dict[str, str] = {'left': 'flushleft', 'center': 'center', 'right': 'flushright'}
def get_alignment( style: Dict[str, Any], key: str, default_value: Optional[str] = None) -> Optional[str]:
48def get_alignment(style: typing.Dict[str, typing.Any], key: str, default_value: typing.Union[str, None] = None) -> typing.Union[str, None]:
49    """ Get the specified alignment, or None if there is no specified alignment (or default). """
50
51    alignment = style.get(key, None)
52    if (alignment is None):
53        return default_value
54
55    alignment = str(alignment).lower()
56    if (alignment not in ALLOWED_VALUES_ALIGNMENT):
57        raise ValueError(f"Unknown value for '{key}' style key '{alignment}'. Allowed values: {ALLOWED_VALUES_ALIGNMENT}.")
58
59    return alignment

Get the specified alignment, or None if there is no specified alignment (or default).

def get_boolean_style_key(style: Dict[str, Any], key: str, default_value: bool = False) -> bool:
61def get_boolean_style_key(style: typing.Dict[str, typing.Any], key: str, default_value: bool = False) -> bool:
62    """ Get a boolean style key. """
63
64    value = style.get(key, default_value)
65    if (value is None):
66        return default_value
67
68    return (value is True)

Get a boolean style key.

def get_image_width(style: Dict[str, Any]) -> float:
70def get_image_width(style: typing.Dict[str, typing.Any]) -> float:
71    """ Get the image width. """
72
73    width = style.get(KEY_IMAGE_WIDTH, None)
74    if (width is None):
75        width = DEFAULT_IMAGE_WIDTH
76
77    return float(width)

Get the image width.

def compute_html_style_string(style: Dict[str, Any]) -> str:
 79def compute_html_style_string(style: typing.Dict[str, typing.Any]) -> str:
 80    """
 81    Compute the attribute style string for an HTML tag.
 82    """
 83
 84    attributes = []
 85
 86    content_align = get_alignment(style, KEY_CONTENT_ALIGN)
 87    if (content_align is not None):
 88        attributes.append("display: flex")
 89        attributes.append("flex-direction: column")
 90        attributes.append("justify-content: flex-start")
 91        attributes.append(f"align-items: {FLEXBOX_ALIGNMENT[content_align]}")
 92
 93    text_align = get_alignment(style, KEY_TEXT_ALIGN)
 94    if (text_align is not None):
 95        attributes.append(f"text-align: {text_align}")
 96
 97    font_size = style.get(KEY_FONT_SIZE, None)
 98    if (font_size is not None):
 99        attributes.append(f"font-size: {float(font_size):0.2f}pt")
100
101    if (len(attributes) == 0):
102        return ''
103
104    return '; '.join(attributes)

Compute the attribute style string for an HTML tag.

def compute_tex_fixes(style: Dict[str, Any]) -> Tuple[List[str], List[str]]:
106def compute_tex_fixes(style: typing.Dict[str, typing.Any]) -> typing.Tuple[typing.List[str], typing.List[str]]:
107    """
108    Compute the fixes (prefixes, suffixes) for a portion of TeX.
109    These are things like `\\begin{center}`/`\\end{center}`.
110    The returned lists have matching indexes.
111    """
112
113    # The beginning and ends of groups.
114    # These will match 1-1.
115    prefixes = []
116    suffixes = []
117
118    content_align = get_alignment(style, KEY_CONTENT_ALIGN)
119    if (content_align is not None):
120        env_name = TEX_BLOCK_ALIGNMENT[content_align]
121        prefixes.append("\\begin{%s}" % (env_name))
122        suffixes.append("\\end{%s}" % (env_name))
123
124    font_size = style.get(KEY_FONT_SIZE, None)
125    if (font_size is not None):
126        font_size = float(font_size)
127        # 1.2 is the default size for baseline skip relative to font size.
128        # See: https://ctan.math.illinois.edu/macros/latex/contrib/fontsize/fontsize.pdf
129        baseline_skip = 1.2 * font_size
130
131        prefixes.append('\\begingroup\\fontsize{%.2fpt}{%.2fpt}\\selectfont' % (font_size, baseline_skip))
132        suffixes.append('\\endgroup')
133
134    return prefixes, suffixes

Compute the fixes (prefixes, suffixes) for a portion of TeX. These are things like \begin{center}/\end{center}. The returned lists have matching indexes.