diff --git a/CHANGELOG.md b/CHANGELOG.md
index add8e2f..5f8f365 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,10 +11,11 @@ I.e. `mjml_nif 0.x` versions use mrml versions `>= 0.1, < 1.0.0`, and `mjml_nif
## [Unreleased]
### Changed
-- Use rustler_precompiled v0.7.0 + drop support for Elixir < v1.12
+- Use rustler_precompiled v0.7.1 + drop support for Elixir < v1.12
- Use Rust edition 2021
- Use rustler v0.30.0
- Drop arm-unknown-linux-gnueabihf as precompiled target
+- Use [mrml] v.2.1.1
---
diff --git a/README.md b/README.md
index 6d6086a..8fd1be9 100644
--- a/README.md
+++ b/README.md
@@ -49,13 +49,21 @@ Available rendering options are:
* `keep_comments` – when `false`, removes comments from the final HTML. Defaults to `true`.
* `social_icon_path` – when given, uses this base path to generate social icon URLs.
+* `fonts` – a Map of font names and their URLs to a hosted CSS file.
+ When given, includes these fonts in the rendered HTML
+ (Note that only actually used fonts will show up!).
+ Defaults to `nil`, which will make the default font families available to
+ be used (Open Sans, Droid Sans, Lato, Roboto, and Ubuntu).
```elixir
mjml = "..."
opts = [
keep_comments: false,
- social_icon_path: "https://example.com/icons/"
+ social_icon_path: "https://example.com/icons/",
+ fonts: %{
+ "Noto Color Emoji": "https://fonts.googleapis.com/css?family=Noto+Color+Emoji:400"
+ }
]
{:ok, html} = Mjml.to_html(mjml, opts)
diff --git a/lib/mjml.ex b/lib/mjml.ex
index 1af4459..7dca491 100644
--- a/lib/mjml.ex
+++ b/lib/mjml.ex
@@ -20,6 +20,12 @@ defmodule Mjml do
E.g. `social_icon_path: "https://example.com/icons/"` will generate a social icon
URL, like "https://example.com/icons/github.png"
+ * `fonts` – a Map of font names and their URLs to hosted CSS files.
+ When given, includes these fonts in the rendered HTML
+ (Note that only actually used fonts will show up!).
+ Defaults to `nil`, which will make the default font families available to
+ be used (Open Sans, Droid Sans, Lato, Roboto, and Ubuntu).
+
## Examples
iex> Mjml.to_html("")
@@ -28,7 +34,13 @@ defmodule Mjml do
iex> Mjml.to_html("something not MJML")
{:error, "Couldn't convert MJML template"}
- iex> opts = [keep_comments: false, social_icon_path: "https://example.com/icons/"]
+ iex> opts = [
+ iex> keep_comments: false,
+ iex> social_icon_path: "https://example.com/icons/"
+ iex> fonts: %{
+ iex> "Noto Color Emoji": "https://fonts.googleapis.com/css?family=Noto+Color+Emoji:400"
+ iex> }
+ iex> ]
iex> Mjml.to_html("", opts)
{:ok, " {
pub keep_comments: bool,
- pub social_icon_path: Option
+ pub social_icon_path: Option,
+ pub fonts: Option, Term<'a>>>
}
#[rustler::nif]
-pub fn to_html<'a>(env: Env<'a>, mjml: String, render_options: Options) -> NifResult> {
+pub fn to_html<'a>(env: Env<'a>, mjml: String, render_options: RenderOptions) -> NifResult> {
return match mrml::parse(&mjml) {
Ok(root) => {
- let options = mrml::prelude::render::Options{
+ let options = mrml::prelude::render::RenderOptions{
disable_comments: !render_options.keep_comments,
- social_icon_origin: render_options.social_icon_path
+ social_icon_origin: social_icon_origin_option(render_options.social_icon_path),
+ fonts: fonts_option(render_options.fonts)
};
return match root.render(&options) {
@@ -33,4 +38,49 @@ pub fn to_html<'a>(env: Env<'a>, mjml: String, render_options: Options) -> NifRe
};
}
+fn social_icon_origin_option(option_value: Option) -> Option> {
+ option_value.map_or(
+ mrml::prelude::render::RenderOptions::default().social_icon_origin,
+ |origin| Some(Owned(origin))
+ )
+}
+
+fn fonts_option<'a>(option_values: Option, Term<'a>>>) -> HashMap> {
+ option_values.map_or(
+ mrml::prelude::render::RenderOptions::default().fonts,
+ |fonts| -> HashMap> {
+ let mut options : HashMap> = HashMap::new();
+
+ for (key, value) in fonts {
+ let (k, v) = font_option(key, value);
+ options.insert(k, v);
+ }
+
+ return options
+ }
+ )
+}
+
+fn font_option<'a>(key: Term<'a>, value: Term<'a>) -> (String, Cow<'static, str>) {
+ (
+ match key.atom_to_string() {
+ Ok(s) => s,
+ Err(_) => panic!(
+ "Keys for the `fonts` render option must be of type Atom, got {:?}.
+ Please use a Map like this: %{{\"My Font Name\": \"https://myfonts.example.com/css\"}}",
+ key.get_type()
+ )
+
+ },
+ match value.decode::() {
+ Ok(s) => Owned(s),
+ Err(_) => panic!(
+ "Values for the `fonts` render option must be of type String, got {:?}.
+ Please use a Map like this: %{{\"My Font Name\": \"https://myfonts.example.com/css\"}}",
+ value.get_type()
+ )
+ }
+ )
+}
+
rustler::init!("Elixir.Mjml.Native", [to_html]);
diff --git a/test/mjml_test.exs b/test/mjml_test.exs
index a74d24a..54541af 100644
--- a/test/mjml_test.exs
+++ b/test/mjml_test.exs
@@ -31,10 +31,10 @@ defmodule MjmlTest do
assert String.starts_with?(message, "unexpected element")
assert {:error, message} = Mjml.to_html("not MJML")
- assert String.starts_with?(message, "parsing error: unknown token")
+ assert String.starts_with?(message, "unable to load included template")
assert {:error, message} = Mjml.to_html("//invalid-element>")
- assert String.starts_with?(message, "parsing error: invalid element")
+ assert String.starts_with?(message, "unable to load included template")
end
describe "when passing options" do
@@ -76,5 +76,67 @@ defmodule MjmlTest do
assert html =~ "#{social_icon_path}github.png"
assert html =~ "#{social_icon_path}twitter.png"
end
+
+ test "`fonts: %{\"font name\": \"font URL\", ...}` includes the given fonts" do
+ fonts = %{
+ "My Font": "https://myfontserver.example.com/css?family=My+Font:300,400,500,700",
+ "Your Font": "https://yourfontserver.example.com/css?family=Your+Font:300,400,500,700"
+ }
+
+ mjml = """
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+ """
+
+ assert {:ok, html} = Mjml.to_html(mjml, fonts: fonts)
+
+ [font_name_a, font_name_b] = fonts |> Map.keys()
+ [font_url_a, font_url_b] = fonts |> Map.values()
+
+ assert html =~ font_name_a |> Atom.to_string()
+ assert html =~ font_url_a
+
+ assert html =~ font_name_b |> Atom.to_string()
+ assert html =~ font_url_b
+ end
+
+ test "not providing `fonts` allows using the default fonts" do
+ mjml = """
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+ """
+
+ assert {:ok, html} = Mjml.to_html(mjml)
+ assert html =~ "Open Sans"
+ assert html =~ "https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700"
+ end
end
end