pango_itemize/pango_shape
This commit is contained in:
parent
17063d5216
commit
d6f6b04ca3
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -9,6 +9,7 @@ dependencies = [
|
|||||||
"gio 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gio 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"glib 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"glib 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"glib-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"glib-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"gobject-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"gtk 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gtk 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"gtk-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gtk-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -14,6 +14,7 @@ glib-sys = "0.4"
|
|||||||
gdk = "0.6"
|
gdk = "0.6"
|
||||||
gdk-sys = "0.4"
|
gdk-sys = "0.4"
|
||||||
gio = "0.2"
|
gio = "0.2"
|
||||||
|
gobject-sys = "0.4"
|
||||||
#gdk = { git = 'https://github.com/gtk-rs/gdk' }
|
#gdk = { git = 'https://github.com/gtk-rs/gdk' }
|
||||||
#gdk-sys = { git = 'https://github.com/gtk-rs/sys' }
|
#gdk-sys = { git = 'https://github.com/gtk-rs/sys' }
|
||||||
#glib = { git = 'https://github.com/gtk-rs/glib' }
|
#glib = { git = 'https://github.com/gtk-rs/glib' }
|
||||||
|
@ -6,6 +6,7 @@ extern crate gdk_sys;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate glib;
|
extern crate glib;
|
||||||
extern crate glib_sys as glib_ffi;
|
extern crate glib_sys as glib_ffi;
|
||||||
|
extern crate gobject_sys as gobject_ffi;
|
||||||
extern crate cairo;
|
extern crate cairo;
|
||||||
extern crate pango;
|
extern crate pango;
|
||||||
extern crate pango_sys;
|
extern crate pango_sys;
|
||||||
|
@ -14,16 +14,14 @@ pub struct Context {
|
|||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new(font_desc: &pango::FontDescription) -> Self {
|
pub fn new(font_desc: &pango::FontDescription) -> Self {
|
||||||
Context {
|
Context { pango_context: create_pango_context(font_desc) }
|
||||||
pango_context: create_pango_context(font_desc),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, font_desc: &pango::FontDescription) {
|
pub fn update(&mut self, font_desc: &pango::FontDescription) {
|
||||||
self.pango_context = create_pango_context(font_desc);
|
self.pango_context = create_pango_context(font_desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn itemize(&self, line: &StyledLine)-> Vec<item::Item> {
|
pub fn itemize(&self, line: &StyledLine) -> Vec<item::Item> {
|
||||||
pango_itemize(&self.pango_context, &line.line_str, &line.attr_list)
|
pango_itemize(&self.pango_context, &line.line_str, &line.attr_list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,23 +8,80 @@ use pangocairo::{CairoContextExt, FontMap};
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use ui_model;
|
use ui_model;
|
||||||
|
|
||||||
pub fn render(ctx: &cairo::Context, font_desc: pango::FontDescription) {
|
pub fn render(
|
||||||
let font_map = FontMap::get_default();
|
ctx: &cairo::Context,
|
||||||
let pango_context = font_map.create_context().unwrap();
|
font_desc: pango::FontDescription,
|
||||||
pango_context.set_font_description(&font_desc);
|
ui_model: &mut ui_model::UiModel,
|
||||||
|
) {
|
||||||
|
let font_ctx = context::Context::new(&font_desc);
|
||||||
|
|
||||||
let text = "TEST String".to_owned();
|
shape_dirty(&font_ctx, ui_model);
|
||||||
let attr_list = pango::AttrList::new();
|
|
||||||
|
|
||||||
ctx.move_to(0.0, 50.0);
|
|
||||||
let items = pango_itemize(&pango_context, &text, &attr_list);
|
for line in ui_model.model_mut() {
|
||||||
for item in items {
|
for i in 0..line.line.len() {
|
||||||
let mut glyphs = pango::GlyphString::new();
|
let item = line.item_line[i].as_ref();
|
||||||
let analysis = item.analysis();
|
if let Some(item) = item {
|
||||||
pango_shape(&text, &analysis, &mut glyphs);
|
if let Some(ref glyphs) = item.glyphs {
|
||||||
let font = analysis.font();
|
let analysis = item.item.analysis();
|
||||||
let (ink, logical) = glyphs.extents(&font);
|
let font = analysis.font();
|
||||||
ctx.show_glyph_string(&font, &glyphs);
|
ctx.show_glyph_string(&font, glyphs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shape_dirty(ctx: &context::Context, ui_model: &mut ui_model::UiModel) {
|
||||||
|
for line in ui_model.model_mut() {
|
||||||
|
if line.dirty_line {
|
||||||
|
let styled_line = ui_model::StyledLine::from(line);
|
||||||
|
let items = ctx.itemize(&styled_line);
|
||||||
|
line.merge(&styled_line, &items);
|
||||||
|
|
||||||
|
for i in 0..line.line.len() {
|
||||||
|
if line[i].dirty {
|
||||||
|
let mut item = line.get_item_mut(i).unwrap();
|
||||||
|
let mut glyphs = pango::GlyphString::new();
|
||||||
|
{
|
||||||
|
let analysis = item.item.analysis();
|
||||||
|
let (offset, length, _) = item.item.offset();
|
||||||
|
pango_shape(
|
||||||
|
&styled_line.line_str,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
&analysis,
|
||||||
|
&mut glyphs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.set_glyphs(glyphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
line[i].dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.dirty_line = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//pub fn render_test(ctx: &cairo::Context, font_desc: pango::FontDescription) {
|
||||||
|
//let font_map = FontMap::get_default();
|
||||||
|
//let pango_context = font_map.create_context().unwrap();
|
||||||
|
//pango_context.set_font_description(&font_desc);
|
||||||
|
|
||||||
|
//let text = "TEST String".to_owned();
|
||||||
|
//let attr_list = pango::AttrList::new();
|
||||||
|
|
||||||
|
//ctx.move_to(0.0, 50.0);
|
||||||
|
//let items = pango_itemize(&pango_context, &text, &attr_list);
|
||||||
|
//for item in items {
|
||||||
|
//let mut glyphs = pango::GlyphString::new();
|
||||||
|
//let analysis = item.analysis();
|
||||||
|
//pango_shape(&text, &analysis, &mut glyphs);
|
||||||
|
//let font = analysis.font();
|
||||||
|
//let (ink, logical) = glyphs.extents(&font);
|
||||||
|
//ctx.show_glyph_string(&font, &glyphs);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
31
src/shell.rs
31
src/shell.rs
@ -621,31 +621,30 @@ fn update_line_metrics(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtk_draw(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit {
|
fn gtk_draw(state_arc: &Arc<UiMutex<State>>, ctx: &cairo::Context) -> Inhibit {
|
||||||
//update_line_metrics(state_arc, ctx);
|
update_line_metrics(state_arc, ctx);
|
||||||
|
|
||||||
//if state_arc.borrow_mut().request_nvim_resize {
|
if state_arc.borrow_mut().request_nvim_resize {
|
||||||
// try_nvim_resize(state_arc);
|
try_nvim_resize(state_arc);
|
||||||
//}
|
}
|
||||||
|
|
||||||
//init_nvim(state_arc);
|
init_nvim(state_arc);
|
||||||
|
|
||||||
//let mut state = state_arc.borrow_mut();
|
|
||||||
//if state.nvim.borrow().is_initialized() {
|
|
||||||
// draw(&*state, ctx);
|
|
||||||
// request_window_resize(&mut *state);
|
|
||||||
//} else if state.nvim.borrow().is_initializing() {
|
|
||||||
// draw_initializing(&*state, ctx);
|
|
||||||
//}
|
|
||||||
|
|
||||||
let mut state = state_arc.borrow_mut();
|
let mut state = state_arc.borrow_mut();
|
||||||
render(&*state, ctx);
|
if state.nvim.borrow().is_initialized() {
|
||||||
|
// draw(&*state, ctx);
|
||||||
|
render(&mut *state, ctx);
|
||||||
|
request_window_resize(&mut *state);
|
||||||
|
} else if state.nvim.borrow().is_initializing() {
|
||||||
|
draw_initializing(&*state, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
Inhibit(false)
|
Inhibit(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(state: &State, ctx: &cairo::Context) {
|
fn render(state: &mut State, ctx: &cairo::Context) {
|
||||||
let font_desc = state.create_pango_font();
|
let font_desc = state.create_pango_font();
|
||||||
|
|
||||||
render::render(ctx, font_desc);
|
render::render(ctx, font_desc, &mut state.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_nvim_start_error(err: nvim::NvimInitError, state_arc: Arc<UiMutex<State>>) {
|
fn show_nvim_start_error(err: nvim::NvimInitError, state_arc: Arc<UiMutex<State>>) {
|
||||||
|
@ -4,6 +4,7 @@ use std::mem;
|
|||||||
use pango_sys;
|
use pango_sys;
|
||||||
|
|
||||||
use glib_ffi;
|
use glib_ffi;
|
||||||
|
use gobject_ffi;
|
||||||
use glib::translate::*;
|
use glib::translate::*;
|
||||||
|
|
||||||
use super::analysis;
|
use super::analysis;
|
||||||
@ -14,6 +15,7 @@ glib_wrapper! {
|
|||||||
match fn {
|
match fn {
|
||||||
copy => |ptr| pango_sys::pango_item_copy(ptr as *mut pango_sys::PangoItem),
|
copy => |ptr| pango_sys::pango_item_copy(ptr as *mut pango_sys::PangoItem),
|
||||||
free => |ptr| pango_sys::pango_item_free(ptr),
|
free => |ptr| pango_sys::pango_item_free(ptr),
|
||||||
|
get_type => || pango_sys::pango_item_get_type(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,4 +23,8 @@ impl Item {
|
|||||||
pub fn analysis(&self) -> analysis::Analysis {
|
pub fn analysis(&self) -> analysis::Analysis {
|
||||||
analysis::Analysis::from(&self.0.analysis)
|
analysis::Analysis::from(&self.0.analysis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn offset(&self) -> (usize, usize, usize) {
|
||||||
|
(self.0.offset as usize, self.0.length as usize, self.0.num_chars as usize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ pub mod item;
|
|||||||
mod analysis;
|
mod analysis;
|
||||||
|
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::ffi::CStr;
|
|
||||||
|
|
||||||
use pango;
|
use pango;
|
||||||
use pango_sys;
|
use pango_sys;
|
||||||
@ -28,13 +27,17 @@ pub fn pango_itemize(
|
|||||||
|
|
||||||
pub fn pango_shape(
|
pub fn pango_shape(
|
||||||
text: &String,
|
text: &String,
|
||||||
|
offset: usize,
|
||||||
|
length: usize,
|
||||||
analysis: &analysis::Analysis,
|
analysis: &analysis::Analysis,
|
||||||
glyphs: &mut pango::GlyphString,
|
glyphs: &mut pango::GlyphString,
|
||||||
) {
|
) {
|
||||||
|
debug_assert!(offset + length <= text.len());
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
pango_sys::pango_shape(
|
pango_sys::pango_shape(
|
||||||
text.as_ptr() as *const i8,
|
(text.as_ptr() as *const i8).offset(offset as isize),
|
||||||
text.len() as i32,
|
length as i32,
|
||||||
analysis.to_glib_ptr(),
|
analysis.to_glib_ptr(),
|
||||||
glyphs.to_glib_none_mut().0,
|
glyphs.to_glib_none_mut().0,
|
||||||
);
|
);
|
||||||
|
@ -43,15 +43,17 @@ impl Attrs {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
pub ch: char,
|
|
||||||
pub attrs: Attrs,
|
pub attrs: Attrs,
|
||||||
|
pub ch: char,
|
||||||
|
pub dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cell {
|
impl Cell {
|
||||||
pub fn new(ch: char) -> Cell {
|
pub fn new(ch: char) -> Cell {
|
||||||
Cell {
|
Cell {
|
||||||
ch: ch,
|
|
||||||
attrs: Attrs::new(),
|
attrs: Attrs::new(),
|
||||||
|
ch: ch,
|
||||||
|
dirty: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,42 @@
|
|||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use super::cell::Cell;
|
use super::cell::Cell;
|
||||||
|
use sys::pango::item as sys_pango;
|
||||||
use pango;
|
use pango;
|
||||||
|
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
item: pango::Item,
|
pub item: sys_pango::Item,
|
||||||
glyph_string: Option<pango::GlyphString>,
|
pub glyphs: Option<pango::GlyphString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
pub fn new(item: pango::Item) -> Self {
|
pub fn new(item: sys_pango::Item) -> Self {
|
||||||
Item {
|
Item {
|
||||||
item,
|
item,
|
||||||
glyph_string: None,
|
glyphs: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, item: sys_pango::Item) {
|
||||||
|
self.item = item;
|
||||||
|
self.glyphs = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_glyphs(&mut self, glyphs: pango::GlyphString) {
|
||||||
|
self.glyphs = Some(glyphs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Line {
|
pub struct Line {
|
||||||
pub line: Box<[Cell]>,
|
pub line: Box<[Cell]>,
|
||||||
item_line: Option<Box<[Item]>>,
|
|
||||||
cell_to_item: Box<[usize]>,
|
// format of item line is
|
||||||
|
// [Item1, Item2, None, None, Item3]
|
||||||
|
// Item2 take 3 cells and renders as one
|
||||||
|
pub item_line: Box<[Option<Item>]>,
|
||||||
|
|
||||||
|
item_line_empty: bool,
|
||||||
|
pub dirty_line: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
@ -29,11 +45,16 @@ impl Line {
|
|||||||
for _ in 0..columns {
|
for _ in 0..columns {
|
||||||
line.push(Cell::new(' '));
|
line.push(Cell::new(' '));
|
||||||
}
|
}
|
||||||
|
let mut item_line = Vec::with_capacity(columns);
|
||||||
|
for _ in 0..columns {
|
||||||
|
item_line.push(None);
|
||||||
|
}
|
||||||
|
|
||||||
Line {
|
Line {
|
||||||
cell_to_item: Vec::with_capacity(line.len()).into_boxed_slice(),
|
|
||||||
line: line.into_boxed_slice(),
|
line: line.into_boxed_slice(),
|
||||||
item_line: None,
|
item_line: item_line.into_boxed_slice(),
|
||||||
|
dirty_line: false,
|
||||||
|
item_line_empty: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +62,63 @@ impl Line {
|
|||||||
for cell in &mut self.line[left..right + 1] {
|
for cell in &mut self.line[left..right + 1] {
|
||||||
cell.clear();
|
cell.clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge(&mut self, old_items: &StyledLine, new_items: &[sys_pango::Item]) {
|
||||||
|
for new_item in new_items {
|
||||||
|
let (offset, length, _) = new_item.offset();
|
||||||
|
let start_cell = old_items.cell_to_byte[offset];
|
||||||
|
let end_cell = old_items.cell_to_byte[offset + length - 1];
|
||||||
|
|
||||||
|
// first time initialization
|
||||||
|
// as cell_to_item is to slow in this case
|
||||||
|
if !self.item_line_empty {
|
||||||
|
let start_item = self.cell_to_item(start_cell);
|
||||||
|
let end_item = self.cell_to_item(end_cell);
|
||||||
|
|
||||||
|
// in case different item length was in previous iteration
|
||||||
|
// mark all item as dirty
|
||||||
|
if start_item != end_item {
|
||||||
|
self.initialize_cells(start_cell, end_cell, new_item);
|
||||||
|
} else {
|
||||||
|
self.item_line[offset].as_mut().unwrap().update(
|
||||||
|
new_item.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.initialize_cells(start_cell, end_cell, new_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.item_line_empty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_cells(&mut self, start_cell: usize, end_cell: usize, new_item: &sys_pango::Item) {
|
||||||
|
for i in start_cell..end_cell {
|
||||||
|
self.line[i].dirty = true;
|
||||||
|
}
|
||||||
|
for i in start_cell + 1..end_cell {
|
||||||
|
self.item_line[i] = None;
|
||||||
|
}
|
||||||
|
self.item_line[start_cell] = Some(Item::new(new_item.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_dirty_cell(&mut self, idx: usize) {
|
||||||
|
self.line[idx].dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_item_mut(&mut self, cell_idx: usize) -> Option<&mut Item> {
|
||||||
|
self.item_line[ self.cell_to_item(cell_idx) ].as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cell_to_item(&self, cell_idx: usize) -> usize {
|
||||||
|
for i in (cell_idx..0).rev() {
|
||||||
|
if self.item_line[i].is_some() {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,11 +157,16 @@ impl StyledLine {
|
|||||||
line_str.push(cell.ch);
|
line_str.push(cell.ch);
|
||||||
let len = line_str.len();
|
let len = line_str.len();
|
||||||
|
|
||||||
for i in byte_offset..byte_offset + len {
|
for _ in byte_offset..byte_offset + len {
|
||||||
cell_to_byte.push(cell_idx);
|
cell_to_byte.push(cell_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
insert_attrs(cell, &attr_list, byte_offset as u32, (byte_offset + len) as u32);
|
insert_attrs(
|
||||||
|
cell,
|
||||||
|
&attr_list,
|
||||||
|
byte_offset as u32,
|
||||||
|
(byte_offset + len) as u32,
|
||||||
|
);
|
||||||
|
|
||||||
byte_offset += len;
|
byte_offset += len;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ pub struct UiModel {
|
|||||||
impl UiModel {
|
impl UiModel {
|
||||||
pub fn new(rows: u64, columns: u64) -> UiModel {
|
pub fn new(rows: u64, columns: u64) -> UiModel {
|
||||||
let mut model = Vec::with_capacity(rows as usize);
|
let mut model = Vec::with_capacity(rows as usize);
|
||||||
for i in 0..rows as usize {
|
for _ in 0..rows as usize {
|
||||||
model.push(Line::new(columns as usize));
|
model.push(Line::new(columns as usize));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +43,10 @@ impl UiModel {
|
|||||||
&self.model
|
&self.model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn model_mut(&mut self) -> &mut [Line] {
|
||||||
|
&mut self.model
|
||||||
|
}
|
||||||
|
|
||||||
pub fn limit_to_model(&self, clip: &mut ModelRect) {
|
pub fn limit_to_model(&self, clip: &mut ModelRect) {
|
||||||
clip.left = if clip.left >= self.columns {
|
clip.left = if clip.left >= self.columns {
|
||||||
self.columns - 1
|
self.columns - 1
|
||||||
@ -92,7 +96,11 @@ impl UiModel {
|
|||||||
|
|
||||||
pub fn put(&mut self, text: &str, attrs: Option<&Attrs>) -> ModelRect {
|
pub fn put(&mut self, text: &str, attrs: Option<&Attrs>) -> ModelRect {
|
||||||
let mut changed_region = self.cur_point();
|
let mut changed_region = self.cur_point();
|
||||||
let mut cell = &mut self.model[self.cur_row][self.cur_col];
|
let mut line = &mut self.model[self.cur_row];
|
||||||
|
line.dirty_line = true;
|
||||||
|
line.mark_dirty_cell(self.cur_col);
|
||||||
|
|
||||||
|
let mut cell = &mut line[self.cur_col];
|
||||||
|
|
||||||
cell.ch = text.chars().last().unwrap_or(' ');
|
cell.ch = text.chars().last().unwrap_or(' ');
|
||||||
cell.attrs = attrs.map(Attrs::clone).unwrap_or_else(Attrs::new);
|
cell.attrs = attrs.map(Attrs::clone).unwrap_or_else(Attrs::new);
|
||||||
|
Loading…
Reference in New Issue
Block a user