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

Issue:
FontSize = 0 issue vlc.vegalite_to_svg()
I encountered an error when converting spec to svg with axes that have labelFontSize = 0. The error message indicates that there was a failure to locate text within an empty SVG element

`vlc.vegalite_to_svg(vl_spec=vl_spec)`

```
ERROR Error: Failed to locate text in SVG:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100" height="100">
    <text x="20" y="50" font-size="0" font-family="sans-serif">000</text>
</svg>
```

```
{
  "layer": [
    {
      "encoding": {
        "x": {
          "axis": {
            "labelFontSize": 0
          }
        },
        "y": {
          "axis": {
            "labelFontSize": 0
          }
        }
      }
    }
  ]
}
```

The workaround was to set `axis: { "labels": False, "labelFontSize": 10 }


Some text collapse in json to svg conversion
For some reason, the attached chart transforms into the attached svg file. It seems like text placement is failing. Vega editor renders it properly.

[P07.json](https://github.com/user-attachments/files/16367233/P07.json)
![P07](https://github.com/user-attachments/assets/69077b5c-ec61-4085-9aa4-0e63139a0aa2)

I also tried with a basic example chart and it worked fine though.


Discussion / hints:
Thanks for the report and workaround @michaelzwong, this is something that we should fix in vl-convert, so going to re-open the issue for tracking.
Thanks for the report @luiscastro193, it does look like something is going wrong with our text measurements. Nothing is obviously unusual about the characters used in the legend titles.

I'll try to take a look soon!

Fix patch:
diff --git a/vl-convert-rs/src/converter.rs b/vl-convert-rs/src/converter.rs
index 59d1896..dfc702c 100644
--- a/vl-convert-rs/src/converter.rs
+++ b/vl-convert-rs/src/converter.rs
@@ -323,7 +323,7 @@ import('{url}').then((sg) => {{
 
         // weight may be string like "bold" or number like 600.
         // Convert number form to string
-        let weight = String(item.fontWeight);
+        let weight = item.fontWeight == null? null: String(item.fontWeight);
         let size = sg.fontSize(item);
         let family = sg.fontFamily(item);
 
@@ -332,7 +332,7 @@ import('{url}').then((sg) => {{
         }}, null, 2);
 
         let fullWidth = op_text_width(text_info);
-        return Math.min(fullWidth, item.limit ?? fullWidth)
+        return item.limit > 0? Math.min(fullWidth, item.limit): fullWidth
     }};
 }})
 "#,
diff --git a/vl-convert-rs/src/text.rs b/vl-convert-rs/src/text.rs
index e1e7832..645b86f 100644
--- a/vl-convert-rs/src/text.rs
+++ b/vl-convert-rs/src/text.rs
@@ -333,6 +333,11 @@ pub fn op_text_width(#[string] text_info_str: String) -> Result<f64, AnyError> {
         Err(err) => bail!("Failed to deserialize text info: {}", err.to_string()),
     };
 
+    // Return width zero for text with non-positive size
+    if text_info.size <= 0.0 {
+        return Ok(0.0);
+    }
+
     // Return width zero for empty strings and missing text
     match &text_info.text {
         Some(Value::String(text)) => {


Test patch:
diff --git a/vl-convert-rs/tests/test_specs.rs b/vl-convert-rs/tests/test_specs.rs
index 3212a0a..64efde1 100644
--- a/vl-convert-rs/tests/test_specs.rs
+++ b/vl-convert-rs/tests/test_specs.rs
@@ -645,6 +645,7 @@ mod test_png_no_theme {
         case("geoScale", 1.0),
         case("table_heatmap", 1.0),
         case("long_text_lable", 1.0),
+        case("gh_174", 1.0),
     )]
     fn test(
         name: &str,
diff --git a/vl-convert-rs/tests/vl-specs/expected/v5_8/gh_174.png b/vl-convert-rs/tests/vl-specs/expected/v5_8/gh_174.png
new file mode 100644
index 0000000..4680572
Binary files /dev/null and b/vl-convert-rs/tests/vl-specs/expected/v5_8/gh_174.png differ
diff --git a/vl-convert-rs/tests/vl-specs/gh_174.vl.json b/vl-convert-rs/tests/vl-specs/gh_174.vl.json
new file mode 100644
index 0000000..2bfdfcc
--- /dev/null
+++ b/vl-convert-rs/tests/vl-specs/gh_174.vl.json
@@ -0,0 +1,159 @@
+{
+  "config": {
+    "view": {"continuousWidth": 300, "continuousHeight": 300},
+    "legend": {"layout": {"expr": "{\"bottom\": {\"anchor\": \"middle\"}}"}},
+    "locale": {
+      "number": {
+        "decimal": ",",
+        "thousands": ".",
+        "grouping": [3],
+        "currency": ["", " €"]
+      }
+    }
+  },
+  "layer": [
+    {
+      "mark": {"type": "bar"},
+      "encoding": {
+        "color": {
+          "field": "Sexo",
+          "legend": {
+            "labelFontSize": 12,
+            "labelLimit": 0,
+            "labelOffset": 2,
+            "offset": 15,
+            "orient": "bottom",
+            "symbolSize": 50
+          },
+          "scale": {"range": ["#B07BD7", "#B1D6AF", "#BF9000"]},
+          "sort": null,
+          "title": null,
+          "type": "nominal"
+        },
+        "xOffset": {"field": "Sexo", "sort": null, "type": "nominal"},
+        "y": {
+          "axis": {
+            "domain": false,
+            "labelAngle": 0,
+            "labelExpr": "split(datum.label, '\\n')",
+            "labelFontSize": 12,
+            "labelLimit": 0,
+            "labelPadding": 8,
+            "ticks": false,
+            "titleFontSize": 12,
+            "titlePadding": 8
+          },
+          "field": "value",
+          "title": "Porcentaje",
+          "type": "quantitative"
+        }
+      },
+      "transform": [{"filter": "datum.Sexo != 'Diferencia'"}]
+    },
+    {
+      "mark": {"type": "line", "strokeWidth": 3},
+      "encoding": {
+        "color": {
+          "field": "Sexo",
+          "legend": {
+            "labelFontSize": 12,
+            "labelLimit": 0,
+            "labelOffset": 2,
+            "offset": 15,
+            "orient": "bottom",
+            "symbolSize": 50
+          },
+          "scale": {"range": ["#B07BD7", "#B1D6AF", "#BF9000"]},
+          "sort": null,
+          "title": null,
+          "type": "nominal"
+        },
+        "y": {
+          "axis": {
+            "domain": false,
+            "labelExpr": "split(datum.label, '\\n')",
+            "labelFontSize": 12,
+            "labelLimit": 0,
+            "labelPadding": 8,
+            "ticks": false,
+            "titleFontSize": 12,
+            "titlePadding": 8
+          },
+          "field": "value",
+          "title": "Brecha de género",
+          "type": "quantitative"
+        }
+      },
+      "transform": [{"filter": "datum.Sexo == 'Diferencia'"}]
+    },
+    {
+      "mark": {"type": "text", "dy": -11, "fontSize": 10.666666666666666},
+      "encoding": {
+        "text": {"field": "value", "format": ".1f", "type": "quantitative"},
+        "xOffset": {"field": "Sexo", "sort": null, "type": "nominal"},
+        "y": {
+          "axis": null,
+          "field": "value",
+          "title": null,
+          "type": "quantitative"
+        }
+      },
+      "transform": [{"filter": "datum.Sexo != 'Diferencia'"}]
+    }
+  ],
+  "data": {"name": "data-d9816b078d38723c826db8427f81174e"},
+  "encoding": {
+    "x": {
+      "axis": {
+        "domain": false,
+        "labelAngle": 0,
+        "labelExpr": "split(datum.label, '\\n')",
+        "labelFontSize": 12,
+        "labelLimit": 0,
+        "labelPadding": 8,
+        "ticks": false,
+        "titleFontSize": 12,
+        "titlePadding": 8
+      },
+      "field": "Categoría",
+      "sort": null,
+      "title": null,
+      "type": "nominal"
+    }
+  },
+  "height": 250,
+  "resolve": {"scale": {"y": "independent"}},
+  "width": 500,
+  "$schema": "https://vega.github.io/schema/vega-lite/v5.17.0.json",
+  "datasets": {
+    "data-d9816b078d38723c826db8427f81174e": [
+      {"Categoría": "Mala", "Sexo": "Mujeres", "value": 4.888938288354638},
+      {"Categoría": "Regular", "Sexo": "Mujeres", "value": 17.20105746322346},
+      {"Categoría": "Buena", "Sexo": "Mujeres", "value": 47.73831733721285},
+      {"Categoría": "Muy buena", "Sexo": "Mujeres", "value": 22.48178472726535},
+      {"Categoría": "Excelente", "Sexo": "Mujeres", "value": 7.689902183943703},
+      {"Categoría": "Mala", "Sexo": "Hombres", "value": 3.290678903385191},
+      {"Categoría": "Regular", "Sexo": "Hombres", "value": 13.19356745004503},
+      {"Categoría": "Buena", "Sexo": "Hombres", "value": 45.60874073590572},
+      {"Categoría": "Muy buena", "Sexo": "Hombres", "value": 26.48511010933306},
+      {"Categoría": "Excelente", "Sexo": "Hombres", "value": 11.421902801331},
+      {"Categoría": "Mala", "Sexo": "Diferencia", "value": 1.598259384969446},
+      {
+        "Categoría": "Regular",
+        "Sexo": "Diferencia",
+        "value": 4.007490013178437
+      },
+      {"Categoría": "Buena", "Sexo": "Diferencia", "value": 2.129576601307129},
+      {
+        "Categoría": "Muy buena",
+        "Sexo": "Diferencia",
+        "value": -4.003325382067715
+      },
+      {
+        "Categoría": "Excelente",
+        "Sexo": "Diferencia",
+        "value": -3.732000617387293
+      }
+    ]
+  }
+}
\ No newline at end of file