zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

html.ex (7450B)


      1 defmodule Makeup.Styles.HTML do
      2   defmodule Style do
      3     defstruct long_name: "",
      4               short_name: "",
      5               background_color: "#ffffff",
      6               highlight_color: "#ffffcc",
      7               styles: []
      8 
      9     alias Makeup.Styles.HTML.TokenStyle
     10     require Makeup.Token.Utils
     11     alias Makeup.Token.Utils
     12 
     13     defp handle_inheritance(style_map) do
     14       # Handles insheritance between styles.
     15       # This is automatic in Pygments' design, because they use class inheritance for tokens.
     16       # We don't have class inheritance in elixir, so we must have something else.
     17       # Here, we use a manually build hierarchy to fake inheritance.
     18       #
     19       # In any case, the goal is to have flat tokens at runtime.
     20       # This function is only called at compile time.
     21       Enum.reduce(Utils.precedence(), style_map, fn {parent_key, child_keys}, style_map ->
     22         parent_style = style_map[parent_key]
     23 
     24         Enum.reduce(child_keys, style_map, fn child_key, style_map ->
     25           child_style = style_map[child_key]
     26 
     27           Map.put(
     28             style_map,
     29             child_key,
     30             Map.merge(
     31               parent_style,
     32               child_style,
     33               fn _k, v1, v2 -> v2 || v1 end
     34             )
     35           )
     36         end)
     37       end)
     38     end
     39 
     40     require EEx
     41 
     42     EEx.function_from_string(
     43       :def,
     44       :render_css,
     45       """
     46       .<%= highlight_class %> .hll {background-color: <%= highlight_color %>}
     47       .<%= highlight_class %> {\
     48       <%= if token_text.color do %>color: <%= token_text.color %>; <% end %>\
     49       <%= if token_text.font_style do %>font-style: <%= token_text.font_style %>; <% end %>\
     50       <%= if token_text.font_weight do %>font-weight: <%= token_text.font_weight %>; <% end %>\
     51       <%= if token_text.border do %>border: <%= token_text.border %>; <% end %>\
     52       <%= if token_text.text_decoration do %>text-decoration: <%= token_text.text_decoration %>; <% end %>\
     53       <%= if background_color do %>background-color: <%= background_color %><% end %>}\
     54       .<%= highlight_class %> .unselectable {
     55         -webkit-touch-callout: none;
     56         -webkit-user-select: none;
     57         -khtml-user-select: none;
     58         -moz-user-select: none;
     59         -ms-user-select: none;
     60         user-select: none;
     61       }
     62       <%= for {css_class, token_style, token_type} <- styles do %>
     63       .<%= highlight_class %> .<%= css_class %> {\
     64       <%= if token_style.color do %>color: <%= token_style.color %>; <% end %>\
     65       <%= if token_style.font_style do %>font-style: <%= token_style.font_style %>; <% end %>\
     66       <%= if token_style.font_weight do %>font-weight: <%= token_style.font_weight %>; <% end %>\
     67       <%= if token_style.border do %>border: <%= token_style.border %>; <% end %>\
     68       <%= if token_style.text_decoration do %>text-decoration: <%= token_style.text_decoration %>; <% end %>\
     69       <%= if token_style.background_color do %>background-color: <%= token_style.background_color %>; <% end %>\
     70       } /* :<%= Atom.to_string(token_type) %> */\
     71       <% end %>
     72       """,
     73       [:highlight_class, :highlight_color, :background_color, :token_text, :styles]
     74     )
     75 
     76     @doc """
     77     Generate a stylesheet for a style.
     78     """
     79     def stylesheet(style, css_class \\ "highlight") do
     80       token_styles =
     81         style.styles
     82         |> Map.delete(:text)
     83         |> Enum.into([])
     84         |> Enum.map(fn {token_type, token_style} ->
     85           css_class = Makeup.Token.Utils.css_class_for_token_type(token_type)
     86           {css_class, token_style, token_type}
     87         end)
     88         |> Enum.filter(fn {_, token_style, _} ->
     89           Makeup.Styles.HTML.TokenStyle.not_empty?(token_style)
     90         end)
     91         |> Enum.sort()
     92 
     93       token_text = style.styles[:text]
     94 
     95       render_css(
     96         css_class,
     97         style.highlight_color,
     98         style.background_color,
     99         token_text,
    100         token_styles
    101       )
    102     end
    103 
    104     @doc """
    105     Creates a new style.
    106 
    107     Takes care of unspecified token types and inheritance.
    108     Writes and caches a CSS stylesheet for the style.
    109     """
    110     def make_style(options \\ []) do
    111       short_name = Keyword.fetch!(options, :short_name)
    112       long_name = Keyword.fetch!(options, :long_name)
    113       background_color = Keyword.fetch!(options, :background_color)
    114       highlight_color = Keyword.fetch!(options, :highlight_color)
    115       incomplete_style_map = Keyword.fetch!(options, :styles)
    116 
    117       complete_style_map =
    118         Utils.standard_token_types()
    119         |> Enum.map(fn k -> {k, ""} end)
    120         |> Enum.into(%{})
    121         |> Map.merge(incomplete_style_map)
    122         |> Enum.map(fn {k, v} -> {k, TokenStyle.from_string(v)} end)
    123         |> Enum.into(%{})
    124         |> handle_inheritance
    125 
    126       %__MODULE__{
    127         long_name: long_name,
    128         short_name: short_name,
    129         background_color: background_color,
    130         highlight_color: highlight_color,
    131         styles: complete_style_map
    132       }
    133     end
    134   end
    135 
    136   defmodule TokenStyle do
    137     @moduledoc """
    138     A CSS style for a single token.
    139     """
    140 
    141     defstruct font_style: nil,
    142               font_weight: nil,
    143               border: nil,
    144               text_decoration: nil,
    145               color: nil,
    146               background_color: nil,
    147               literal: nil
    148 
    149     @doc """
    150     A `TokenStyle` is considered empty if all its fields are `nil`.
    151 
    152     A CSS class for an empty `TokenStyle` is not rendered in the stylesheet.
    153     This saves a little space and makes the stylesheet more human-readable.
    154     """
    155     def empty?(style) do
    156       not not_empty?(style)
    157     end
    158 
    159     @doc """
    160     A `TokenStyle` is empty if at least a field is not `nil`.
    161 
    162     A CSS class for an empty `TokenStyle` is rendered in the stylesheet.
    163     """
    164     def not_empty?(style) do
    165       style |> Map.from_struct() |> Map.values() |> Enum.any?()
    166     end
    167 
    168     # Foreground color
    169     defp to_attr("#" <> _ = color), do: {:color, color}
    170     # Background color
    171     defp to_attr("bg:" <> color), do: {:background_color, color}
    172     # Border (can only specify border color)
    173     defp to_attr("border:" <> color), do: {:border, color}
    174     # Font weight (bold vs normal)
    175     defp to_attr("bold"), do: {:font_weight, "bold"}
    176     defp to_attr("nobold"), do: {:font_weight, "normal"}
    177     # Font style (italic vs oblique vs normal)
    178     defp to_attr("italic"), do: {:font_style, "italic"}
    179     defp to_attr("oblique"), do: {:font_style, "oblique"}
    180     defp to_attr("noitalic"), do: {:font_style, "normal"}
    181     # Text decoration (underline vs none)
    182     defp to_attr("underline"), do: {:text_decoration, "underline"}
    183     # Unrecognized commands:
    184     defp to_attr(other) do
    185       # Log the command
    186       IO.warn("unknown attribute #{inspect(other)}")
    187       false
    188     end
    189 
    190     @doc """
    191     Creates a `TokenStyle` from string description.
    192 
    193     The string description is highly optimized for the goal of being typed by a human.
    194     The following commands are recognized:
    195 
    196     * `~r/#[0-9a-f]+/` for foreround color
    197     * `~r/bg:#[0-9a-f]+/` for background color
    198     * `~r/border:#[0-9a-f]+/` for border color
    199     * `italic` for `font-style: italic`
    200     * `oblique` for `font-style: oblique`
    201     * `noitalic` for `font-style: normal`
    202     * `underline` for `font-style: underline`
    203 
    204     No other commands are currently recognized.
    205     """
    206     def from_string(str) do
    207       attrs =
    208         str
    209         |> String.split()
    210         |> Enum.map(&to_attr/1)
    211         |> Enum.filter(fn x -> x end)
    212 
    213       struct(TokenStyle, attrs)
    214     end
    215   end
    216 end