summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKirill Chibisov <contact@kchibisov.com>2022-02-08 20:47:31 +0300
committerGitHub <noreply@github.com>2022-02-08 20:47:31 +0300
commit73c3dd86280e98e11f080123aec47d08f3a76b49 (patch)
treec54d98dd763c12876235acc9880be5ec069c52b6
parent7263d223bf535fdffe00d5d9d398a8d188b3c185 (diff)
Add support for drawing undercurls
Fixes #1628.
-rw-r--r--CHANGELOG.md1
-rw-r--r--alacritty/res/rect.f.glsl45
-rw-r--r--alacritty/src/display/content.rs2
-rw-r--r--alacritty/src/display/mod.rs4
-rw-r--r--alacritty/src/renderer/mod.rs9
-rw-r--r--alacritty/src/renderer/rects.rs163
-rw-r--r--alacritty_terminal/src/ansi.rs3
-rw-r--r--alacritty_terminal/src/term/cell.rs3
-rw-r--r--alacritty_terminal/src/term/mod.rs10
-rw-r--r--alacritty_terminal/tests/ref/underline/grid.json2
-rw-r--r--alacritty_terminal/tests/ref/underline/size.json2
11 files changed, 195 insertions, 49 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ec237ac..93963d8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Option `font.builtin_box_drawing` to disable the built-in font for drawing box characters
- Track and report surface damage information to Wayland compositors
+- Escape sequence for undercurl (`CSI 4 : 3 m`)
### Changed
diff --git a/alacritty/res/rect.f.glsl b/alacritty/res/rect.f.glsl
index 945eaf2d..00c2de83 100644
--- a/alacritty/res/rect.f.glsl
+++ b/alacritty/res/rect.f.glsl
@@ -1,10 +1,53 @@
#version 330 core
+// We're using `origin_upper_left`, since we only known about padding from left
+// and top. If we use default `origin_bottom_left` we won't be able to offset
+// `gl_FragCoord` properly to align with the terminal grid.
+layout(origin_upper_left) in vec4 gl_FragCoord;
+
flat in vec4 color;
out vec4 FragColor;
+uniform int isUndercurl;
+
+uniform float cellWidth;
+uniform float cellHeight;
+uniform float paddingY;
+uniform float paddingX;
+
+uniform float undercurlThickness;
+uniform float undercurlPosition;
+
+#define PI 3.1415926538
+
void main()
{
- FragColor = color;
+ if (isUndercurl == 0) {
+ FragColor = color;
+ return;
+ }
+
+ int x = int(gl_FragCoord.x - paddingX) % int(cellWidth);
+ int y = int(gl_FragCoord.y - paddingY) % int(cellHeight);
+
+ // We use `undercurlPosition` as amplitude, since it's half of the descent
+ // value.
+ float undercurl = -1. * undercurlPosition / 2.
+ * cos(float(x) * 2 * PI / float(cellWidth))
+ + cellHeight - undercurlPosition;
+
+ float undercurl_top = undercurl + undercurlThickness / 2.;
+ float undercurl_bottom = undercurl - undercurlThickness / 2.;
+
+
+ // Compute resulted alpha based on distance from `gl_FragCoord.y` to the
+ // cosine curve.
+ float alpha = 1.;
+ if (y > undercurl_top || y < undercurl_bottom) {
+ alpha = 1. - min(abs(undercurl_top - y), abs(undercurl_bottom - y));
+ }
+
+ // The result is an alpha mask on a rect, which leaves only curve opaque.
+ FragColor = vec4(color.xyz, alpha);
}
diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs
index 09d27cf0..d2da1e81 100644
--- a/alacritty/src/display/content.rs
+++ b/alacritty/src/display/content.rs
@@ -267,7 +267,7 @@ impl RenderableCell {
self.bg_alpha == 0.
&& self.character == ' '
&& self.zerowidth.is_none()
- && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE)
+ && !self.flags.intersects(Flags::ALL_UNDERLINES | Flags::STRIKEOUT)
}
/// Apply [`CellRgb`] colors to the cell's colors.
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs
index 7d53e678..d4d1a91b 100644
--- a/alacritty/src/display/mod.rs
+++ b/alacritty/src/display/mod.rs
@@ -668,7 +668,7 @@ impl Display {
rects.push(message_bar_rect);
// Draw rectangles.
- self.renderer.draw_rects(&size_info, rects);
+ self.renderer.draw_rects(&size_info, &metrics, rects);
// Relay messages to the user.
let glyph_cache = &mut self.glyph_cache;
@@ -681,7 +681,7 @@ impl Display {
}
} else {
// Draw rectangles.
- self.renderer.draw_rects(&size_info, rects);
+ self.renderer.draw_rects(&size_info, &metrics, rects);
}
self.draw_render_timer(config, &size_info);
diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs
index 6e3d2ab9..ad59a4e1 100644
--- a/alacritty/src/renderer/mod.rs
+++ b/alacritty/src/renderer/mod.rs
@@ -5,7 +5,7 @@ use std::{fmt, ptr};
use bitflags::bitflags;
use crossfont::{
- BitmapBuffer, Error as RasterizerError, FontDesc, FontKey, GlyphKey, Rasterize,
+ BitmapBuffer, Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize,
RasterizedGlyph, Rasterizer, Size, Slant, Style, Weight,
};
use fnv::FnvHasher;
@@ -35,6 +35,7 @@ macro_rules! cstr {
unsafe { std::ffi::CStr::from_ptr(concat!($s, "\0").as_ptr().cast()) }
};
}
+pub(crate) use cstr;
// Shader source.
static TEXT_SHADER_F: &str = include_str!("../../res/text.f.glsl");
@@ -148,7 +149,7 @@ pub struct GlyphCache {
glyph_offset: Delta<i8>,
/// Font metrics.
- metrics: crossfont::Metrics,
+ metrics: Metrics,
/// Whether to use the built-in font for box drawing characters.
builtin_box_drawing: bool,
@@ -687,7 +688,7 @@ impl QuadRenderer {
}
/// Draw all rectangles simultaneously to prevent excessive program swaps.
- pub fn draw_rects(&mut self, size_info: &SizeInfo, rects: Vec<RenderRect>) {
+ pub fn draw_rects(&mut self, size_info: &SizeInfo, metrics: &Metrics, rects: Vec<RenderRect>) {
if rects.is_empty() {
return;
}
@@ -699,7 +700,7 @@ impl QuadRenderer {
gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE);
}
- self.rect_renderer.draw(size_info, rects);
+ self.rect_renderer.draw(size_info, metrics, rects);
// Activate regular state again.
unsafe {
diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs
index 2cf43a34..e9eb107f 100644
--- a/alacritty/src/renderer/rects.rs
+++ b/alacritty/src/renderer/rects.rs
@@ -10,9 +10,10 @@ use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::SizeInfo;
use crate::display::content::RenderableCell;
+use crate::gl;
use crate::gl::types::*;
-use crate::renderer::shader::ShaderProgram;
-use crate::{gl, renderer};
+use crate::renderer::shader::{ShaderError, ShaderProgram};
+use crate::renderer::{self, cstr};
#[derive(Debug, Copy, Clone)]
pub struct RenderRect {
@@ -22,11 +23,12 @@ pub struct RenderRect {
pub height: f32,
pub color: Rgb,
pub alpha: f32,
+ pub is_undercurl: bool,
}
impl RenderRect {
pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self {
- RenderRect { x, y, width, height, color, alpha }
+ RenderRect { x, y, width, height, color, alpha, is_undercurl: false }
}
}
@@ -80,20 +82,17 @@ impl RenderLine {
(bottom_pos, metrics.underline_thickness)
},
+ // Make undercurl occupy the entire descent area.
+ Flags::UNDERCURL => (metrics.descent, metrics.descent.abs()),
Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness),
Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness),
_ => unimplemented!("Invalid flag for cell line drawing specified"),
};
- rects.push(Self::create_rect(
- size,
- metrics.descent,
- start,
- end,
- position,
- thickness,
- color,
- ));
+ let mut rect =
+ Self::create_rect(size, metrics.descent, start, end, position, thickness, color);
+ rect.is_undercurl = flag == Flags::UNDERCURL;
+ rects.push(rect);
}
/// Create a line's rect at a position relative to the baseline.
@@ -161,6 +160,7 @@ impl RenderLines {
self.update_flag(cell, Flags::UNDERLINE);
self.update_flag(cell, Flags::DOUBLE_UNDERLINE);
self.update_flag(cell, Flags::STRIKEOUT);
+ self.update_flag(cell, Flags::UNDERCURL);
}
/// Update the lines for a specific flag.
@@ -222,16 +222,17 @@ pub struct RectRenderer {
vao: GLuint,
vbo: GLuint,
- program: ShaderProgram,
+ program: RectShaderProgram,
- vertices: Vec<Vertex>,
+ rect_vertices: Vec<Vertex>,
+ curl_vertices: Vec<Vertex>,
}
impl RectRenderer {
pub fn new() -> Result<Self, renderer::Error> {
let mut vao: GLuint = 0;
let mut vbo: GLuint = 0;
- let program = ShaderProgram::new(RECT_SHADER_V, RECT_SHADER_F)?;
+ let program = RectShaderProgram::new()?;
unsafe {
// Allocate buffers.
@@ -273,10 +274,10 @@ impl RectRenderer {
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
}
- Ok(Self { vao, vbo, program, vertices: Vec::new() })
+ Ok(Self { vao, vbo, program, rect_vertices: Vec::new(), curl_vertices: Vec::new() })
}
- pub fn draw(&mut self, size_info: &SizeInfo, rects: Vec<RenderRect>) {
+ pub fn draw(&mut self, size_info: &SizeInfo, metrics: &Metrics, rects: Vec<RenderRect>) {
unsafe {
// Bind VAO to enable vertex attribute slots.
gl::BindVertexArray(self.vao);
@@ -285,28 +286,51 @@ impl RectRenderer {
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
gl::UseProgram(self.program.id());
+ self.program.update_uniforms(size_info, metrics);
}
let half_width = size_info.width() / 2.;
let half_height = size_info.height() / 2.;
// Build rect vertices vector.
- self.vertices.clear();
+ self.rect_vertices.clear();
+ self.curl_vertices.clear();
for rect in &rects {
- self.add_rect(half_width, half_height, rect);
+ if rect.is_undercurl {
+ Self::add_rect(&mut self.curl_vertices, half_width, half_height, rect);
+ } else {
+ Self::add_rect(&mut self.rect_vertices, half_width, half_height, rect);
+ }
}
unsafe {
- // Upload accumulated vertices.
- gl::BufferData(
- gl::ARRAY_BUFFER,
- (self.vertices.len() * mem::size_of::<Vertex>()) as isize,
- self.vertices.as_ptr() as *const _,
- gl::STREAM_DRAW,
- );
+ if !self.curl_vertices.is_empty() {
+ self.program.set_undercurl(true);
+ // Upload accumulated undercurl vertices.
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ (self.curl_vertices.len() * mem::size_of::<Vertex>()) as isize,
+ self.curl_vertices.as_ptr() as *const _,
+ gl::STREAM_DRAW,
+ );
+
+ // Draw all vertices as list of triangles.
+ gl::DrawArrays(gl::TRIANGLES, 0, self.curl_vertices.len() as i32);
+ }
- // Draw all vertices as list of triangles.
- gl::DrawArrays(gl::TRIANGLES, 0, self.vertices.len() as i32);
+ if !self.rect_vertices.is_empty() {
+ self.program.set_undercurl(false);
+ // Upload accumulated rect vertices.
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ (self.rect_vertices.len() * mem::size_of::<Vertex>()) as isize,
+ self.rect_vertices.as_ptr() as *const _,
+ gl::STREAM_DRAW,
+ );
+
+ // Draw all vertices as list of triangles.
+ gl::DrawArrays(gl::TRIANGLES, 0, self.rect_vertices.len() as i32);
+ }
// Disable program.
gl::UseProgram(0);
@@ -317,7 +341,7 @@ impl RectRenderer {
}
}
- fn add_rect(&mut self, half_width: f32, half_height: f32, rect: &RenderRect) {
+ fn add_rect(vertices: &mut Vec<Vertex>, half_width: f32, half_height: f32, rect: &RenderRect) {
// Calculate rectangle vertices positions in normalized device coordinates.
// NDC range from -1 to +1, with Y pointing up.
let x = rect.x / half_width - 1.0;
@@ -336,12 +360,12 @@ impl RectRenderer {
];
// Append the vertices to form two triangles.
- self.vertices.push(quad[0]);
- self.vertices.push(quad[1]);
- self.vertices.push(quad[2]);
- self.vertices.push(quad[2]);
- self.vertices.push(quad[3]);
- self.vertices.push(quad[1]);
+ vertices.push(quad[0]);
+ vertices.push(quad[1]);
+ vertices.push(quad[2]);
+ vertices.push(quad[2]);
+ vertices.push(quad[3]);
+ vertices.push(quad[1]);
}
}
@@ -353,3 +377,72 @@ impl Drop for RectRenderer {
}
}
}
+
+/// Rectangle drawing program.
+#[derive(Debug)]
+pub struct RectShaderProgram {
+ /// Shader program.
+ program: ShaderProgram,
+
+ /// Undercurl flag.
+ ///
+ /// Rect rendering has two modes; one for normal filled rects, and other for undercurls.
+ u_is_undercurl: GLint,
+
+ /// Cell width.
+ u_cell_width: GLint,
+
+ /// Cell height.
+ u_cell_height: GLint,
+
+ /// Terminal padding.
+ u_padding_x: GLint,
+ u_padding_y: GLint,
+
+ /// Undercurl thickness.
+ u_undercurl_thickness: GLint,
+
+ /// Undercurl position.
+ u_undercurl_position: GLint,
+}
+
+impl RectShaderProgram {
+ pub fn new() -> Result<Self, ShaderError> {
+ let program = ShaderProgram::new(RECT_SHADER_V, RECT_SHADER_F)?;
+
+ Ok(Self {
+ u_is_undercurl: program.get_uniform_location(cstr!("isUndercurl"))?,
+ u_cell_width: program.get_uniform_location(cstr!("cellWidth"))?,
+ u_cell_height: program.get_uniform_location(cstr!("cellHeight"))?,
+ u_padding_x: program.get_uniform_location(cstr!("paddingX"))?,
+ u_padding_y: program.get_uniform_location(cstr!("paddingY"))?,
+ u_undercurl_position: program.get_uniform_location(cstr!("undercurlPosition"))?,
+ u_undercurl_thickness: program.get_uniform_location(cstr!("undercurlThickness"))?,
+ program,
+ })
+ }
+
+ fn id(&self) -> GLuint {
+ self.program.id()
+ }
+
+ fn set_undercurl(&self, is_undercurl: bool) {
+ let value = if is_undercurl { 1 } else { 0 };
+
+ unsafe {
+ gl::Uniform1i(self.u_is_undercurl, value);
+ }
+ }
+
+ pub fn update_uniforms(&self, size_info: &SizeInfo, metrics: &Metrics) {
+ let position = (0.5 * metrics.descent).abs();
+ unsafe {
+ gl::Uniform1f(self.u_cell_width, size_info.cell_width());
+ gl::Uniform1f(self.u_cell_height, size_info.cell_height());
+ gl::Uniform1f(self.u_padding_y, size_info.padding_y());
+ gl::Uniform1f(self.u_padding_x, size_info.padding_x());
+ gl::Uniform1f(self.u_undercurl_thickness, metrics.underline_thickness);
+ gl::Uniform1f(self.u_undercurl_position, position);
+ }
+ }
+}
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 06ea0234..91dd3540 100644
--- a/alacritty_terminal/src/ansi.rs
+++ b/alacritty_terminal/src/ansi.rs
@@ -770,6 +770,8 @@ pub enum Attr {
Underline,
/// Underlined twice.
DoubleUnderline,
+ /// Undercurled text.
+ Undercurl,
/// Blink cursor slowly.
BlinkSlow,
/// Blink cursor fast.
@@ -1329,6 +1331,7 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> {
[3] => Some(Attr::Italic),
[4, 0] => Some(Attr::CancelUnderline),
[4, 2] => Some(Attr::DoubleUnderline),
+ [4, 3] => Some(Attr::Undercurl),
[4, ..] => Some(Attr::Underline),
[5] => Some(Attr::BlinkSlow),
[6] => Some(Attr::BlinkFast),
diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs
index 64de5492..49d25eb3 100644
--- a/alacritty_terminal/src/term/cell.rs
+++ b/alacritty_terminal/src/term/cell.rs
@@ -24,6 +24,8 @@ bitflags! {
const STRIKEOUT = 0b0000_0010_0000_0000;
const LEADING_WIDE_CHAR_SPACER = 0b0000_0100_0000_0000;
const DOUBLE_UNDERLINE = 0b0000_1000_0000_0000;
+ const UNDERCURL = 0b0001_0000_0000_0000;
+ const ALL_UNDERLINES = Self::UNDERLINE.bits | Self::DOUBLE_UNDERLINE.bits | Self::UNDERCURL.bits;
}
}
@@ -119,6 +121,7 @@ impl GridCell for Cell {
Flags::INVERSE
| Flags::UNDERLINE
| Flags::DOUBLE_UNDERLINE
+ | Flags::UNDERCURL
| Flags::STRIKEOUT
| Flags::WRAPLINE
| Flags::WIDE_CHAR_SPACER
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 50f423e5..4d30602b 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -1839,16 +1839,18 @@ impl<T: EventListener> Handler for Term<T> {
Attr::Italic => cursor.template.flags.insert(Flags::ITALIC),
Attr::CancelItalic => cursor.template.flags.remove(Flags::ITALIC),
Attr::Underline => {
- cursor.template.flags.remove(Flags::DOUBLE_UNDERLINE);
+ cursor.template.flags.remove(Flags::ALL_UNDERLINES);
cursor.template.flags.insert(Flags::UNDERLINE);
},
Attr::DoubleUnderline => {
- cursor.template.flags.remove(Flags::UNDERLINE);
+ cursor.template.flags.remove(Flags::ALL_UNDERLINES);
cursor.template.flags.insert(Flags::DOUBLE_UNDERLINE);
},
- Attr::CancelUnderline => {
- cursor.template.flags.remove(Flags::UNDERLINE | Flags::DOUBLE_UNDERLINE);
+ Attr::Undercurl => {
+ cursor.template.flags.remove(Flags::ALL_UNDERLINES);
+ cursor.template.flags.insert(Flags::UNDERCURL);
},
+ Attr::CancelUnderline => cursor.template.flags.remove(Flags::ALL_UNDERLINES),
Attr::Hidden => cursor.template.flags.insert(Flags::HIDDEN),
Attr::CancelHidden => cursor.template.flags.remove(Flags::HIDDEN),
Attr::Strike => cursor.template.flags.insert(Flags::STRIKEOUT),
diff --git a/alacritty_terminal/tests/ref/underline/grid.json b/alacritty_terminal/tests/ref/underline/grid.json
index 01778e2d..e6652aca 100644
--- a/alacritty_terminal/tests/ref/underline/grid.json
+++ b/alacritty_terminal/tests/ref/underline/grid.json
@@ -1 +1 @@
-{"raw":{"inner":[{"inner":[{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}}],"occ":0},{"inner":[{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}}],"occ":0},{"inner":[{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"Foreground"},"bg":{"Named":"Background"},"flags":{"bits":0}},{"c":" ","fg":{"Named":"For