Repository: vega/vl-convert (language: python)

Issue:
Valid spec using `labelExpr` and `tickBand` results in `ValueError: Vega to SVG conversion failed`
Hello.  Thank you for this wonderful tool.  I'm the author of [a library](http://github.com/moj-analytical-services/splink) which makes extensive use of Altair charts (though note the Altair specs are actually stored as vega-lite specs and loaded into Altair using `alt.Chart().from_dict`)

`vl-convert-python` is working flawlessly on all of our charts except one.  The spec of our chart is complex but I've managed to boil it down to the following reprex: [open the Chart in the Vega Editor](https://vega.github.io/editor/#/url/vega-lite/N4IgJAzgxgFgpgWwIYgFwhgF0wBwqgegIDc4BzJAOjIEtMYBXAI0poHsDp5kTykBaADZ04JAKyUAnJQDMlAFYQ2AOxAAaEABMkmFKlDEkghnAhoA2qChtjCZQH1lSBHDQgACgCd2n9SByecMTsDBD2EAwIaACMlABMGoJsZHH2TEgAnqb2AGZIUJhsvqhxlAAsGobGrug0ToIABNBFrgC+ALqtiZlwxZYgyJ4A1migmBk4NSDpvhoA7jSa9GgAbAAMXSBwytaadWSjIAAeh+OTbspsCHVGfkhHNGb6IIJITHCCAKJHAW7amJFKFUTA0ALyghoAci8PkhDQAPvCGv9AcC4GCIZCAGI3RrNQJwgD8ULhqGROlRRhMfkwNCgQwAQkhlJo3HAjphtpgQJscjQPqz0NZbA4nC4eRoMqcJlMAI4MZm03S00h+PkCtwBIIhMIRKKtA2dIA)

Note this has:
![image](https://github.com/vega/vl-convert/assets/2608005/29d0d969-b2fb-41c9-97ba-233be46cab08)


<details><summary>Expand me for full json spec</summary>

```json
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.9.3.json",
  "data": {
    "values": [
      {
        "column_name": "Prior",
        "previous_sum": 1.2,
        "log2_bayes_factor": 2.4,
        "value": "inal score"
      }
    ]
  },
  "layer": [
    {
      "mark": {
        "type": "bar",
        "width": 60
      },
      "encoding": {
        "x": {
          "type": "nominal",
          "axis": {
            "labelExpr": "datum.value == 'Prior' || datum.value == 'Final score' ? '' : datum.value",
            "tickBand": "extent"
          },
          "field": "column_name"
        },
        "y": {
          "type": "quantitative",
          "field": "previous_sum"
        }
      }
    }
  ]
}
```
</details>


```python
import altair as alt
import json

with open("replicate_vl.json") as f:
    data = json.load(f)

chart = alt.Chart().from_dict(data)
chart.save("delete.png")
```

This results in the following error:

```
ValueError: Vega to SVG conversion failed:
TypeError: Cannot read properties of undefined (reading 'marktype')
```

<details><summary>full error</summary>

```
ERROR Error: Failed to deserialize text info: missing field `text` at line 5 column 1
    at Object.sg.textMetrics.width (<anon>:17:45)
    at Ve (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:1:30235)
    at J (https://cdn.skypack.dev/-/vega-view-transforms@v4.5.9-LiB26zIbxiHZW70fnrDI/dist=es2020,mode=imports,min/optimized/vega-view-transforms.js:1:1588)
    at https://cdn.skypack.dev/-/vega-view-transforms@v4.5.9-LiB26zIbxiHZW70fnrDI/dist=es2020,mode=imports,min/optimized/vega-view-transforms.js:1:1278
    at Array.forEach (<anonymous>)
    at Mt.transform (https://cdn.skypack.dev/-/vega-view-transforms@v4.5.9-LiB26zIbxiHZW70fnrDI/dist=es2020,mode=imports,min/optimized/vega-view-transforms.js:1:1259)
    at Mt.evaluate (https://cdn.skypack.dev/-/vega-dataflow@v5.7.5-asKYS4gpPLMPf64pSozt/dist=es2020,mode=imports,min/optimized/vega-dataflow.js:1:15429)
    at Mt.run (https://cdn.skypack.dev/-/vega-dataflow@v5.7.5-asKYS4gpPLMPf64pSozt/dist=es2020,mode=imports,min/optimized/vega-dataflow.js:1:15286)
    at ne.Gt [as evaluate] (https://cdn.skypack.dev/-/vega-dataflow@v5.7.5-asKYS4gpPLMPf64pSozt/dist=es2020,mode=imports,min/optimized/vega-dataflow.js:1:12073)
    at ne.evaluate (https://cdn.skypack.dev/-/vega-view@v5.11.1-FQ9r1BvJOMHegkomXDyj/dist=es2020,mode=imports,min/optimized/vega-view.js:2:1621)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
[/Users/robinlinacre/Documents/data_linking/splink/try_replicate_vl.py](https://file+.vscode-resource.vscode-cdn.net/Users/robinlinacre/Documents/data_linking/splink/try_replicate_vl.py) in line 8
      [6](file:///Users/robinlinacre/Documents/data_linking/splink/try_replicate_vl.py?line=5) chart = alt.Chart().from_dict(data)
      [7](file:///Users/robinlinacre/Documents/data_linking/splink/try_replicate_vl.py?line=6) chart
----> [8](file:///Users/robinlinacre/Documents/data_linking/splink/try_replicate_vl.py?line=7) chart.save("delete.png")

File [~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/vegalite/v5/api.py:948](https://file+.vscode-resource.vscode-cdn.net/Users/robinlinacre/Documents/data_linking/splink/~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/vegalite/v5/api.py:948), in TopLevelMixin.save(self, fp, format, override_data_transformer, scale_factor, vegalite_version, vega_version, vegaembed_version, **kwargs)
    [946](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/vegalite/v5/api.py?line=945) if override_data_transformer:
    [947](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/vegalite/v5/api.py?line=946)     with data_transformers.disable_max_rows():
--> [948](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/vegalite/v5/api.py?line=947)         result = save(**kwds)
    [949](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/vegalite/v5/api.py?line=948) else:
    [950](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/vegalite/v5/api.py?line=949)     result = save(**kwds)

File [~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py:154](https://file+.vscode-resource.vscode-cdn.net/Users/robinlinacre/Documents/data_linking/splink/~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py:154), in save(chart, fp, vega_version, vegaembed_version, format, mode, vegalite_version, embed_options, json_kwds, webdriver, scale_factor, engine, inline, **kwargs)
    [152](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=151)     write_file_or_filename(fp, mimebundle["text/html"], mode="w")
    [153](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=152) elif format in ["png", "svg", "pdf", "vega"]:
--> [154](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=153)     mimebundle = spec_to_mimebundle(
    [155](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=154)         spec=spec,
    [156](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=155)         format=format,
    [157](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=156)         mode=mode,
    [158](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=157)         vega_version=vega_version,
    [159](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=158)         vegalite_version=vegalite_version,
    [160](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=159)         vegaembed_version=vegaembed_version,
    [161](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=160)         webdriver=webdriver,
    [162](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=161)         scale_factor=scale_factor,
    [163](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=162)         engine=engine,
    [164](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=163)         **kwargs,
    [165](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=164)     )
    [166](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=165)     if format == "png":
    [167](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/save.py?line=166)         write_file_or_filename(fp, mimebundle["image/png"], mode="wb")

File [~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py:51](https://file+.vscode-resource.vscode-cdn.net/Users/robinlinacre/Documents/data_linking/splink/~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py:51), in spec_to_mimebundle(spec, format, mode, vega_version, vegaembed_version, vegalite_version, engine, **kwargs)
     [48](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=47)     raise ValueError("mode must be 'vega-lite'")
     [50](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=49) if format in ["png", "svg", "pdf", "vega"]:
---> [51](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=50)     return _spec_to_mimebundle_with_engine(
     [52](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=51)         spec, format, mode, engine=engine, **kwargs
     [53](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=52)     )
     [54](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=53) if format == "html":
     [55](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=54)     html = spec_to_html(
     [56](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=55)         spec,
     [57](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=56)         mode=mode,
   (...)
     [61](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=60)         **kwargs,
     [62](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=61)     )

File [~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py:111](https://file+.vscode-resource.vscode-cdn.net/Users/robinlinacre/Documents/data_linking/splink/~/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py:111), in _spec_to_mimebundle_with_engine(spec, format, mode, **kwargs)
    [109](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=108)     return {"image/svg+xml": svg}
    [110](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=109) elif format == "png":
--> [111](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=110)     png = vlc.vegalite_to_png(
    [112](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=111)         spec,
    [113](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=112)         vl_version=vl_version,
    [114](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=113)         scale=kwargs.get("scale_factor", 1.0),
    [115](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=114)     )
    [116](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=115)     return {"image/png": png}
    [117](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=116) else:
    [118](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=117)     # This should be validated above
    [119](file:///Users/robinlinacre/Documents/data_linking/splink/.venv/lib/python3.8/site-packages/altair/utils/mimebundle.py?line=118)     # but raise exception for the sake of future development

ValueError: Vega to SVG conversion failed:
TypeError: Cannot read properties of undefined (reading 'marktype')
    at sn.mark (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:2:1445)
    at https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:2:2054
    at L (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:1:19746)
    at o (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:2:2042)
    at L (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:1:19746)
    at sn.mark (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:2:2323)
    at https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:2:2054
    at L (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:1:19746)
    at o (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:2:2042)
    at L (https://cdn.skypack.dev/-/vega-scenegraph@v4.10.2-W1dltMWN7mO3TARazJA1/dist=es2020,mode=imports,min/optimized/vega-scenegraph.js:1:19746)
```
</details>

I'm using:
```
vl-convert-python==0.10.2
altair==5.0.1
```
and I'm on Python 3.8.3 on Mac OS Catalina (though I note it was one of our users who originally reported this problem, who's using Windows)


I note that if I delete _either_ `labelExpr` or `tickBand`, the error no longer occurs.  i.e. it only occurs when both are included in the spec.
Apologies if I've made a mistake and the error is my own!


Discussion / hints:

Fix patch:
diff --git a/DEVELOP.md b/DEVELOP.md
index 936d9db8..c6ec9197 100644
--- a/DEVELOP.md
+++ b/DEVELOP.md
@@ -37,7 +37,7 @@ This will produce `target/release/vl-convert`, which should be uploaded to the G
 ### Build Python wheels
 Build the Python wheels with:
 ```
-maturin -m vl-convert-python/Cargo.toml --release -i python3.11 -i python3.10 -i python3.9 -i python3.8 -i python3.7 --strip 
+maturin build -m vl-convert-python/Cargo.toml --release -i python3.11 -i python3.10 -i python3.9 -i python3.8 -i python3.7 --strip 
 ```
 
 This will produce a collection of wheel files in `target/wheels`, which should be uploaded to the GitHub Release below. These wheels must also be uploaded to PyPI with:
diff --git a/vl-convert-rs/src/text.rs b/vl-convert-rs/src/text.rs
index fd01780e..44a59e44 100644
--- a/vl-convert-rs/src/text.rs
+++ b/vl-convert-rs/src/text.rs
@@ -105,7 +105,7 @@ struct TextInfo {
     weight: Option<String>,
     family: Option<String>,
     size: f64,
-    text: Value,
+    text: Option<Value>,
 }
 
 impl TextInfo {
@@ -136,8 +136,9 @@ impl TextInfo {
         let mut escaped_text = String::new();
 
         let text = match &self.text {
-            Value::String(s) => s.to_string(),
-            text => text.to_string(),
+            Some(Value::String(s)) => s.to_string(),
+            Some(text) => text.to_string(),
+            None => "".to_string(),
         };
 
         for char in text.chars() {
@@ -181,10 +182,17 @@ pub fn op_text_width(text_info_str: String) -> Result<f64, AnyError> {
         Err(err) => bail!("Failed to deserialize text info: {}", err.to_string()),
     };
 
-    if let Some(text) = text_info.text.as_str() {
-        if text.trim().is_empty() {
+    // Return width zero for empty strings and missing text
+    match &text_info.text {
+        Some(Value::String(text)) => {
+            if text.trim().is_empty() {
+                return Ok(0.0);
+            }
+        }
+        None => {
             return Ok(0.0);
         }
+        _ => {}
     }
 
     let svg = text_info.to_svg();


Test patch:
diff --git a/vl-convert-python/tests/test_specs.py b/vl-convert-python/tests/test_specs.py
index 439615f7..c1814ca3 100644
--- a/vl-convert-python/tests/test_specs.py
+++ b/vl-convert-python/tests/test_specs.py
@@ -99,6 +99,7 @@ def test_svg(name, as_dict):
         ("stacked_bar_h", 2.0),
         ("remote_images", 1.0),
         ("maptile_background", 1.0),
+        ("no_text_in_font_metrics", 1.0),
     ],
 )
 @pytest.mark.parametrize("as_dict", [False])
diff --git a/vl-convert-rs/tests/test_specs.rs b/vl-convert-rs/tests/test_specs.rs
index aeab40be..e1c55eae 100644
--- a/vl-convert-rs/tests/test_specs.rs
+++ b/vl-convert-rs/tests/test_specs.rs
@@ -101,9 +101,9 @@ fn make_expected_svg_path(name: &str, vl_version: VlVersion, theme: Option<&str>
         })
 }
 
-fn load_expected_svg(name: &str, vl_version: VlVersion, theme: Option<&str>) -> String {
+fn load_expected_svg(name: &str, vl_version: VlVersion, theme: Option<&str>) -> Option<String> {
     let spec_path = make_expected_svg_path(name, vl_version, theme);
-    fs::read_to_string(&spec_path).unwrap_or_else(|_| panic!("Failed to read {:?}", spec_path))
+    fs::read_to_string(&spec_path).ok()
 }
 
 fn write_failed_svg(name: &str, vl_version: VlVersion, theme: Option<&str>, img: &str) -> PathBuf {
@@ -129,7 +129,7 @@ fn write_failed_svg(name: &str, vl_version: VlVersion, theme: Option<&str>, img:
 
 fn check_svg(name: &str, vl_version: VlVersion, theme: Option<&str>, img: &str) {
     let expected = load_expected_svg(name, vl_version, theme);
-    if img != expected {
+    if Some(img.to_string()) != expected {
         let path = write_failed_svg(name, vl_version, None, img);
         panic!(
             "Images don't match for {}.svg. Failed image written to {:?}",
@@ -156,9 +156,9 @@ fn load_expected_png_dssim(
     name: &str,
     vl_version: VlVersion,
     theme: Option<&str>,
-) -> DssimImage<f32> {
+) -> Option<DssimImage<f32>> {
     let spec_path = make_expected_png_path(name, vl_version, theme);
-    dssim::load_image(&Dssim::new(), spec_path).unwrap()
+    dssim::load_image(&Dssim::new(), spec_path).ok()
 }
 
 fn to_dssim(img: &[u8]) -> DssimImage<f32> {
@@ -190,16 +190,24 @@ fn write_failed_png(name: &str, vl_version: VlVersion, theme: Option<&str>, img:
 
 fn check_png(name: &str, vl_version: VlVersion, theme: Option<&str>, img: &[u8]) {
     let expected_dssim = load_expected_png_dssim(name, vl_version, theme);
-    let img_dssim = to_dssim(img);
-
-    let attr = Dssim::new();
-    let (diff, _) = attr.compare(&expected_dssim, img_dssim);
-
-    if diff > 0.0001 {
-        println!("DSSIM diff {diff}");
+    if let Some(expected_dssim) = expected_dssim {
+        let img_dssim = to_dssim(img);
+
+        let attr = Dssim::new();
+        let (diff, _) = attr.compare(&expected_dssim, img_dssim);
+
+        if diff > 0.0001 {
+            println!("DSSIM diff {diff}");
+            let path = write_failed_png(name, vl_version, None, img);
+            panic!(
+                "Images don't match for {}.png. Failed image written to {:?}",
+                name, path
+            )
+        }
+    } else {
         let path = write_failed_png(name, vl_version, None, img);
         panic!(
-            "Images don't match for {}.png. Failed image written to {:?}",
+            "Baseline image does not exist for {}.png. Failed image written to {:?}",
             name, path
         )
     }
@@ -267,6 +275,7 @@ mod test_svg {
             "line_with_log_scale",
             "numeric_font_weight",
             "float_font_size",
+            "no_text_in_font_metrics"
         )]
         name: &str,
     ) {
@@ -311,6 +320,7 @@ mod test_png_no_theme {
         case("remote_images", 1.0),
         case("maptile_background", 1.0),
         case("float_font_size", 1.0),
+        case("no_text_in_font_metrics", 1.0),
     )]
     fn test(
         name: &str,
diff --git a/vl-convert-rs/tests/vl-specs/expected/v5_8/no_text_in_font_metrics.png b/vl-convert-rs/tests/vl-specs/expected/v5_8/no_text_in_font_metrics.png
new file mode 100644
index 00000000..05f0f924
Binary files /dev/null and b/vl-convert-rs/tests/vl-specs/expected/v5_8/no_text_in_font_metrics.png differ
diff --git a/vl-convert-rs/tests/vl-specs/expected/v5_8/no_text_in_font_metrics.svg b/vl-convert-rs/tests/vl-specs/expected/v5_8/no_text_in_font_metrics.svg
new file mode 100644
index 00000000..9fbc41cb
--- /dev/null
+++ b/vl-convert-rs/tests/vl-specs/expected/v5_8/no_text_in_font_metrics.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" class="marks" width="93" height="237" viewBox="0 0 93 237"><rect width="93" height="237" fill="white"/><g fill="none" stroke-miterlimit="10" transform="translate(41,10)"><g class="mark-group role-frame root" role="graphics-object" aria-roledescription="group mark container"><g transform="translate(0,0)"><path class="background" aria-hidden="true" d="M0.5,0.5h20v200h-20Z" stroke="#ddd"/><g><g class="mark-group role-axis" aria-hidden="true"><g transform="translate(0.5,0.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-rule role-axis-grid" pointer-events="none"><line transform="translate(0,200)" x2="20" y2="0" stroke="#ddd" stroke-width="1" opacity="1"/><line transform="translate(0,167)" x2="20" y2="0" stroke="#ddd" stroke-width="1" opacity="1"/><line transform="translate(0,133)" x2="20" y2="0" stroke="#ddd" stroke-width="1" opacity="1"/><line transform="translate(0,100)" x2="20" y2="0" stroke="#ddd" stroke-width="1" opacity="1"/><line transform="translate(0,67)" x2="20" y2="0" stroke="#ddd" stroke-width="1" opacity="1"/><line transform="translate(0,33)" x2="20" y2="0" stroke="#ddd" stroke-width="1" opacity="1"/><line transform="translate(0,0)" x2="20" y2="0" stroke="#ddd" stroke-width="1" opacity="1"/></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-group role-axis" role="graphics-symbol" aria-roledescription="axis" aria-label="X-axis titled 'column_name' for a discrete scale with 1 value: Prior"><g transform="translate(0.5,200.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-rule role-axis-tick" pointer-events="none"><line transform="translate(19,0)" x2="0" y2="5" stroke="#888" stroke-width="1" opacity="1"/><line transform="translate(1,0)" x2="0" y2="5" stroke="#888" stroke-width="1" opacity="1"/></g><g class="mark-text role-axis-label" pointer-events="none"><text text-anchor="end" transform="translate(10,7) rotate(270) translate(0,3)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1"/><text text-anchor="end" transform="translate(0,7) rotate(270) translate(0,3)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1"/></g><g class="mark-rule role-axis-domain" pointer-events="none"><line transform="translate(0,0)" x2="20" y2="0" stroke="#888" stroke-width="1" opacity="1"/></g><g class="mark-text role-axis-title" pointer-events="none"><text text-anchor="middle" transform="translate(10,20)" font-family="sans-serif" font-size="11px" font-weight="bold" fill="#000" opacity="1">column_name</text></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-group role-axis" role="graphics-symbol" aria-roledescription="axis" aria-label="Y-axis titled 'previous_sum' for a linear scale with values from 0.0 to 1.2"><g transform="translate(0.5,0.5)"><path class="background" aria-hidden="true" d="M0,0h0v0h0Z" pointer-events="none"/><g><g class="mark-rule role-axis-tick" pointer-events="none"><line transform="translate(0,200)" x2="-5" y2="0" stroke="#888" stroke-width="1" opacity="1"/><line transform="translate(0,167)" x2="-5" y2="0" stroke="#888" stroke-width="1" opacity="1"/><line transform="translate(0,133)" x2="-5" y2="0" stroke="#888" stroke-width="1" opacity="1"/><line transform="translate(0,100)" x2="-5" y2="0" stroke="#888" stroke-width="1" opacity="1"/><line transform="translate(0,67)" x2="-5" y2="0" stroke="#888" stroke-width="1" opacity="1"/><line transform="translate(0,33)" x2="-5" y2="0" stroke="#888" stroke-width="1" opacity="1"/><line transform="translate(0,0)" x2="-5" y2="0" stroke="#888" stroke-width="1" opacity="1"/></g><g class="mark-text role-axis-label" pointer-events="none"><text text-anchor="end" transform="translate(-7,203)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1">0.0</text><text text-anchor="end" transform="translate(-7,169.66666666666666)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1">0.2</text><text text-anchor="end" transform="translate(-7,136.33333333333331)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1">0.4</text><text text-anchor="end" transform="translate(-7,103)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1">0.6</text><text text-anchor="end" transform="translate(-7,69.66666666666666)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1">0.8</text><text text-anchor="end" transform="translate(-7,36.33333333333333)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1">1.0</text><text text-anchor="end" transform="translate(-7,3)" font-family="sans-serif" font-size="10px" fill="#000" opacity="1">1.2</text></g><g class="mark-rule role-axis-domain" pointer-events="none"><line transform="translate(0,200)" x2="0" y2="-200" stroke="#888" stroke-width="1" opacity="1"/></g><g class="mark-text role-axis-title" pointer-events="none"><text text-anchor="middle" transform="translate(-24.9013671875,100) rotate(-90) translate(0,-2)" font-family="sans-serif" font-size="11px" font-weight="bold" fill="#000" opacity="1">previous_sum</text></g></g><path class="foreground" aria-hidden="true" d="" pointer-events="none" display="none"/></g></g><g class="mark-rect role-mark layer_0_marks" role="graphics-object" aria-roledescription="rect mark container"><path aria-label="column_name: Prior; previous_sum: 1.2" role="graphics-symbol" aria-roledescription="bar" d="M-20,0h60v200h-60Z" fill="#4c78a8"/></g></g><path class="foreground" aria-hidden="true" d="" display="none"/></g></g></g></svg>
\ No newline at end of file
diff --git a/vl-convert-rs/tests/vl-specs/no_text_in_font_metrics.vl.json b/vl-convert-rs/tests/vl-specs/no_text_in_font_metrics.vl.json
new file mode 100644
index 00000000..e253c978
--- /dev/null
+++ b/vl-convert-rs/tests/vl-specs/no_text_in_font_metrics.vl.json
@@ -0,0 +1,30 @@
+{
+  "$schema": "https://vega.github.io/schema/vega-lite/v5.9.3.json",
+  "description": "https://github.com/vega/vl-convert/issues/72",
+  "data": {
+    "values": [
+      {
+        "column_name": "Prior",
+        "previous_sum": 1.2,
+        "log2_bayes_factor": 2.4,
+        "value": "inal score"
+      }
+    ]
+  },
+  "layer": [
+    {
+      "mark": {"type": "bar", "width": 60},
+      "encoding": {
+        "x": {
+          "type": "nominal",
+          "axis": {
+            "labelExpr": "datum.value == 'Prior' || datum.value == 'Final score' ? '' : datum.value",
+            "tickBand": "extent"
+          },
+          "field": "column_name"
+        },
+        "y": {"type": "quantitative", "field": "previous_sum"}
+      }
+    }
+  ]
+}