Add microjustification

This commit is contained in:
diquah 2025-04-09 23:20:46 -07:00
parent 94a497a01f
commit 7f9adfac22
4 changed files with 55 additions and 1 deletions

View File

@ -71,6 +71,16 @@ impl Line<'_> {
count
}
/// How many glyphs are in the text where we can insert micro-amounts
/// of additional space when encountering underfull lines.
fn microjustifiables(&self) -> usize {
let mut count = 0;
for shaped in self.items.iter().filter_map(Item::text) {
count += shaped.microjustifiables();
}
count
}
/// How much the line can stretch.
pub fn stretchability(&self) -> Abs {
self.items
@ -472,6 +482,7 @@ pub fn commit(
let fr = line.fr();
let mut justification_ratio = 0.0;
let mut extra_justification = Abs::zero();
let mut extra_microjustification = Abs::zero();
let shrinkability = line.shrinkability();
let stretchability = line.stretchability();
@ -487,9 +498,13 @@ pub fn commit(
}
let justifiables = line.justifiables();
let microjustifiables = line.microjustifiables();
if justifiables > 0 && remaining > Abs::zero() {
// Underfull line, distribute the extra space.
extra_justification = remaining / justifiables as f64;
extra_microjustification = (remaining / microjustifiables as f64).min(p.config.microjustification);
extra_justification = (remaining - extra_microjustification * microjustifiables as f64) / justifiables as f64;
remaining = Abs::zero();
}
}
@ -531,6 +546,7 @@ pub fn commit(
&p.spans,
justification_ratio,
extra_justification,
extra_microjustification,
);
push(&mut offset, frame);
}

View File

@ -183,11 +183,13 @@ fn configuration(
situation: Option<ParSituation>,
) -> Config {
let justify = base.justify;
let microjustification = ParElem::microjustification_in(shared);
let font_size = TextElem::size_in(shared);
let dir = TextElem::dir_in(shared);
Config {
justify,
microjustification,
linebreaks: base.linebreaks.unwrap_or_else(|| {
if justify {
Linebreaks::Optimized
@ -267,6 +269,10 @@ struct ConfigBase {
struct Config {
/// Whether to justify text.
justify: bool,
/// The maximum allowed kerning adjustment for microjustification.
microjustification: Abs,
/// How to determine line breaks.
linebreaks: Linebreaks,
/// The indent the first line of a paragraph should have.

View File

@ -83,6 +83,8 @@ pub struct ShapedGlyph {
pub c: char,
/// Whether this glyph is justifiable for CJK scripts.
pub is_justifiable: bool,
/// Whether this glyph is allowed additional kerning for microjustification.
pub is_microjustifiable: bool,
/// The script of the glyph.
pub script: Script,
}
@ -107,6 +109,11 @@ impl ShapedGlyph {
self.is_justifiable
}
/// Whether the glyph is microjustifiable.
pub fn is_microjustifiable(&self) -> bool {
self.is_microjustifiable
}
/// Whether the glyph is part of Chinese or Japanese script (i.e. CJ, not CJK).
pub fn is_cj_script(&self) -> bool {
is_cj_script(self.c, self.script)
@ -216,6 +223,7 @@ impl<'a> ShapedText<'a> {
spans: &SpanMapper,
justification_ratio: f64,
extra_justification: Abs,
extra_microjustification: Abs,
) -> Frame {
let (top, bottom) = self.measure(engine);
let size = Size::new(self.width, top + bottom);
@ -261,6 +269,10 @@ impl<'a> ShapedText<'a> {
justification_right +=
Em::from_length(extra_justification, self.size)
}
if shaped.is_microjustifiable() {
justification_right +=
Em::from_length(extra_microjustification, self.size)
}
frame.size_mut().x += justification_left.at(self.size)
+ justification_right.at(self.size);
@ -375,6 +387,12 @@ impl<'a> ShapedText<'a> {
self.glyphs.iter().filter(|g| g.is_justifiable()).count()
}
/// How many glyphs are in the text that are allowed extra kerning for the
/// use of justification when encountering underfull lines.
pub fn microjustifiables(&self) -> usize {
self.glyphs.iter().filter(|g| g.is_microjustifiable()).count()
}
/// Whether the last glyph is a CJK character which should not be justified
/// on line end.
pub fn cjk_justifiable_at_last(&self) -> bool {
@ -496,6 +514,7 @@ impl<'a> ShapedText<'a> {
safe_to_break: true,
c: '-',
is_justifiable: false,
is_microjustifiable: false,
script: Script::Common,
};
match side {
@ -881,6 +900,7 @@ fn shape_segment<'a>(
x_advance,
Adjustability::default().stretchability,
),
is_microjustifiable: true,
script,
});
} else {
@ -973,6 +993,7 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font: Font) {
x_advance,
Adjustability::default().stretchability,
),
is_microjustifiable: false,
script,
});
};

View File

@ -138,6 +138,17 @@ pub struct ParElem {
#[default(false)]
pub justify: bool,
/// The maximum amount of kerning that is allowed to be used to further
/// justify an existing line. When this value is nonzero, additional
/// justification will be applied to individual glyphs.
///
/// Note that microjustifications are applied only after a line of text has
/// been constructed. This means that the layout will *not* be affected by
/// microjustifications, but the internal kerning of a line will be.
#[resolve]
#[default(Em::new(0.0).into())]
pub microjustification: Length,
/// How to determine line breaks.
///
/// When this property is set to `{auto}`, its default value, optimized line