Auto merge of #13337 - epage:unicode, r=weihanglo

feat(tree): Control `--charset` via auto-detecting config value

### What does this PR try to resolve?

This tries to generalize `cargo tree --charset` so any part of cargo's output can use it.  For example,
- `cargo search` could use this for fancier tables
- `cargo add` could use this for fancier feature lists
- #12235 could use this for fancy rendering like miette does (CC `@Muscraft` )
- Progress bars could use fancier characters <-- this is what I'm personally working towards

This builds on the idea from #12889 that we can use fancier terminal features so long as we have good auto-detection and provide users an escape hatch until the auto-detection is improved or in case they disagree.

As a side benefit
- `cargo tree` will auto-detect when the prior default of `--charset utf8` is a good choice
- Users can control `cargo tree --charset` via `.cargo/config.toml`

### How should we test and review this PR?

This is gradually introduced through the individual commits.

### Additional information
This commit is contained in:
bors 2024-03-15 20:41:31 +00:00
commit c319962079
12 changed files with 139 additions and 78 deletions

10
Cargo.lock generated
View File

@ -336,6 +336,7 @@ dependencies = [
"shell-escape",
"snapbox",
"supports-hyperlinks",
"supports-unicode",
"tar",
"tempfile",
"time",
@ -3243,6 +3244,15 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee"
[[package]]
name = "supports-unicode"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f850c19edd184a205e883199a261ed44471c81e39bd95b1357f5febbef00e77a"
dependencies = [
"is-terminal",
]
[[package]]
name = "syn"
version = "1.0.109"

View File

@ -204,6 +204,7 @@ unicase.workspace = true
unicode-width.workspace = true
url.workspace = true
walkdir.workspace = true
supports-unicode = "2.1.0"
[target.'cfg(target_has_atomic = "64")'.dependencies]
tracing-chrome.workspace = true

View File

@ -69,8 +69,7 @@ pub fn cli() -> Command {
.arg(
opt("charset", "Character set to use in output")
.value_name("CHARSET")
.value_parser(["utf8", "ascii"])
.default_value("utf8"),
.value_parser(["utf8", "ascii"]),
)
.arg(
opt("format", "Format string used for printing dependencies")
@ -101,6 +100,24 @@ pub fn cli() -> Command {
))
}
#[derive(Copy, Clone)]
pub enum Charset {
Utf8,
Ascii,
}
impl FromStr for Charset {
type Err = &'static str;
fn from_str(s: &str) -> Result<Charset, &'static str> {
match s {
"utf8" => Ok(Charset::Utf8),
"ascii" => Ok(Charset::Ascii),
_ => Err("invalid charset"),
}
}
}
pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
if args.flag("version") {
let verbose = args.verbose() > 0;
@ -181,8 +198,17 @@ subtree of the package given to -p.\n\
print_available_packages(&ws)?;
}
let charset = tree::Charset::from_str(args.get_one::<String>("charset").unwrap())
.map_err(|e| anyhow::anyhow!("{}", e))?;
let charset = args.get_one::<String>("charset");
if let Some(charset) = charset
.map(|c| Charset::from_str(c))
.transpose()
.map_err(|e| anyhow::anyhow!("{}", e))?
{
match charset {
Charset::Utf8 => gctx.shell().set_unicode(true)?,
Charset::Ascii => gctx.shell().set_unicode(false)?,
}
}
let opts = tree::TreeOptions {
cli_features: args.cli_features()?,
packages,
@ -193,7 +219,6 @@ subtree of the package given to -p.\n\
prefix,
no_dedupe,
duplicates: args.flag("duplicates"),
charset,
format: args.get_one::<String>("format").cloned().unwrap(),
graph_features,
max_display_depth: args.value_of_u32("depth")?.unwrap_or(u32::MAX),

View File

@ -53,6 +53,8 @@ impl Shell {
color_choice: auto_clr,
hyperlinks: supports_hyperlinks(),
stderr_tty: std::io::stderr().is_terminal(),
stdout_unicode: supports_unicode(&std::io::stdout()),
stderr_unicode: supports_unicode(&std::io::stderr()),
},
verbosity: Verbosity::Verbose,
needs_clear: false,
@ -230,11 +232,11 @@ impl Shell {
/// Updates the color choice (always, never, or auto) from a string..
pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
if let ShellOut::Stream {
ref mut stdout,
ref mut stderr,
ref mut color_choice,
stdout,
stderr,
color_choice,
..
} = self.output
} = &mut self.output
{
let cfg = color
.map(|c| c.parse())
@ -249,16 +251,40 @@ impl Shell {
Ok(())
}
pub fn set_hyperlinks(&mut self, yes: bool) -> CargoResult<()> {
pub fn set_unicode(&mut self, yes: bool) -> CargoResult<()> {
if let ShellOut::Stream {
ref mut hyperlinks, ..
} = self.output
stdout_unicode,
stderr_unicode,
..
} = &mut self.output
{
*stdout_unicode = yes;
*stderr_unicode = yes;
}
Ok(())
}
pub fn set_hyperlinks(&mut self, yes: bool) -> CargoResult<()> {
if let ShellOut::Stream { hyperlinks, .. } = &mut self.output {
*hyperlinks = yes;
}
Ok(())
}
pub fn out_unicode(&self) -> bool {
match &self.output {
ShellOut::Write(_) => true,
ShellOut::Stream { stdout_unicode, .. } => *stdout_unicode,
}
}
pub fn err_unicode(&self) -> bool {
match &self.output {
ShellOut::Write(_) => true,
ShellOut::Stream { stderr_unicode, .. } => *stderr_unicode,
}
}
/// Gets the current color choice.
///
/// If we are not using a color stream, this will always return `Never`, even if the color
@ -384,6 +410,8 @@ enum ShellOut {
stderr_tty: bool,
color_choice: ColorChoice,
hyperlinks: bool,
stdout_unicode: bool,
stderr_unicode: bool,
},
}
@ -416,17 +444,17 @@ impl ShellOut {
/// Gets stdout as a `io::Write`.
fn stdout(&mut self) -> &mut dyn Write {
match *self {
ShellOut::Stream { ref mut stdout, .. } => stdout,
ShellOut::Write(ref mut w) => w,
match self {
ShellOut::Stream { stdout, .. } => stdout,
ShellOut::Write(w) => w,
}
}
/// Gets stderr as a `io::Write`.
fn stderr(&mut self) -> &mut dyn Write {
match *self {
ShellOut::Stream { ref mut stderr, .. } => stderr,
ShellOut::Write(ref mut w) => w,
match self {
ShellOut::Stream { stderr, .. } => stderr,
ShellOut::Write(w) => w,
}
}
}
@ -519,6 +547,10 @@ fn supports_color(choice: anstream::ColorChoice) -> bool {
}
}
fn supports_unicode(stream: &dyn IsTerminal) -> bool {
!stream.is_terminal() || supports_unicode::supports_unicode()
}
fn supports_hyperlinks() -> bool {
#[allow(clippy::disallowed_methods)] // We are reading the state of the system, not config
if std::env::var_os("TERM_PROGRAM").as_deref() == Some(std::ffi::OsStr::new("iTerm.app")) {

View File

@ -39,8 +39,6 @@ pub struct TreeOptions {
/// appear with different versions, and report if any where found. Implies
/// `invert`.
pub duplicates: bool,
/// The style of characters to use.
pub charset: Charset,
/// A format string indicating how each package should be displayed.
pub format: String,
/// Includes features in the tree as separate nodes.
@ -68,23 +66,6 @@ impl Target {
}
}
pub enum Charset {
Utf8,
Ascii,
}
impl FromStr for Charset {
type Err = &'static str;
fn from_str(s: &str) -> Result<Charset, &'static str> {
match s {
"utf8" => Ok(Charset::Utf8),
"ascii" => Ok(Charset::Ascii),
_ => Err("invalid charset"),
}
}
}
#[derive(Clone, Copy)]
pub enum Prefix {
None,
@ -236,9 +217,10 @@ fn print(
let format = Pattern::new(&opts.format)
.with_context(|| format!("tree format `{}` not valid", opts.format))?;
let symbols = match opts.charset {
Charset::Utf8 => &UTF8_SYMBOLS,
Charset::Ascii => &ASCII_SYMBOLS,
let symbols = if gctx.shell().out_unicode() {
&UTF8_SYMBOLS
} else {
&ASCII_SYMBOLS
};
// The visited deps is used to display a (*) whenever a dep has

View File

@ -1049,6 +1049,9 @@ impl GlobalContext {
if let Some(hyperlinks) = term.hyperlinks {
self.shell().set_hyperlinks(hyperlinks)?;
}
if let Some(unicode) = term.unicode {
self.shell().set_unicode(unicode)?;
}
self.progress_config = term.progress.unwrap_or_default();
@ -2646,6 +2649,7 @@ pub struct TermConfig {
pub quiet: Option<bool>,
pub color: Option<String>,
pub hyperlinks: Option<bool>,
pub unicode: Option<bool>,
#[serde(default)]
#[serde(deserialize_with = "progress_or_string")]
pub progress: Option<ProgressConfig>,

View File

@ -150,7 +150,7 @@ The default is the host platform. Use the value `all` to include *all* targets.
{{#option "`--charset` _charset_" }}
Chooses the character set to use for the tree. Valid values are "utf8" or
"ascii". Default is "utf8".
"ascii". When unspecified, cargo will auto-select a value.
{{/option}}
{{#option "`-f` _format_" "`--format` _format_" }}

View File

@ -141,7 +141,8 @@ OPTIONS
Tree Formatting Options
--charset charset
Chooses the character set to use for the tree. Valid values are
“utf8” or “ascii”. Default is “utf8”.
“utf8” or “ascii”. When unspecified, cargo will auto-select
a value.
-f format, --format format
Set the format string for each package. The default is “{p}”.

View File

@ -146,7 +146,7 @@ The default is the host platform. Use the value <code>all</code> to include <em>
<dt class="option-term" id="option-cargo-tree---charset"><a class="option-anchor" href="#option-cargo-tree---charset"></a><code>--charset</code> <em>charset</em></dt>
<dd class="option-desc">Chooses the character set to use for the tree. Valid values are “utf8” or
“ascii”. Default is “utf8”.</dd>
“ascii”. When unspecified, cargo will auto-select a value.</dd>
<dt class="option-term" id="option-cargo-tree--f"><a class="option-anchor" href="#option-cargo-tree--f"></a><code>-f</code> <em>format</em></dt>

View File

@ -182,6 +182,7 @@ quiet = false # whether cargo output is quiet
verbose = false # whether cargo provides verbose output
color = 'auto' # whether cargo colorizes output
hyperlinks = true # whether cargo inserts links into output
unicode = true # whether cargo can render output using non-ASCII unicode characters
progress.when = 'auto' # whether cargo shows progress bar
progress.width = 80 # width of progress bar
```
@ -1298,6 +1299,13 @@ Can be overridden with the `--color` command-line option.
Controls whether or not hyperlinks are used in the terminal.
#### `term.unicode`
* Type: bool
* Default: auto-detect
* Environment: `CARGO_TERM_UNICODE`
Control whether output can be rendered using non-ASCII unicode characters.
#### `term.progress.when`
* Type: string
* Default: "auto"

View File

@ -175,7 +175,7 @@ The default is the host platform. Use the value \fBall\fR to include \fIall\fR t
\fB\-\-charset\fR \fIcharset\fR
.RS 4
Chooses the character set to use for the tree. Valid values are \[lq]utf8\[rq] or
\[lq]ascii\[rq]\&. Default is \[lq]utf8\[rq]\&.
\[lq]ascii\[rq]\&. When unspecified, cargo will auto\-select a value.
.RE
.sp
\fB\-f\fR \fIformat\fR,

View File

@ -1,4 +1,4 @@
<svg width="860px" height="848px" xmlns="http://www.w3.org/2000/svg">
<svg width="860px" height="830px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
@ -47,69 +47,67 @@
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-cyan bold">-d</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--duplicates</tspan><tspan> Show only dependencies which come in multiple versions (implies -i)</tspan>
</tspan>
<tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-cyan bold">--charset</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;CHARSET&gt;</tspan><tspan> Character set to use in output [default: utf8] [possible values: utf8,</tspan>
<tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-cyan bold">--charset</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;CHARSET&gt;</tspan><tspan> Character set to use in output [possible values: utf8, ascii]</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan> ascii]</tspan>
<tspan x="10px" y="298px"><tspan> </tspan><tspan class="fg-cyan bold">-f</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--format</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FORMAT&gt;</tspan><tspan> Format string used for printing dependencies [default: {p}]</tspan>
</tspan>
<tspan x="10px" y="316px"><tspan> </tspan><tspan class="fg-cyan bold">-f</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--format</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FORMAT&gt;</tspan><tspan> Format string used for printing dependencies [default: {p}]</tspan>
<tspan x="10px" y="316px"><tspan> </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan> Use verbose output (-vv very verbose/build.rs output)</tspan>
</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan> Use verbose output (-vv very verbose/build.rs output)</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan> Do not print cargo log messages</tspan>
</tspan>
<tspan x="10px" y="352px"><tspan> </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan> Do not print cargo log messages</tspan>
<tspan x="10px" y="352px"><tspan> </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan> Coloring: auto, always, never</tspan>
</tspan>
<tspan x="10px" y="370px"><tspan> </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan> Coloring: auto, always, never</tspan>
<tspan x="10px" y="370px"><tspan> </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan> Override a configuration value</tspan>
</tspan>
<tspan x="10px" y="388px"><tspan> </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan> Override a configuration value</tspan>
<tspan x="10px" y="388px"><tspan> </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details</tspan>
</tspan>
<tspan x="10px" y="406px"><tspan> </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details</tspan>
<tspan x="10px" y="406px"><tspan> </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan> Print help</tspan>
</tspan>
<tspan x="10px" y="424px"><tspan> </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan> Print help</tspan>
<tspan x="10px" y="424px">
</tspan>
<tspan x="10px" y="442px">
<tspan x="10px" y="442px"><tspan class="fg-green bold">Package Selection:</tspan>
</tspan>
<tspan x="10px" y="460px"><tspan class="fg-green bold">Package Selection:</tspan>
<tspan x="10px" y="460px"><tspan> </tspan><tspan class="fg-cyan bold">-p</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--package</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;SPEC&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan> Package to be used as the root of the tree</tspan>
</tspan>
<tspan x="10px" y="478px"><tspan> </tspan><tspan class="fg-cyan bold">-p</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--package</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;SPEC&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan> Package to be used as the root of the tree</tspan>
<tspan x="10px" y="478px"><tspan> </tspan><tspan class="fg-cyan bold">--workspace</tspan><tspan> Display the tree for all packages in the workspace</tspan>
</tspan>
<tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-cyan bold">--workspace</tspan><tspan> Display the tree for all packages in the workspace</tspan>
<tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-cyan bold">--exclude</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;SPEC&gt;</tspan><tspan> Exclude specific workspace members</tspan>
</tspan>
<tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-cyan bold">--exclude</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;SPEC&gt;</tspan><tspan> Exclude specific workspace members</tspan>
<tspan x="10px" y="514px">
</tspan>
<tspan x="10px" y="532px">
<tspan x="10px" y="532px"><tspan class="fg-green bold">Feature Selection:</tspan>
</tspan>
<tspan x="10px" y="550px"><tspan class="fg-green bold">Feature Selection:</tspan>
<tspan x="10px" y="550px"><tspan> </tspan><tspan class="fg-cyan bold">-F</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--features</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FEATURES&gt;</tspan><tspan> Space or comma separated list of features to activate</tspan>
</tspan>
<tspan x="10px" y="568px"><tspan> </tspan><tspan class="fg-cyan bold">-F</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--features</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FEATURES&gt;</tspan><tspan> Space or comma separated list of features to activate</tspan>
<tspan x="10px" y="568px"><tspan> </tspan><tspan class="fg-cyan bold">--all-features</tspan><tspan> Activate all available features</tspan>
</tspan>
<tspan x="10px" y="586px"><tspan> </tspan><tspan class="fg-cyan bold">--all-features</tspan><tspan> Activate all available features</tspan>
<tspan x="10px" y="586px"><tspan> </tspan><tspan class="fg-cyan bold">--no-default-features</tspan><tspan> Do not activate the `default` feature</tspan>
</tspan>
<tspan x="10px" y="604px"><tspan> </tspan><tspan class="fg-cyan bold">--no-default-features</tspan><tspan> Do not activate the `default` feature</tspan>
<tspan x="10px" y="604px">
</tspan>
<tspan x="10px" y="622px">
<tspan x="10px" y="622px"><tspan class="fg-green bold">Compilation Options:</tspan>
</tspan>
<tspan x="10px" y="640px"><tspan class="fg-green bold">Compilation Options:</tspan>
<tspan x="10px" y="640px"><tspan> </tspan><tspan class="fg-cyan bold">--target</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;TRIPLE&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan> Filter dependencies matching the given target-triple (default host</tspan>
</tspan>
<tspan x="10px" y="658px"><tspan> </tspan><tspan class="fg-cyan bold">--target</tspan><tspan class="fg-cyan"> [</tspan><tspan class="fg-cyan">&lt;TRIPLE&gt;</tspan><tspan class="fg-cyan">]</tspan><tspan> Filter dependencies matching the given target-triple (default host</tspan>
<tspan x="10px" y="658px"><tspan> platform). Pass `all` to include all targets.</tspan>
</tspan>
<tspan x="10px" y="676px"><tspan> platform). Pass `all` to include all targets.</tspan>
<tspan x="10px" y="676px">
</tspan>
<tspan x="10px" y="694px">
<tspan x="10px" y="694px"><tspan class="fg-green bold">Manifest Options:</tspan>
</tspan>
<tspan x="10px" y="712px"><tspan class="fg-green bold">Manifest Options:</tspan>
<tspan x="10px" y="712px"><tspan> </tspan><tspan class="fg-cyan bold">--manifest-path</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PATH&gt;</tspan><tspan> Path to Cargo.toml</tspan>
</tspan>
<tspan x="10px" y="730px"><tspan> </tspan><tspan class="fg-cyan bold">--manifest-path</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PATH&gt;</tspan><tspan> Path to Cargo.toml</tspan>
<tspan x="10px" y="730px"><tspan> </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan> Require Cargo.lock and cache are up to date</tspan>
</tspan>
<tspan x="10px" y="748px"><tspan> </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan> Require Cargo.lock and cache are up to date</tspan>
<tspan x="10px" y="748px"><tspan> </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan> Require Cargo.lock is up to date</tspan>
</tspan>
<tspan x="10px" y="766px"><tspan> </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan> Require Cargo.lock is up to date</tspan>
<tspan x="10px" y="766px"><tspan> </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan> Run without accessing the network</tspan>
</tspan>
<tspan x="10px" y="784px"><tspan> </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan> Run without accessing the network</tspan>
<tspan x="10px" y="784px">
</tspan>
<tspan x="10px" y="802px">
<tspan x="10px" y="802px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help tree</tspan><tspan class="bold">` for more detailed information.</tspan>
</tspan>
<tspan x="10px" y="820px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help tree</tspan><tspan class="bold">` for more detailed information.</tspan>
</tspan>
<tspan x="10px" y="838px">
<tspan x="10px" y="820px">
</tspan>
</text>

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB