Skip to content

Commit

Permalink
Hyphenate ragged paragraphs
Browse files Browse the repository at this point in the history
Fixes #285.
  • Loading branch information
baskerville committed Nov 30, 2023
1 parent 1cb7093 commit babe9a9
Showing 1 changed file with 71 additions and 65 deletions.
136 changes: 71 additions & 65 deletions crates/core/src/document/html/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,15 @@ impl Engine {
} else if end_index < text.len() {
let penalty = if c == '-' { self.hyphen_penalty } else { 0 };
let flagged = penalty > 0;
items.push(ParagraphItem::Penalty { width: 0, penalty, flagged });
if matches!(parent_style.text_align, TextAlign::Justify | TextAlign::Center) {
items.push(ParagraphItem::Penalty { width: 0, penalty, flagged });
} else {
let stretch = 3 * space_plan.glyph_advance(0);
items.push(ParagraphItem::Penalty { width: 0, penalty: INFINITE_PENALTY, flagged: false });
items.push(ParagraphItem::Glue { width: 0, stretch, shrink: 0 });
items.push(ParagraphItem::Penalty { width: 0, penalty: 10*penalty, flagged: true });
items.push(ParagraphItem::Glue { width: 0, stretch: -stretch, shrink: 0 });
}
}
}
start_index += chunk.len();
Expand Down Expand Up @@ -1004,11 +1012,6 @@ impl Engine {
style.text_indent
};

let stretch_tolerance = if style.text_align == TextAlign::Justify {
self.stretch_tolerance
} else {
10.0
};
let (ascender, descender) = {
let fonts = self.fonts.as_mut().unwrap();
let font = fonts.get_mut(style.font_kind, style.font_style, style.font_weight);
Expand Down Expand Up @@ -1118,25 +1121,21 @@ impl Engine {
let mut line_lengths: Vec<i32> = para_shape.iter().map(|(a, b)| b - a).collect();
line_lengths[0] -= text_indent;

let mut bps = total_fit(&items, &line_lengths, stretch_tolerance, 0);
let mut bps = total_fit(&items, &line_lengths, self.stretch_tolerance, 0);

let mut hyph_indices = Vec::new();
let mut glue_drifts = Vec::new();

if bps.is_empty() {
let dictionary = if style.text_align == TextAlign::Justify {
hyph_lang(style.language.as_ref().map_or(DEFAULT_HYPH_LANG, String::as_str))
.and_then(|lang| HYPHENATION_PATTERNS.get(&lang))
} else {
None
};

items = self.hyphenate_paragraph(dictionary, items, &mut hyph_indices);
bps = total_fit(&items, &line_lengths, stretch_tolerance, 0);
if bps.is_empty() && style.text_align != TextAlign::Center {
if let Some(dictionary) = hyph_lang(style.language.as_ref().map_or(DEFAULT_HYPH_LANG, String::as_str))
.and_then(|lang| HYPHENATION_PATTERNS.get(&lang)) {
items = self.hyphenate_paragraph(style, dictionary, items, &mut hyph_indices);
bps = total_fit(&items, &line_lengths, self.stretch_tolerance, 0);
}
}

if bps.is_empty() {
bps = standard_fit(&items, &line_lengths, stretch_tolerance);
bps = standard_fit(&items, &line_lengths, self.stretch_tolerance);
}

if bps.is_empty() {
Expand Down Expand Up @@ -1495,69 +1494,72 @@ impl Engine {
}
}

fn hyphenate_paragraph(&mut self, dictionary: Option<&Standard>, items: Vec<ParagraphItem<ParagraphElement>>, hyph_indices: &mut Vec<[usize; 2]>) -> Vec<ParagraphItem<ParagraphElement>> {
fn hyphenate_paragraph(&mut self, style: &StyleData, dictionary: &Standard, items: Vec<ParagraphItem<ParagraphElement>>, hyph_indices: &mut Vec<[usize; 2]>) -> Vec<ParagraphItem<ParagraphElement>> {
let mut hyph_items = Vec::with_capacity(items.len());

for itm in items {
match itm {
ParagraphItem::Box { data: ParagraphElement::Text(ref element), .. } => {
let text = &element.text;
let hyphen_width = if dictionary.is_some() {
let (hyphen_width, stretch) = {
let font = self.fonts.as_mut().unwrap()
.get_mut(element.font_kind, element.font_style, element.font_weight);
font.set_size(element.font_size, self.dpi);
font.plan("-", None, element.font_features.as_deref()).width
} else {
0
let plan = font.plan(" -", None, element.font_features.as_deref());
(plan.glyph_advance(1), 3 * plan.glyph_advance(0))
};

if let Some(dict) = dictionary {
let mut index_before = text.find(char::is_alphabetic).unwrap_or_else(|| text.len());
if index_before > 0 {
let subelem = self.box_from_chunk(&text[0..index_before],
0,
let mut index_before = text.find(char::is_alphabetic).unwrap_or_else(|| text.len());
if index_before > 0 {
let subelem = self.box_from_chunk(&text[0..index_before],
0,
element);
hyph_items.push(subelem);
}

let mut index_after = text[index_before..].find(|c: char| !c.is_alphabetic())
.map(|i| index_before + i)
.unwrap_or_else(|| text.len());
while index_before < index_after {
let mut index = 0;
let chunk = &text[index_before..index_after];
let len_before = hyph_items.len();

for segment in dictionary.hyphenate(chunk).iter().segments() {
let subelem = self.box_from_chunk(segment,
index_before + index,
element);
hyph_items.push(subelem);
}

let mut index_after = text[index_before..].find(|c: char| !c.is_alphabetic())
.map(|i| index_before + i)
.unwrap_or_else(|| text.len());
while index_before < index_after {
let mut index = 0;
let chunk = &text[index_before..index_after];
let len_before = hyph_items.len();
for segment in dict.hyphenate(chunk).iter().segments() {
let subelem = self.box_from_chunk(segment,
index_before + index,
element);
hyph_items.push(subelem);
index += segment.len();
if index < chunk.len() {
index += segment.len();
if index < chunk.len() {
if style.text_align == TextAlign::Justify {
hyph_items.push(ParagraphItem::Penalty { width: hyphen_width, penalty: self.hyphen_penalty, flagged: true });
} else {
hyph_items.push(ParagraphItem::Penalty { width: 0, penalty: INFINITE_PENALTY, flagged: false });
hyph_items.push(ParagraphItem::Glue { width: 0, stretch, shrink: 0 });
hyph_items.push(ParagraphItem::Penalty { width: hyphen_width, penalty: 10*self.hyphen_penalty, flagged: true });
hyph_items.push(ParagraphItem::Glue { width: 0, stretch: -stretch, shrink: 0 });
}
}
let len_after = hyph_items.len();
if len_after > 1 + len_before {
hyph_indices.push([len_before, len_after]);
}
index_before = text[index_after..].find(char::is_alphabetic)
.map(|i| index_after + i)
.unwrap_or_else(|| text.len());
if index_before > index_after {
let subelem = self.box_from_chunk(&text[index_after..index_before],
index_after,
&element);
hyph_items.push(subelem);
}
}

index_after = text[index_before..].find(|c: char| !c.is_alphabetic())
.map(|i| index_before + i)
.unwrap_or_else(|| text.len());
let len_after = hyph_items.len();
if len_after > 1 + len_before {
hyph_indices.push([len_before, len_after]);
}
} else {
let subelem = self.box_from_chunk(text, 0, element);
hyph_items.push(subelem);
index_before = text[index_after..].find(char::is_alphabetic)
.map(|i| index_after + i)
.unwrap_or_else(|| text.len());
if index_before > index_after {
let subelem = self.box_from_chunk(&text[index_after..index_before],
index_after,
&element);
hyph_items.push(subelem);
}

index_after = text[index_before..].find(|c: char| !c.is_alphabetic())
.map(|i| index_before + i)
.unwrap_or_else(|| text.len());
}
},
_ => { hyph_items.push(itm) },
Expand Down Expand Up @@ -1612,7 +1614,13 @@ impl Engine {

line_stats = LineStats::default();
merged_items.push(itm);
if i >= start_index && i < end_index {
start_index = i + 1;
}
} else if i >= start_index && i < end_index {
if i > start_index {
index_drift += 1;
}
if let ParagraphItem::Box { width, data } = itm {
match merged_element {
ParagraphElement::Text(TextElement { ref mut text, .. }) => {
Expand All @@ -1627,8 +1635,6 @@ impl Engine {
if !line_stats.started {
line_stats.started = true;
}
} else {
index_drift += 2;
}
if i == end_index - 1 {
j += 1;
Expand Down

0 comments on commit babe9a9

Please sign in to comment.