mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Less style chains lookups during paragraph layout
This commit is contained in:
parent
34fa8df044
commit
029b87b0a9
@ -197,7 +197,7 @@ pub struct Text {
|
|||||||
impl Text {
|
impl Text {
|
||||||
/// The width of the text run.
|
/// The width of the text run.
|
||||||
pub fn width(&self) -> Length {
|
pub fn width(&self) -> Length {
|
||||||
self.glyphs.iter().map(|g| g.x_advance.at(self.size)).sum()
|
self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ impl Layout for ParNode {
|
|||||||
let lines = linebreak(&p, &mut ctx.fonts, regions.first.x);
|
let lines = linebreak(&p, &mut ctx.fonts, regions.first.x);
|
||||||
|
|
||||||
// Stack the lines into one frame per region.
|
// Stack the lines into one frame per region.
|
||||||
Ok(stack(&lines, &ctx.fonts, regions, styles))
|
Ok(stack(&lines, &mut ctx.fonts, regions, styles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,10 +300,8 @@ struct Line<'a> {
|
|||||||
/// and `last` aren't trimmed to the line, but it doesn't matter because
|
/// and `last` aren't trimmed to the line, but it doesn't matter because
|
||||||
/// we're just checking which range an index falls into.
|
/// we're just checking which range an index falls into.
|
||||||
ranges: &'a [Range],
|
ranges: &'a [Range],
|
||||||
/// The size of the line.
|
/// The width of the line.
|
||||||
size: Size,
|
width: Length,
|
||||||
/// The baseline of the line.
|
|
||||||
baseline: Length,
|
|
||||||
/// The sum of fractions in the line.
|
/// The sum of fractions in the line.
|
||||||
fr: Fraction,
|
fr: Fraction,
|
||||||
/// Whether the line ends at a mandatory break.
|
/// Whether the line ends at a mandatory break.
|
||||||
@ -457,7 +455,7 @@ fn linebreak_simple<'a>(
|
|||||||
// If the line doesn't fit anymore, we push the last fitting attempt
|
// If the line doesn't fit anymore, we push the last fitting attempt
|
||||||
// into the stack and rebuild the line from its end. The resulting
|
// into the stack and rebuild the line from its end. The resulting
|
||||||
// line cannot be broken up further.
|
// line cannot be broken up further.
|
||||||
if !width.fits(attempt.size.x) {
|
if !width.fits(attempt.width) {
|
||||||
if let Some((last_attempt, last_end)) = last.take() {
|
if let Some((last_attempt, last_end)) = last.take() {
|
||||||
lines.push(last_attempt);
|
lines.push(last_attempt);
|
||||||
start = last_end;
|
start = last_end;
|
||||||
@ -468,7 +466,7 @@ fn linebreak_simple<'a>(
|
|||||||
// Finish the current line if there is a mandatory line break (i.e.
|
// Finish the current line if there is a mandatory line break (i.e.
|
||||||
// due to "\n") or if the line doesn't fit horizontally already
|
// due to "\n") or if the line doesn't fit horizontally already
|
||||||
// since then no shorter line will be possible.
|
// since then no shorter line will be possible.
|
||||||
if mandatory || !width.fits(attempt.size.x) {
|
if mandatory || !width.fits(attempt.width) {
|
||||||
lines.push(attempt);
|
lines.push(attempt);
|
||||||
start = end;
|
start = end;
|
||||||
last = None;
|
last = None;
|
||||||
@ -547,7 +545,7 @@ fn linebreak_optimized<'a>(
|
|||||||
|
|
||||||
// Determine how much the line's spaces would need to be stretched
|
// Determine how much the line's spaces would need to be stretched
|
||||||
// to make it the desired width.
|
// to make it the desired width.
|
||||||
let delta = width - attempt.size.x;
|
let delta = width - attempt.width;
|
||||||
let mut ratio = delta / attempt.stretch();
|
let mut ratio = delta / attempt.stretch();
|
||||||
if ratio.is_infinite() {
|
if ratio.is_infinite() {
|
||||||
ratio = delta / (em / 2.0);
|
ratio = delta / (em / 2.0);
|
||||||
@ -796,8 +794,6 @@ fn line<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut width = Length::zero();
|
let mut width = Length::zero();
|
||||||
let mut top = Length::zero();
|
|
||||||
let mut bottom = Length::zero();
|
|
||||||
let mut fr = Fraction::zero();
|
let mut fr = Fraction::zero();
|
||||||
|
|
||||||
// Measure the size of the line.
|
// Measure the size of the line.
|
||||||
@ -805,16 +801,8 @@ fn line<'a>(
|
|||||||
match item {
|
match item {
|
||||||
ParItem::Absolute(v) => width += *v,
|
ParItem::Absolute(v) => width += *v,
|
||||||
ParItem::Fractional(v) => fr += *v,
|
ParItem::Fractional(v) => fr += *v,
|
||||||
ParItem::Text(shaped) => {
|
ParItem::Text(shaped) => width += shaped.width,
|
||||||
width += shaped.size.x;
|
ParItem::Frame(frame) => width += frame.size.x,
|
||||||
top.set_max(shaped.baseline);
|
|
||||||
bottom.set_max(shaped.size.y - shaped.baseline);
|
|
||||||
}
|
|
||||||
ParItem::Frame(frame) => {
|
|
||||||
width += frame.size.x;
|
|
||||||
top.set_max(frame.baseline());
|
|
||||||
bottom.set_max(frame.size.y - frame.baseline());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -825,8 +813,7 @@ fn line<'a>(
|
|||||||
items,
|
items,
|
||||||
last,
|
last,
|
||||||
ranges: &p.ranges[first_idx ..= last_idx],
|
ranges: &p.ranges[first_idx ..= last_idx],
|
||||||
size: Size::new(width, top + bottom),
|
width,
|
||||||
baseline: top,
|
|
||||||
fr,
|
fr,
|
||||||
mandatory,
|
mandatory,
|
||||||
dash,
|
dash,
|
||||||
@ -836,7 +823,7 @@ fn line<'a>(
|
|||||||
/// Combine layouted lines into one frame per region.
|
/// Combine layouted lines into one frame per region.
|
||||||
fn stack(
|
fn stack(
|
||||||
lines: &[Line],
|
lines: &[Line],
|
||||||
fonts: &FontStore,
|
fonts: &mut FontStore,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Arc<Frame>> {
|
) -> Vec<Arc<Frame>> {
|
||||||
@ -848,7 +835,7 @@ fn stack(
|
|||||||
// should expand or there's fractional spacing, fit-to-width otherwise.
|
// should expand or there's fractional spacing, fit-to-width otherwise.
|
||||||
let mut width = regions.first.x;
|
let mut width = regions.first.x;
|
||||||
if !regions.expand.x && lines.iter().all(|line| line.fr.is_zero()) {
|
if !regions.expand.x && lines.iter().all(|line| line.fr.is_zero()) {
|
||||||
width = lines.iter().map(|line| line.size.x).max().unwrap_or_default();
|
width = lines.iter().map(|line| line.width).max().unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
// State for final frame building.
|
// State for final frame building.
|
||||||
@ -859,7 +846,10 @@ fn stack(
|
|||||||
|
|
||||||
// Stack the lines into one frame per region.
|
// Stack the lines into one frame per region.
|
||||||
for line in lines {
|
for line in lines {
|
||||||
while !regions.first.y.fits(line.size.y) && !regions.in_last() {
|
let frame = commit(line, fonts, width, align, justify);
|
||||||
|
let height = frame.size.y;
|
||||||
|
|
||||||
|
while !regions.first.y.fits(height) && !regions.in_last() {
|
||||||
finished.push(Arc::new(output));
|
finished.push(Arc::new(output));
|
||||||
output = Frame::new(Size::with_x(width));
|
output = Frame::new(Size::with_x(width));
|
||||||
regions.next();
|
regions.next();
|
||||||
@ -870,12 +860,11 @@ fn stack(
|
|||||||
output.size.y += leading;
|
output.size.y += leading;
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = commit(line, fonts, width, align, justify);
|
|
||||||
let pos = Point::with_y(output.size.y);
|
let pos = Point::with_y(output.size.y);
|
||||||
output.size.y += frame.size.y;
|
output.size.y += height;
|
||||||
output.merge_frame(pos, frame);
|
output.merge_frame(pos, frame);
|
||||||
|
|
||||||
regions.first.y -= line.size.y + leading;
|
regions.first.y -= height + leading;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -886,13 +875,12 @@ fn stack(
|
|||||||
/// Commit to a line and build its frame.
|
/// Commit to a line and build its frame.
|
||||||
fn commit(
|
fn commit(
|
||||||
line: &Line,
|
line: &Line,
|
||||||
fonts: &FontStore,
|
fonts: &mut FontStore,
|
||||||
width: Length,
|
width: Length,
|
||||||
align: Align,
|
align: Align,
|
||||||
justify: bool,
|
justify: bool,
|
||||||
) -> Frame {
|
) -> Frame {
|
||||||
let size = Size::new(width, line.size.y);
|
let mut remaining = width - line.width;
|
||||||
let mut remaining = width - line.size.x;
|
|
||||||
let mut offset = Length::zero();
|
let mut offset = Length::zero();
|
||||||
|
|
||||||
// Reorder the line from logical to visual order.
|
// Reorder the line from logical to visual order.
|
||||||
@ -903,8 +891,7 @@ fn commit(
|
|||||||
if let Some(glyph) = text.glyphs.first() {
|
if let Some(glyph) = text.glyphs.first() {
|
||||||
if text.styles.get(TextNode::OVERHANG) {
|
if text.styles.get(TextNode::OVERHANG) {
|
||||||
let start = text.dir.is_positive();
|
let start = text.dir.is_positive();
|
||||||
let em = text.styles.get(TextNode::SIZE);
|
let amount = overhang(glyph.c, start) * glyph.x_advance.at(text.size);
|
||||||
let amount = overhang(glyph.c, start) * glyph.x_advance.at(em);
|
|
||||||
offset -= amount;
|
offset -= amount;
|
||||||
remaining += amount;
|
remaining += amount;
|
||||||
}
|
}
|
||||||
@ -918,8 +905,7 @@ fn commit(
|
|||||||
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
&& (reordered.len() > 1 || text.glyphs.len() > 1)
|
||||||
{
|
{
|
||||||
let start = !text.dir.is_positive();
|
let start = !text.dir.is_positive();
|
||||||
let em = text.styles.get(TextNode::SIZE);
|
let amount = overhang(glyph.c, start) * glyph.x_advance.at(text.size);
|
||||||
let amount = overhang(glyph.c, start) * glyph.x_advance.at(em);
|
|
||||||
remaining += amount;
|
remaining += amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -940,24 +926,41 @@ fn commit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut output = Frame::new(size);
|
let mut top = Length::zero();
|
||||||
output.baseline = Some(line.baseline);
|
let mut bottom = Length::zero();
|
||||||
|
|
||||||
// Construct the line's frame from left to right.
|
// Build the frames and determine the height and baseline.
|
||||||
|
let mut frames = vec![];
|
||||||
for item in reordered {
|
for item in reordered {
|
||||||
let mut position = |frame: Frame| {
|
let frame = match item {
|
||||||
let x = offset + align.position(remaining);
|
ParItem::Absolute(v) => {
|
||||||
let y = line.baseline - frame.baseline();
|
offset += *v;
|
||||||
offset += frame.size.x;
|
continue;
|
||||||
output.merge_frame(Point::new(x, y), frame);
|
}
|
||||||
|
ParItem::Fractional(v) => {
|
||||||
|
offset += v.share(line.fr, remaining);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ParItem::Text(shaped) => shaped.build(fonts, justification),
|
||||||
|
ParItem::Frame(frame) => frame.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match item {
|
let width = frame.size.x;
|
||||||
ParItem::Absolute(v) => offset += *v,
|
top.set_max(frame.baseline());
|
||||||
ParItem::Fractional(v) => offset += v.share(line.fr, remaining),
|
bottom.set_max(frame.size.y - frame.baseline());
|
||||||
ParItem::Text(shaped) => position(shaped.build(fonts, justification)),
|
frames.push((offset, frame));
|
||||||
ParItem::Frame(frame) => position(frame.clone()),
|
offset += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let size = Size::new(width, top + bottom);
|
||||||
|
let mut output = Frame::new(size);
|
||||||
|
output.baseline = Some(top);
|
||||||
|
|
||||||
|
// Construct the line's frame.
|
||||||
|
for (offset, frame) in frames {
|
||||||
|
let x = offset + align.position(remaining);
|
||||||
|
let y = top - frame.baseline();
|
||||||
|
output.merge_frame(Point::new(x, y), frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
output
|
output
|
||||||
|
@ -20,10 +20,12 @@ pub struct ShapedText<'a> {
|
|||||||
pub dir: Dir,
|
pub dir: Dir,
|
||||||
/// The text's style properties.
|
/// The text's style properties.
|
||||||
pub styles: StyleChain<'a>,
|
pub styles: StyleChain<'a>,
|
||||||
/// The size of the text's bounding box.
|
/// The font variant.
|
||||||
pub size: Size,
|
pub variant: FontVariant,
|
||||||
/// The baseline from the top of the frame.
|
/// The font size.
|
||||||
pub baseline: Length,
|
pub size: Length,
|
||||||
|
/// The width of the text's bounding box.
|
||||||
|
pub width: Length,
|
||||||
/// The shaped glyphs.
|
/// The shaped glyphs.
|
||||||
pub glyphs: Cow<'a, [ShapedGlyph]>,
|
pub glyphs: Cow<'a, [ShapedGlyph]>,
|
||||||
}
|
}
|
||||||
@ -74,15 +76,17 @@ impl<'a> ShapedText<'a> {
|
|||||||
///
|
///
|
||||||
/// The `justification` defines how much extra advance width each
|
/// The `justification` defines how much extra advance width each
|
||||||
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
|
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
|
||||||
pub fn build(&self, fonts: &FontStore, justification: Length) -> Frame {
|
pub fn build(&self, fonts: &mut FontStore, justification: Length) -> Frame {
|
||||||
|
let (top, bottom) = self.measure(fonts);
|
||||||
|
let size = Size::new(self.width, top + bottom);
|
||||||
|
|
||||||
let mut offset = Length::zero();
|
let mut offset = Length::zero();
|
||||||
let mut frame = Frame::new(self.size);
|
let mut frame = Frame::new(size);
|
||||||
frame.baseline = Some(self.baseline);
|
frame.baseline = Some(top);
|
||||||
|
|
||||||
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
|
||||||
let pos = Point::new(offset, self.baseline);
|
let pos = Point::new(offset, top);
|
||||||
|
|
||||||
let size = self.styles.get(TextNode::SIZE);
|
|
||||||
let fill = self.styles.get(TextNode::FILL);
|
let fill = self.styles.get(TextNode::FILL);
|
||||||
let glyphs = group
|
let glyphs = group
|
||||||
.iter()
|
.iter()
|
||||||
@ -91,7 +95,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
x_advance: glyph.x_advance
|
x_advance: glyph.x_advance
|
||||||
+ if glyph.is_justifiable() {
|
+ if glyph.is_justifiable() {
|
||||||
frame.size.x += justification;
|
frame.size.x += justification;
|
||||||
Em::from_length(justification, size)
|
Em::from_length(justification, self.size)
|
||||||
} else {
|
} else {
|
||||||
Em::zero()
|
Em::zero()
|
||||||
},
|
},
|
||||||
@ -99,7 +103,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let text = Text { face_id, size, fill, glyphs };
|
let text = Text { face_id, size: self.size, fill, glyphs };
|
||||||
let text_layer = frame.layer();
|
let text_layer = frame.layer();
|
||||||
let width = text.width();
|
let width = text.width();
|
||||||
|
|
||||||
@ -120,6 +124,40 @@ impl<'a> ShapedText<'a> {
|
|||||||
frame
|
frame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Measure the top and bottom extent of a this text.
|
||||||
|
fn measure(&self, fonts: &mut FontStore) -> (Length, Length) {
|
||||||
|
let mut top = Length::zero();
|
||||||
|
let mut bottom = Length::zero();
|
||||||
|
|
||||||
|
let top_edge = self.styles.get(TextNode::TOP_EDGE);
|
||||||
|
let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE);
|
||||||
|
|
||||||
|
// Expand top and bottom by reading the face's vertical metrics.
|
||||||
|
let mut expand = |face: &Face| {
|
||||||
|
let metrics = face.metrics();
|
||||||
|
top.set_max(top_edge.resolve(self.styles, metrics));
|
||||||
|
bottom.set_max(-bottom_edge.resolve(self.styles, metrics));
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.glyphs.is_empty() {
|
||||||
|
// When there are no glyphs, we just use the vertical metrics of the
|
||||||
|
// first available font.
|
||||||
|
for family in families(self.styles) {
|
||||||
|
if let Some(face_id) = fonts.select(family, self.variant) {
|
||||||
|
expand(fonts.get(face_id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (face_id, _) in self.glyphs.group_by_key(|g| g.face_id) {
|
||||||
|
let face = fonts.get(face_id);
|
||||||
|
expand(face);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(top, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
/// How many justifiable glyphs the text contains.
|
/// How many justifiable glyphs the text contains.
|
||||||
pub fn justifiables(&self) -> usize {
|
pub fn justifiables(&self) -> usize {
|
||||||
self.glyphs.iter().filter(|g| g.is_justifiable()).count()
|
self.glyphs.iter().filter(|g| g.is_justifiable()).count()
|
||||||
@ -132,7 +170,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
.filter(|g| g.is_justifiable())
|
.filter(|g| g.is_justifiable())
|
||||||
.map(|g| g.x_advance)
|
.map(|g| g.x_advance)
|
||||||
.sum::<Em>()
|
.sum::<Em>()
|
||||||
.at(self.styles.get(TextNode::SIZE))
|
.at(self.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reshape a range of the shaped text, reusing information from this
|
/// Reshape a range of the shaped text, reusing information from this
|
||||||
@ -143,13 +181,13 @@ impl<'a> ShapedText<'a> {
|
|||||||
text_range: Range<usize>,
|
text_range: Range<usize>,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
||||||
let (size, baseline) = measure(fonts, &glyphs, self.styles);
|
|
||||||
Self {
|
Self {
|
||||||
text: Cow::Borrowed(&self.text[text_range]),
|
text: Cow::Borrowed(&self.text[text_range]),
|
||||||
dir: self.dir,
|
dir: self.dir,
|
||||||
styles: self.styles,
|
styles: self.styles,
|
||||||
size,
|
size: self.size,
|
||||||
baseline,
|
variant: self.variant,
|
||||||
|
width: glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size),
|
||||||
glyphs: Cow::Borrowed(glyphs),
|
glyphs: Cow::Borrowed(glyphs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -159,16 +197,14 @@ impl<'a> ShapedText<'a> {
|
|||||||
|
|
||||||
/// Push a hyphen to end of the text.
|
/// Push a hyphen to end of the text.
|
||||||
pub fn push_hyphen(&mut self, fonts: &mut FontStore) {
|
pub fn push_hyphen(&mut self, fonts: &mut FontStore) {
|
||||||
let size = self.styles.get(TextNode::SIZE);
|
|
||||||
let variant = variant(self.styles);
|
|
||||||
families(self.styles).find_map(|family| {
|
families(self.styles).find_map(|family| {
|
||||||
let face_id = fonts.select(family, variant)?;
|
let face_id = fonts.select(family, self.variant)?;
|
||||||
let face = fonts.get(face_id);
|
let face = fonts.get(face_id);
|
||||||
let ttf = face.ttf();
|
let ttf = face.ttf();
|
||||||
let glyph_id = ttf.glyph_index('-')?;
|
let glyph_id = ttf.glyph_index('-')?;
|
||||||
let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
||||||
let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default();
|
let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default();
|
||||||
self.size.x += x_advance.at(size);
|
self.width += x_advance.at(self.size);
|
||||||
self.glyphs.to_mut().push(ShapedGlyph {
|
self.glyphs.to_mut().push(ShapedGlyph {
|
||||||
face_id,
|
face_id,
|
||||||
glyph_id: glyph_id.0,
|
glyph_id: glyph_id.0,
|
||||||
@ -247,6 +283,7 @@ struct ShapingContext<'a> {
|
|||||||
glyphs: Vec<ShapedGlyph>,
|
glyphs: Vec<ShapedGlyph>,
|
||||||
used: Vec<FaceId>,
|
used: Vec<FaceId>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
|
size: Length,
|
||||||
variant: FontVariant,
|
variant: FontVariant,
|
||||||
tags: Vec<rustybuzz::Feature>,
|
tags: Vec<rustybuzz::Feature>,
|
||||||
fallback: bool,
|
fallback: bool,
|
||||||
@ -260,6 +297,7 @@ pub fn shape<'a>(
|
|||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
|
let size = styles.get(TextNode::SIZE);
|
||||||
let text = match styles.get(TextNode::CASE) {
|
let text = match styles.get(TextNode::CASE) {
|
||||||
Some(case) => Cow::Owned(case.apply(text)),
|
Some(case) => Cow::Owned(case.apply(text)),
|
||||||
None => Cow::Borrowed(text),
|
None => Cow::Borrowed(text),
|
||||||
@ -267,6 +305,7 @@ pub fn shape<'a>(
|
|||||||
|
|
||||||
let mut ctx = ShapingContext {
|
let mut ctx = ShapingContext {
|
||||||
fonts,
|
fonts,
|
||||||
|
size,
|
||||||
glyphs: vec![],
|
glyphs: vec![],
|
||||||
used: vec![],
|
used: vec![],
|
||||||
styles,
|
styles,
|
||||||
@ -282,14 +321,13 @@ pub fn shape<'a>(
|
|||||||
|
|
||||||
track_and_space(&mut ctx);
|
track_and_space(&mut ctx);
|
||||||
|
|
||||||
let (size, baseline) = measure(ctx.fonts, &ctx.glyphs, styles);
|
|
||||||
|
|
||||||
ShapedText {
|
ShapedText {
|
||||||
text,
|
text,
|
||||||
dir,
|
dir,
|
||||||
styles,
|
styles,
|
||||||
|
variant: ctx.variant,
|
||||||
size,
|
size,
|
||||||
baseline,
|
width: ctx.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(size),
|
||||||
glyphs: Cow::Owned(ctx.glyphs),
|
glyphs: Cow::Owned(ctx.glyphs),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -443,9 +481,11 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, face_id: FaceI
|
|||||||
|
|
||||||
/// Apply tracking and spacing to a slice of shaped glyphs.
|
/// Apply tracking and spacing to a slice of shaped glyphs.
|
||||||
fn track_and_space(ctx: &mut ShapingContext) {
|
fn track_and_space(ctx: &mut ShapingContext) {
|
||||||
let em = ctx.styles.get(TextNode::SIZE);
|
let tracking = Em::from_length(ctx.styles.get(TextNode::TRACKING), ctx.size);
|
||||||
let tracking = Em::from_length(ctx.styles.get(TextNode::TRACKING), em);
|
let spacing = ctx
|
||||||
let spacing = ctx.styles.get(TextNode::SPACING).map(|abs| Em::from_length(abs, em));
|
.styles
|
||||||
|
.get(TextNode::SPACING)
|
||||||
|
.map(|abs| Em::from_length(abs, ctx.size));
|
||||||
|
|
||||||
if tracking.is_zero() && spacing.is_one() {
|
if tracking.is_zero() && spacing.is_one() {
|
||||||
return;
|
return;
|
||||||
@ -463,52 +503,6 @@ fn track_and_space(ctx: &mut ShapingContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Measure the size and baseline of a run of shaped glyphs with the given
|
|
||||||
/// properties.
|
|
||||||
fn measure(
|
|
||||||
fonts: &mut FontStore,
|
|
||||||
glyphs: &[ShapedGlyph],
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> (Size, Length) {
|
|
||||||
let mut width = Length::zero();
|
|
||||||
let mut top = Length::zero();
|
|
||||||
let mut bottom = Length::zero();
|
|
||||||
|
|
||||||
let size = styles.get(TextNode::SIZE);
|
|
||||||
let top_edge = styles.get(TextNode::TOP_EDGE);
|
|
||||||
let bottom_edge = styles.get(TextNode::BOTTOM_EDGE);
|
|
||||||
|
|
||||||
// Expand top and bottom by reading the face's vertical metrics.
|
|
||||||
let mut expand = |face: &Face| {
|
|
||||||
let metrics = face.metrics();
|
|
||||||
top.set_max(top_edge.resolve(styles, metrics));
|
|
||||||
bottom.set_max(-bottom_edge.resolve(styles, metrics));
|
|
||||||
};
|
|
||||||
|
|
||||||
if glyphs.is_empty() {
|
|
||||||
// When there are no glyphs, we just use the vertical metrics of the
|
|
||||||
// first available font.
|
|
||||||
let variant = variant(styles);
|
|
||||||
for family in families(styles) {
|
|
||||||
if let Some(face_id) = fonts.select(family, variant) {
|
|
||||||
expand(fonts.get(face_id));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (face_id, group) in glyphs.group_by_key(|g| g.face_id) {
|
|
||||||
let face = fonts.get(face_id);
|
|
||||||
expand(face);
|
|
||||||
|
|
||||||
for glyph in group {
|
|
||||||
width += glyph.x_advance.at(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Size::new(width, top + bottom), top)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve the font variant with `STRONG` and `EMPH` factored in.
|
/// Resolve the font variant with `STRONG` and `EMPH` factored in.
|
||||||
fn variant(styles: StyleChain) -> FontVariant {
|
fn variant(styles: StyleChain) -> FontVariant {
|
||||||
let mut variant = FontVariant::new(
|
let mut variant = FontVariant::new(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user