Skip to content

Commit

Permalink
SDF-based mechanism for making letters bold (#3385)
Browse files Browse the repository at this point in the history
  • Loading branch information
farmaazon authored Apr 12, 2022
1 parent 6b7622d commit 8ca4e2b
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 56 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@
- [Fixed developer console error about failing to decode a notification
"executionContext/visualisationEvaluationFailed"][3193]

#### EnsoGL (rendering engine)

- [You can change font and set letters bold in the <code>text::Area</code>
component][3385]. Use the <code>set_font</code> and
<code>set_bold_bytes</code> respectively.

#### Enso Standard Library

- [Implemented `Vector.distinct` allowing to remove duplicate elements from a
Expand Down Expand Up @@ -152,6 +158,7 @@
[3379]: https://github.com/enso-org/enso/pull/3379
[3381]: https://github.com/enso-org/enso/pull/3381
[3383]: https://github.com/enso-org/enso/pull/3383
[3385]: https://github.com/enso-org/enso/pull/3385
[3392]: https://github.com/enso-org/enso/pull/3392

#### Enso Compiler
Expand Down
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/rust/ensogl/component/text/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ensogl-core = { path = "../../core" }
ensogl-text-embedded-fonts = { path = "embedded-fonts" }
ensogl-text-msdf-sys = { path = "msdf-sys" }
ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
const_format = "0.2.22"
xi-rope = { version = "0.3.0" }

[dev-dependencies]
Expand Down
8 changes: 8 additions & 0 deletions lib/rust/ensogl/component/text/src/buffer/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def_style_property!(Size(f32));
def_style_property!(Bold(bool));
def_style_property!(Italic(bool));
def_style_property!(Underline(bool));
def_style_property!(SdfBold(f32));

impl Default for Size {
fn default() -> Self {
Expand All @@ -247,12 +248,19 @@ impl Default for Underline {
}
}

impl Default for SdfBold {
fn default() -> Self {
Self::new(0.0)
}
}

define_styles! {
size : Size,
color : color::Rgba,
bold : Bold,
italics : Italic,
underline : Underline,
sdf_bold : SdfBold,
}


Expand Down
4 changes: 3 additions & 1 deletion lib/rust/ensogl/component/text/src/buffer/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ ensogl_core::define_endpoints! {
redo (),
set_default_color (color::Rgba),
set_default_text_size (style::Size),
set_color_bytes (buffer::Range<Bytes>,color::Rgba),
set_color_bytes (buffer::Range<Bytes>, color::Rgba),
set_sdf_bold (buffer::Range<Bytes>, style::SdfBold),
}

Output {
Expand Down Expand Up @@ -449,6 +450,7 @@ impl View {
eval input.set_default_color ((t) m.set_default(*t));
eval input.set_default_text_size ((t) m.set_default(*t));
eval input.set_color_bytes (((range,color)) m.replace(range,*color));
eval input.set_sdf_bold (((range,value)) m.replace(range,*value));
eval input.set_default_color ((color) m.set_default(*color));

output.source.selection_edit_mode <+ sel_on_modification;
Expand Down
62 changes: 50 additions & 12 deletions lib/rust/ensogl/component/text/src/component/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,16 @@ ensogl_core::define_endpoints! {
insert (String),
set_color_bytes (buffer::Range<Bytes>,color::Rgba),
set_color_all (color::Rgba),
set_sdf_bold (buffer::Range<Bytes>,style::SdfBold),
set_default_color (color::Rgba),
set_selection_color (color::Rgb),
set_default_text_size (style::Size),
/// Set font in the text area. The name will be looked up in [`typeface::font::Registry`].
///
/// Note, that this is a relatively heavy operation - it requires not only redrawing all
/// lines, but also re-load internal structures for rendering (like WebGL buffers,
/// MSDF texture, etc.).
set_font (String),
set_content (String),
}
Output {
Expand Down Expand Up @@ -474,6 +481,11 @@ impl Area {
input.remove_all_cursors();
});

// === Font ===

eval input.set_font ((t) m.set_font(t));


// === Colors ===

eval input.set_default_color ((t) m.buffer.frp.set_default_color(*t));
Expand All @@ -495,6 +507,13 @@ impl Area {
});
self.frp.source.selection_color <+ self.frp.set_selection_color;


// === Style ===

m.buffer.frp.set_sdf_bold <+ input.set_sdf_bold;
eval_ input.set_sdf_bold (m.redraw(false));


// === Changes ===

// The `content` event should be fired first, as any listener for `changed` may want to
Expand Down Expand Up @@ -545,7 +564,7 @@ impl Area {

#[cfg(target_arch = "wasm32")]
fn symbols(&self) -> SmallVec<[display::Symbol; 1]> {
let text_symbol = self.data.glyph_system.sprite_system().symbol.clone_ref();
let text_symbol = self.data.glyph_system.borrow().sprite_system().symbol.clone_ref();
let shapes = &self.data.app.display.default_scene.shapes;
let selection_system = shapes.shape_system(PhantomData::<selection::shape::Shape>);
let _selection_symbol = selection_system.shape_system.symbol.clone_ref();
Expand Down Expand Up @@ -573,7 +592,7 @@ pub struct AreaModel {
buffer: buffer::View,
display_object: display::object::Instance,
#[cfg(target_arch = "wasm32")]
glyph_system: glyph::System,
glyph_system: Rc<RefCell<glyph::System>>,
lines: Lines,
single_line: Rc<Cell<bool>>,
selection_map: Rc<RefCell<SelectionMap>>,
Expand All @@ -586,21 +605,20 @@ impl AreaModel {
let scene = &app.display.default_scene;
let logger = Logger::new("text_area");
let selection_map = default();
let display_object = display::object::Instance::new(&logger);
#[cfg(target_arch = "wasm32")]
let glyph_system = {
let fonts = scene.extension::<typeface::font::Registry>();
let font = fonts.load("DejaVuSansMono");
typeface::glyph::System::new(&scene, font)
let glyph_system = typeface::glyph::System::new(&scene, font);
display_object.add_child(&glyph_system);
Rc::new(RefCell::new(glyph_system))
};
let display_object = display::object::Instance::new(&logger);
let buffer = default();
let lines = default();
let single_line = default();
let camera = Rc::new(CloneRefCell::new(scene.camera().clone_ref()));

#[cfg(target_arch = "wasm32")]
display_object.add_child(&glyph_system);

// FIXME[WD]: These settings should be managed wiser. They should be set up during
// initialization of the shape system, not for every area creation. To be improved during
// refactoring of the architecture some day.
Expand Down Expand Up @@ -792,12 +810,13 @@ impl AreaModel {
let line_object = line.display_object().clone_ref();
let line_range = self.buffer.byte_range_of_view_line_index_snapped(view_line_index.into());
let mut line_style = self.buffer.sub_style(line_range.start..line_range.end).iter();
let mut pen = pen::Pen::new(&self.glyph_system.font);
let glyph_system = self.glyph_system.borrow();
let mut pen = pen::Pen::new(&glyph_system.font);
let mut divs = vec![];
let mut column = 0.column();
let mut last_cursor = None;
let mut last_cursor_target = default();
line.resize_with(content.chars().count(), || self.glyph_system.new_glyph());
line.resize_with(content.chars().count(), || glyph_system.new_glyph());
let mut iter = line.glyphs.iter_mut().zip(content.chars());
loop {
let next = iter.next();
Expand All @@ -822,15 +841,16 @@ impl AreaModel {
Some((glyph, chr)) => {
let chr_bytes: Bytes = chr.len_utf8().into();
line_style.drop(chr_bytes - 1.bytes());
let glyph_info = self.glyph_system.font.glyph_info(chr);
let size = glyph_info.scale.scale(chr_size);
let glyph_info = glyph_system.font.glyph_info(chr);
let glyph_offset = glyph_info.offset.scale(chr_size);
let glyph_x = info.offset + glyph_offset.x;
let glyph_y = glyph_offset.y;
glyph.set_position_xy(Vector2(glyph_x, glyph_y));
glyph.set_char(chr);
glyph.set_color(style.color);
glyph.size.set(size);
glyph.set_bold(style.bold.raw);
glyph.set_sdf_bold(style.sdf_bold.raw);
glyph.set_font_size(chr_size);
match &last_cursor {
None => line_object.add_child(glyph),
Some(cursor) => {
Expand Down Expand Up @@ -920,6 +940,24 @@ impl AreaModel {
let end = self.buffer.snap_location(selection.end);
selection.with_start(start).with_end(end)
}

#[cfg(target_arch = "wasm32")]
fn set_font(&self, font_name: &str) {
let app = &self.app;
let scene = &app.display.default_scene;
let fonts = scene.extension::<typeface::font::Registry>();
let font = fonts.load(font_name);
let glyph_system = typeface::glyph::System::new(&scene, font);
self.display_object.add_child(&glyph_system);
let old_glyph_system = self.glyph_system.replace(glyph_system);
self.display_object.remove_child(&old_glyph_system);
// Remove old Glyph structures, as they still refer to the old Glyph System.
self.lines.rc.take();
self.redraw(true);
}

#[cfg(not(target_arch = "wasm32"))]
fn set_font(&self, _font_name: &str) {}
}

impl display::Object for AreaModel {
Expand Down
32 changes: 23 additions & 9 deletions lib/rust/ensogl/component/text/src/typeface/glsl/glyph.glsl
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
// A factor describing much the bold letters will be fattened, expressed as the fraction of font size.
const float BOLD_FATTING = 0.04;

highp float median(highp vec3 v) {
return max(min(v.x, v.y), min(max(v.x, v.y), v.z));
}

highp vec2 get_scaled_uv() {
/// Compute the uv coordinates of the MSDF texture fragment where it should be sampled.
///
/// Essentially, it's an input_uv which is a bit transformed to "cut off" the half of the MSDF cell from each side. This
/// way we have better pixel alignment on low resolutions.
highp vec2 msdf_fragment_uv() {
highp vec2 msdf_cell_size = 1.0/input_msdf_size;
highp vec2 offset = msdf_cell_size/2.0;
highp vec2 scale = 1.0 - msdf_cell_size;
return offset + input_uv * scale;
}

highp vec2 get_texture_coord() {
highp vec2 msdf_fragment_size = input_msdf_size / vec2(textureSize(input_atlas,0));
highp vec2 msdf_fragment_size = input_msdf_size / vec2(textureSize(input_atlas, 0));
highp vec2 offset = vec2(0.0, input_atlas_index) * msdf_fragment_size;
return offset + get_scaled_uv() * msdf_fragment_size;
return offset + msdf_fragment_uv() * msdf_fragment_size;
}

highp float get_fatting() {
bool glyph_is_bold = (input_style & STYLE_BOLD_FLAG) != 0;
highp vec2 local_to_px_ratio = 1.0 / fwidth(input_local.xy);
highp float font_size_px = input_font_size * (local_to_px_ratio.x + local_to_px_ratio.y) / 2.0;
highp float fatting = (glyph_is_bold ? BOLD_FATTING : 0.0) + input_sdf_bold;
return font_size_px * fatting;
}

highp float msdf_alpha() {
Expand All @@ -23,12 +38,11 @@ highp float msdf_alpha() {

// We use this parameter to fatten somewhat font on low resolutions. The thershold and exact
// value of this fattening was picked by trial an error, searching for best rendering effect.
highp float dpi_dilate = avg_msdf_unit_px < input_msdf_range*0.49 ? 1.0 : 0.0;

highp vec3 msdf_sample = texture(input_atlas,tex_coord).rgb;
highp float sig_dist = median(msdf_sample) - 0.5;
highp float sig_dist_px = sig_dist * avg_msdf_unit_px;
highp float opacity = 0.5 + sig_dist_px + dpi_dilate * 0.08;
highp float dpi_dilate = avg_msdf_unit_px < input_msdf_range*0.49 ? 1.0 : 0.0;
highp vec3 msdf_sample = texture(input_atlas,tex_coord).rgb;
highp float sig_dist = median(msdf_sample) - 0.5;
highp float sig_dist_px = sig_dist * avg_msdf_unit_px + get_fatting();
highp float opacity = 0.5 + sig_dist_px + dpi_dilate * 0.08;
opacity = clamp(opacity, 0.0, 1.0);
return opacity;
}
Expand Down
29 changes: 22 additions & 7 deletions lib/rust/ensogl/component/text/src/typeface/glsl/glyph_mac.glsl
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
// A factor describing much the bold letters will be fattened, expressed as the fraction of font size.
const float BOLD_FATTING = 0.04;

highp float median(highp vec3 v) {
return max(min(v.x, v.y), min(max(v.x, v.y), v.z));
}

highp vec2 get_scaled_uv() {
/// Compute the uv coordinates of the MSDF texture fragment where it should be sampled.
///
/// Essentially, it's an input_uv which is a bit transformed to "cut off" the half of the MSDF cell from each side. This
/// way we have better pixel alignment on low resolutions.
highp vec2 msdf_fragment_uv() {
highp vec2 msdf_cell_size = 1.0/input_msdf_size;
highp vec2 offset = msdf_cell_size/2.0;
highp vec2 scale = 1.0 - msdf_cell_size;
return offset + input_uv * scale;
}


highp vec2 get_texture_coord() {
highp vec2 msdf_fragment_size = input_msdf_size / vec2(textureSize(input_atlas,0));
highp vec2 offset = vec2(0.0, input_atlas_index) * msdf_fragment_size;
return offset + get_scaled_uv() * msdf_fragment_size;
}

highp float get_fatting() {
bool glyph_is_bold = (input_style & STYLE_BOLD_FLAG) != 0;
highp vec2 local_to_px_ratio = 1.0 / fwidth(input_local.xy);
highp float font_size_px = input_font_size * (local_to_px_ratio.x + local_to_px_ratio.y) / 2.0;
highp float fatting = (glyph_is_bold ? BOLD_FATTING : 0.0) + input_sdf_bold;
return font_size_px * fatting;
}

// FIXME
// The following function uses non-standard font adjustiments (lines marked with FIXME). They make
// the font bolder and more crisp. It was designed to look nice on nodes in the GUI but leaves the
Expand All @@ -28,12 +44,11 @@ highp float msdf_alpha() {

// We use this parameter to fatten somewhat font on low resolutions. The thershold and exact
// value of this fattening was picked by trial an error, searching for best rendering effect.
highp float dpi_dilate = avg_msdf_unit_px < input_msdf_range*0.49 ? 1.0 : 0.0;

highp vec3 msdf_sample = texture(input_atlas,tex_coord).rgb;
highp float sig_dist = median(msdf_sample) - 0.5;
highp float sig_dist_px = sig_dist * avg_msdf_unit_px;
highp float opacity = 0.5 + sig_dist_px + dpi_dilate * 0.08;
highp float dpi_dilate = avg_msdf_unit_px < input_msdf_range*0.49 ? 1.0 : 0.0;
highp vec3 msdf_sample = texture(input_atlas,tex_coord).rgb;
highp float sig_dist = median(msdf_sample) - 0.5;
highp float sig_dist_px = sig_dist * avg_msdf_unit_px + get_fatting();
highp float opacity = 0.5 + sig_dist_px + dpi_dilate * 0.08;
opacity += 0.6; // FIXME: Widen + sharpen
opacity = clamp(opacity, 0.0, 1.0);
opacity = pow(opacity,3.0); // FIXME: sharpen
Expand Down
Loading

0 comments on commit 8ca4e2b

Please sign in to comment.