From 70c6dd7254b886bb6196f83c5901a8b92a658898 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 29 Dec 2023 13:22:07 -0500 Subject: [PATCH] Add font weight support and add test (still no text rotation support) --- .../vega-specs/text/bar_axis_labels.vg.json | 135 +++++ vega-wgpu-renderer/src/renderers/text.rs | 27 +- vega-wgpu-renderer/src/scene/text.rs | 4 +- .../specs/text/bar_axis_labels.dims.json | 6 + .../tests/specs/text/bar_axis_labels.png | Bin 0 -> 13789 bytes .../tests/specs/text/bar_axis_labels.sg.json | 567 ++++++++++++++++++ .../tests/test_image_baselines.rs | 3 +- 7 files changed, 733 insertions(+), 9 deletions(-) create mode 100644 gen-test-data/vega-specs/text/bar_axis_labels.vg.json create mode 100644 vega-wgpu-renderer/tests/specs/text/bar_axis_labels.dims.json create mode 100644 vega-wgpu-renderer/tests/specs/text/bar_axis_labels.png create mode 100644 vega-wgpu-renderer/tests/specs/text/bar_axis_labels.sg.json diff --git a/gen-test-data/vega-specs/text/bar_axis_labels.vg.json b/gen-test-data/vega-specs/text/bar_axis_labels.vg.json new file mode 100644 index 0000000..e3cf559 --- /dev/null +++ b/gen-test-data/vega-specs/text/bar_axis_labels.vg.json @@ -0,0 +1,135 @@ +{ + "$schema": "https://vega.github.io/schema/vega/v5.json", + "description": "A bar graph showing what activities consume what percentage of the day.", + "background": "white", + "padding": 5, + "width": 200, + "style": "cell", + "config": {"style": {"cell": {"stroke": "transparent"}}}, + "data": [ + { + "name": "source_0", + "values": [ + {"Activity": "Sleeping", "Time": 8}, + {"Activity": "Eating", "Time": 2}, + {"Activity": "TV", "Time": 4}, + {"Activity": "Work", "Time": 8}, + {"Activity": "Exercise", "Time": 2} + ] + }, + { + "name": "data_0", + "source": "source_0", + "transform": [ + { + "type": "joinaggregate", + "as": ["TotalTime"], + "ops": ["sum"], + "fields": ["Time"] + }, + { + "type": "formula", + "expr": "datum.Time/datum.TotalTime * 100", + "as": "PercentOfTotal" + }, + { + "type": "stack", + "groupby": ["Activity"], + "field": "PercentOfTotal", + "sort": {"field": [], "order": []}, + "as": ["PercentOfTotal_start", "PercentOfTotal_end"], + "offset": "zero" + }, + { + "type": "filter", + "expr": "isValid(datum[\"PercentOfTotal\"]) && isFinite(+datum[\"PercentOfTotal\"])" + } + ] + } + ], + "signals": [ + {"name": "y_step", "value": 12}, + { + "name": "height", + "update": "bandspace(domain('y').length, 0.1, 0.05) * y_step" + } + ], + "marks": [ + { + "name": "marks", + "type": "rect", + "style": ["bar"], + "from": {"data": "data_0"}, + "encode": { + "update": { + "fill": {"value": "#4c78a8"}, + "ariaRoleDescription": {"value": "bar"}, + "description": { + "signal": "\"% of total Time: \" + (format(datum[\"PercentOfTotal\"], \"\")) + \"; Activity: \" + (isValid(datum[\"Activity\"]) ? datum[\"Activity\"] : \"\"+datum[\"Activity\"])" + }, + "x": {"scale": "x", "field": "PercentOfTotal_end"}, + "x2": {"scale": "x", "field": "PercentOfTotal_start"}, + "y": {"scale": "y", "field": "Activity"}, + "height": {"signal": "max(0.25, bandwidth('y'))"} + } + } + } + ], + "scales": [ + { + "name": "x", + "type": "linear", + "domain": { + "data": "data_0", + "fields": ["PercentOfTotal_start", "PercentOfTotal_end"] + }, + "range": [0, {"signal": "width"}], + "nice": true, + "zero": true + }, + { + "name": "y", + "type": "band", + "domain": {"data": "data_0", "field": "Activity", "sort": true}, + "range": {"step": {"signal": "y_step"}}, + "paddingInner": 0.1, + "paddingOuter": 0.05 + } + ], + "axes": [ + { + "scale": "x", + "orient": "bottom", + "gridScale": "y", + "grid": true, + "tickCount": {"signal": "ceil(width/40)"}, + "domain": false, + "labels": false, + "aria": false, + "maxExtent": 0, + "minExtent": 0, + "ticks": false, + "zindex": 0 + }, + { + "scale": "x", + "orient": "bottom", + "grid": false, + "title": "% of total Time", + "labelFlush": true, + "labelOverlap": true, + "tickCount": {"signal": "ceil(width/40)"}, + "labelFont": "Helvetica", + "titleFont": "Helvetica", + "zindex": 0 + }, + { + "scale": "y", + "orient": "left", + "grid": false, + "labelFont": "Helvetica", + "titleFont": "Helvetica", + "zindex": 0 + } + ] +} diff --git a/vega-wgpu-renderer/src/renderers/text.rs b/vega-wgpu-renderer/src/renderers/text.rs index 2bf07fe..c33f1ed 100644 --- a/vega-wgpu-renderer/src/renderers/text.rs +++ b/vega-wgpu-renderer/src/renderers/text.rs @@ -1,11 +1,11 @@ use crate::renderers::canvas::CanvasUniform; use crate::renderers::mark::MarkShader; use crate::scene::text::TextInstance; -use crate::specs::text::{TextAlignSpec, TextBaselineSpec}; +use crate::specs::text::{FontWeightNameSpec, FontWeightSpec, TextAlignSpec, TextBaselineSpec}; use glyphon::cosmic_text::Align; use glyphon::{ Attrs, Buffer, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea, - TextAtlas, TextBounds, TextRenderer, + TextAtlas, TextBounds, TextRenderer, Weight, }; use wgpu::{ CommandBuffer, CommandEncoderDescriptor, Device, MultisampleState, Operations, Queue, @@ -69,12 +69,26 @@ impl TextMarkRenderer { .map(|instance| { let mut buffer = Buffer::new( &mut self.font_system, - Metrics::new(instance.font_size, instance.font_size * 1.0), + Metrics::new(instance.font_size, instance.font_size), ); + let family = match instance.font.to_lowercase().as_str() { + "serif" => Family::Serif, + "sans serif" => Family::SansSerif, + "cursive" => Family::Cursive, + "fantasy" => Family::Fantasy, + "monospace" => Family::Monospace, + _ => Family::Name(instance.font.as_str()), + }; + let weight = match instance.font_weight { + FontWeightSpec::Name(FontWeightNameSpec::Bold) => Weight::BOLD, + FontWeightSpec::Name(FontWeightNameSpec::Normal) => Weight::NORMAL, + FontWeightSpec::Number(w) => Weight(w as u16), + }; + buffer.set_text( &mut self.font_system, &instance.text, - Attrs::new().family(Family::SansSerif), + Attrs::new().family(family).weight(weight), Shaping::Advanced, ); buffer.set_size( @@ -102,8 +116,9 @@ impl TextMarkRenderer { let top = match instance.baseline { TextBaselineSpec::Alphabetic => instance.position[1] - height, - TextBaselineSpec::Top => instance.position[1], - TextBaselineSpec::Middle => instance.position[1] - height * 0.56, + // Add half pixel for top baseline for better match with resvg + TextBaselineSpec::Top => instance.position[1] + 0.5, + TextBaselineSpec::Middle => instance.position[1] - height * 0.5, TextBaselineSpec::Bottom => instance.position[1] - height, TextBaselineSpec::LineTop => todo!(), TextBaselineSpec::LineBottom => todo!(), diff --git a/vega-wgpu-renderer/src/scene/text.rs b/vega-wgpu-renderer/src/scene/text.rs index eb3856f..db77b6c 100644 --- a/vega-wgpu-renderer/src/scene/text.rs +++ b/vega-wgpu-renderer/src/scene/text.rs @@ -63,8 +63,8 @@ impl TextInstance { font: item_spec .font .clone() - .unwrap_or_else(|| "Liberation Sans".to_string()), - font_size: item_spec.fill_opacity.unwrap_or(12.0), + .unwrap_or_else(|| "Sans Serif".to_string()), + font_size: item_spec.font_size.unwrap_or(10.0), font_weight: item_spec.font_weight.unwrap_or_default(), font_style: item_spec.font_style.unwrap_or_default(), limit: item_spec.limit.unwrap_or(0.0), diff --git a/vega-wgpu-renderer/tests/specs/text/bar_axis_labels.dims.json b/vega-wgpu-renderer/tests/specs/text/bar_axis_labels.dims.json new file mode 100644 index 0000000..25e83d0 --- /dev/null +++ b/vega-wgpu-renderer/tests/specs/text/bar_axis_labels.dims.json @@ -0,0 +1,6 @@ +{ + "width": 257, + "height": 102, + "origin_x": 51, + "origin_y": 5 +} \ No newline at end of file diff --git a/vega-wgpu-renderer/tests/specs/text/bar_axis_labels.png b/vega-wgpu-renderer/tests/specs/text/bar_axis_labels.png new file mode 100644 index 0000000000000000000000000000000000000000..bda863534786a17de6d1a2f46cb79db1167e0f5b GIT binary patch literal 13789 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