Skip to content

Commit e36148f

Browse files
committed
reduce font size of too long lines
lines that are too longs with non-monospace fonts, for example: WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW are now rendered in smaller font. the font size is dynamically computed so that the text is not too small (if possible). + minor changes + update readme + bump to 0.0.6
1 parent 4c8c7f7 commit e36148f

9 files changed

Lines changed: 162 additions & 112 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
u-k-le-guin*
2+
toolong.md
23

34
release.sh
45
profile.sh

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "leguinvim"
3-
version = "0.0.5"
3+
version = "0.0.6"
44
authors = [
55
"daa84 <daa84@inbox.ru>",
66
"Lyude Paul <thatslyude@gmail.com>",

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ __leguinvim__ - neovim gui with non-monospace font support.
22

33
for people who want to use neovim for prose writing: sf novel, master thesis...
44

5-
may look awful with some plugins. still very buggy, with strong [limitations](#limitations).
6-
5+
may look awful with some plugins. still very buggy.
76
if you want to try it anyway, check how to [install](#install) and [configure](#configure) it.
87

9-
the name is a reference to sf writer [ursula k le guin](https://de.wikipedia.org/wiki/Ursula_K._Le_Guin): neovim makes possible to edit text at the speed of thought, just as fast as le guin's characters with their telepathy abilities and their ansibles.
8+
the name is a reference to sf writer [ursula k le guin](https://de.wikipedia.org/wiki/Ursula_K._Le_Guin): neovim makes possible to [edit text at the speed of thought](https://archive.org/details/practical-vim-edit-text-at-the-speed-of-thought), just as fast as le guin's characters with their telepathy abilities and their [ansibles](https://en.wikipedia.org/wiki/Ansible).
109
the project could thus have been named *gtkleguinvim* (because it uses [gtk](https://www.gtk.org/)). but it's too long.
1110

1211
![](./screenshots/completion.png)
@@ -45,17 +44,21 @@ call rpcnotify(1, 'Gui', 'FontMono', 'Fira Code 12') " default
4544
call rpcnotify(1, 'Gui', 'MonoFloat', 1)
4645
```
4746

48-
## limitations
47+
## how it works
4948

5049
the rendering of non-monospace font by __leguinvim__ is a quick and dirty (and ultra-lazy) solution, and kind of a hacky one: __leguinvim__ don't try to wrap the lines by itself, it just lets neovim do it.
5150
some lines could thus be very long, and some other very short..?
5251
yes.
5352
but when writing prose, at least for some languages, and maybe surprisingely, it actually just works, because lines naturally tend to be a mix of narrow, wide and medium-width letters.
54-
except for extreme edge-cases (that __leguinvim__ will try to catch soon):
53+
except for extreme edge-cases, like lines full of bold-uppercased __W__. for these cases, __leguinvim__ reduce the font size for the line, to keep the whole text visible:
5554

56-
![](./screenshots/limitation-out-of-bound-line.png)
55+
![](./screenshots/www.png)
5756

5857
## source
5958

6059
__leguinvim__ is forked from [neovim-gtk](https://github.com/lyude/neovim-gtk).
6160
(lot of removals and changes.)
61+
62+
## todo
63+
64+
this project aims to stay simple. no funky widgets nor smooth scrolling. just monospace fonts: that's still a lot to improves, lot of bugs to fix, of workaround to find!

makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ src := $(shell find -name '*.rs')
55
install_path := $(DEST_DIR)$(PREFIX)/bin/$(name)
66

77
all: $(bin)
8-
./$(bin) u-k*
8+
# ./$(bin) u-k*
9+
./$(bin) toolong.md
910

1011
$(bin): $(src) Cargo.toml
1112
cargo build --release

src/grid.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,6 @@ impl Grid {
132132
pub fn new(id: u64) -> Self {
133133
Grid {
134134
id,
135-
// model: UiModel::default(),
136-
// start_row: 0, // split window
137-
// start_col: 0, // split window
138-
// hidden: false, // e.g. tabs
139-
// floating: false,
140-
// anchor: String::from(""),
141-
// anchor_grid_id: 0,
142-
// zindex: 0,
143-
// compindex: 0,
144-
// border_removed: false,
145-
// border: [false, false, false, false], // top, right, bottom, left
146-
// anchor_pos: (-1, -1),
147-
// monospace: false,
148-
// pix: PixModel::default(),
149135
..Grid::default()
150136
}
151137
}

src/pix_grid.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,29 @@ pub struct PixModel {
1212
rows: usize,
1313
}
1414

15-
fn new_pix_matrix(columns: usize, rows: usize, space_width: f32) -> PixMatrix {
16-
vec![vec![space_width; columns + 1].into_boxed_slice(); rows].into_boxed_slice()
17-
}
18-
1915
impl PixModel {
2016
pub fn new(columns: usize, rows: usize) -> Self {
2117
PixModel {
22-
matrix: new_pix_matrix(columns, rows, 0.0),
2318
columns,
2419
rows,
25-
..PixModel::default()
20+
matrix: vec![vec![0.0; columns + 1].into_boxed_slice(); rows].into_boxed_slice(),
2621
}
2722
}
2823

2924
pub fn from_grid(model: &UiModel, space_width: f32, sign_column: usize) -> Self {
3025
let model = model;
3126
let (rows, columns) = (model.rows, model.columns);
32-
let matrix = new_pix_matrix(columns, rows, space_width);
33-
let mut pix_model = PixModel {
34-
matrix,
35-
columns,
36-
rows,
37-
..PixModel::default()
38-
};
27+
let mut pix_model = PixModel::new(columns, rows);
3928
for (row, line) in model.model().iter().enumerate() {
40-
pix_model.update_line(line, row, space_width, sign_column);
29+
pix_model.update(line, row, space_width, sign_column);
4130
}
4231
pix_model
4332
}
4433

45-
pub fn update_line(&mut self, line: &Line, row: usize, space_width: f32, sign_column: usize) {
34+
pub fn update(&mut self, line: &Line, row: usize, space_width: f32, sign_column: usize) -> f32 {
4635
let pix_line = &mut self.matrix[row];
4736
pix_line.fill(space_width);
37+
let mut last_non_space: usize = sign_column;
4838
for col in sign_column..self.columns {
4939
for item in &line.item_line[col] {
5040
let glyphs = item.glyphs();
@@ -60,31 +50,33 @@ impl PixModel {
6050
pix_line[col + i] = 0.0;
6151
}
6252
}
53+
last_non_space = col + n as usize;
6354
}
6455
}
6556
}
6657
}
67-
self.len_to_pos(row);
58+
self.len_to_pos(row, last_non_space + 1)
6859
}
6960

70-
pub fn update_line_monospace(&mut self, row: usize, space_width: f32) {
61+
pub fn update_mono(&mut self, row: usize, space_width: f32) -> f32 {
7162
self.matrix[row].fill(space_width);
72-
self.len_to_pos(row);
63+
self.len_to_pos(row, self.matrix[row].len())
7364
}
7465

75-
fn len_to_pos(&mut self, row: usize) {
66+
fn len_to_pos(&mut self, row: usize, last_non_space: usize) -> f32 {
7667
/* compute the horizontal position in pixel of each cell
7768
* we just reuse the same array here, because the previous one
7869
* is not usefull anymore
7970
* */
8071
let pix_line = &mut self.matrix[row];
8172
let mut x: f32 = 0.0;
82-
for i in 0..self.columns {
73+
for i in 0..last_non_space {
8374
let len = pix_line[i];
8475
pix_line[i] = x;
8576
x += len;
8677
}
8778
pix_line[self.columns] = pix_line[self.columns - 1];
79+
x
8880
}
8981

9082
pub fn fit(&self, model: &UiModel) -> bool {

src/render/context.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use fnv::FnvHashMap;
12
use std::collections::HashSet;
23

3-
use pango::{self, prelude::*};
4+
use pango::{self, FontDescription, prelude::*};
45

56
use super::itemize::ItemizeIterator;
67
use crate::ui_model::StyledLine;
@@ -9,21 +10,67 @@ pub struct Context {
910
font_metrics: FontMetrix,
1011
font_features: FontFeatures,
1112
line_space: i32,
13+
size: i32,
14+
}
15+
16+
pub struct SubCtx {
17+
font_map: pango::FontMap,
18+
super_desc: FontDescription,
19+
ctxs: FnvHashMap<i32, Context>,
20+
}
21+
22+
impl SubCtx {
23+
pub fn new(pango_context: pango::Context, font_desc: FontDescription) -> Self {
24+
Self {
25+
font_map: pango_context.font_map().unwrap(),
26+
super_desc: FontDescription::from_string(&font_desc.to_str()),
27+
ctxs: FnvHashMap::default(),
28+
}
29+
}
30+
31+
pub fn get(&self, size: i32) -> Option<&Context> {
32+
self.ctxs.get(&size)
33+
}
34+
35+
pub fn max_smaller_size(&self, ratio: f32) -> i32 {
36+
((self.super_desc.size() as f32 * ratio) * 0.8).trunc() as i32
37+
}
38+
39+
fn add_smaller_font(&mut self, new_size: i32) {
40+
let mut new_desc = self.super_desc.clone();
41+
new_desc.set_size(new_size);
42+
let pango_ctx = self.font_map.create_context();
43+
pango_ctx.set_font_description(Some(&new_desc));
44+
self.ctxs
45+
.insert(new_size, Context::new(pango_ctx, new_size));
46+
}
47+
48+
pub fn get_or_create(&mut self, size: i32) -> &Context {
49+
if !self.ctxs.contains_key(&size) {
50+
self.add_smaller_font(size);
51+
}
52+
self.get(size).unwrap()
53+
}
1254
}
1355

1456
impl Context {
15-
pub fn new(pango_context: pango::Context) -> Self {
57+
pub fn new(pango_context: pango::Context, size: i32) -> Self {
1658
Context {
1759
line_space: 0,
1860
font_metrics: FontMetrix::new(pango_context, 0),
1961
font_features: FontFeatures::new(),
62+
size,
2063
}
2164
}
2265

2366
pub fn update(&mut self, pango_context: pango::Context) {
2467
self.font_metrics = FontMetrix::new(pango_context, self.line_space);
2568
}
2669

70+
pub fn size(&self) -> i32 {
71+
self.size
72+
}
73+
2774
pub fn update_font_features(&mut self, font_features: FontFeatures) {
2875
self.font_features = font_features;
2976
}

src/render/mod.rs

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod context;
22
mod itemize;
33

4-
pub use self::context::{CellMetrics, Context, FontFeatures};
4+
pub use self::context::{CellMetrics, Context, FontFeatures, SubCtx};
55

66
use log::warn;
77

@@ -672,54 +672,69 @@ fn snapshot_cell(
672672

673673
pub fn shape_dirty(
674674
ctx: &context::Context,
675-
ui_model: &mut ui_model::UiModel,
676-
pix: &mut PixModel,
675+
sub_ctxs: &mut SubCtx,
676+
grid: &mut Grid,
677677
hl: &HighlightMap,
678678
update_pix: bool,
679-
sign_column: usize,
680679
monospace: bool,
681680
) {
682-
let space_width = ctx.cell_metrics().char_width as f32;
683-
684-
for (row, line) in ui_model.model_mut().iter_mut().enumerate() {
681+
let cell_metrics = ctx.cell_metrics();
682+
let space_width = cell_metrics.char_width as f32;
683+
let sign_column = grid.sign_column_len();
684+
let width = (grid.rect.0 + grid.rect.2) as f32;
685+
for (row, line) in grid.model.model_mut().iter_mut().enumerate() {
685686
if !line.dirty_line {
686687
continue;
687688
}
688-
689-
let styled_line = ui_model::StyledLine::from(line, hl, ctx.font_features());
690-
let items = ctx.itemize(&styled_line);
691-
line.merge(&styled_line, &items);
692-
693-
for (col, cell) in line.line.iter_mut().enumerate() {
694-
if cell.dirty {
695-
for item in &mut *line.item_line[col] {
696-
let mut glyphs = pango::GlyphString::new();
697-
{
698-
let analysis = item.analysis();
699-
let offset = item.item.offset() as usize;
700-
let length = item.item.length() as usize;
701-
if let Some(line_str) = styled_line.line_str.get(offset..offset + length) {
702-
pango::shape(line_str, analysis, &mut glyphs);
703-
} else {
704-
warn!("Wrong itemize split");
705-
}
706-
}
707-
708-
item.set_glyphs(glyphs);
709-
}
689+
shape_dirty_line(line, hl, ctx);
690+
line.dirty_line = false;
691+
if !update_pix {
692+
continue;
693+
}
694+
if monospace {
695+
grid.pix.update_mono(row, space_width);
696+
continue;
697+
}
698+
let x = grid.pix.update(line, row, space_width, sign_column);
699+
if x <= width {
700+
continue;
701+
}
702+
let mut ratio = 1.0;
703+
loop {
704+
ratio -= 0.05;
705+
let size = sub_ctxs.max_smaller_size(ratio);
706+
for cell in line.line.iter_mut().skip(sign_column) {
707+
cell.dirty = true;
708+
}
709+
shape_dirty_line(line, hl, sub_ctxs.get_or_create(size));
710+
if grid.pix.update(line, row, space_width, sign_column) <= width {
711+
break;
710712
}
711-
712-
cell.dirty = false;
713713
}
714+
}
715+
}
714716

715-
if update_pix {
716-
if monospace {
717-
pix.update_line_monospace(row, space_width);
718-
} else {
719-
pix.update_line(line, row, space_width, sign_column);
717+
fn shape_dirty_line(line: &mut Line, hl: &HighlightMap, ctx: &Context) {
718+
let styled_line = ui_model::StyledLine::from(line, hl, ctx.font_features());
719+
let items = ctx.itemize(&styled_line);
720+
line.merge(&styled_line, &items);
721+
for (col, cell) in line.line.iter_mut().enumerate() {
722+
if cell.dirty {
723+
for item in &mut *line.item_line[col] {
724+
let mut glyphs = pango::GlyphString::new();
725+
{
726+
let analysis = item.analysis();
727+
let offset = item.item.offset() as usize;
728+
let length = item.item.length() as usize;
729+
if let Some(line_str) = styled_line.line_str.get(offset..offset + length) {
730+
pango::shape(line_str, analysis, &mut glyphs);
731+
} else {
732+
warn!("Wrong itemize split");
733+
}
734+
}
735+
item.set_glyphs(glyphs);
720736
}
721737
}
722-
723-
line.dirty_line = false;
738+
cell.dirty = false;
724739
}
725740
}

0 commit comments

Comments
 (0)