mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Renaming
`Face` -> `Font` `FaceId` -> `FontId` `SourceFile` -> `Source`
This commit is contained in:
parent
4ec3bcee48
commit
30be75c668
@ -9,20 +9,20 @@ use crate::util::SliceExt;
|
|||||||
|
|
||||||
/// Embed all used fonts into the PDF.
|
/// Embed all used fonts into the PDF.
|
||||||
pub fn write_fonts(ctx: &mut PdfContext) {
|
pub fn write_fonts(ctx: &mut PdfContext) {
|
||||||
for face_id in ctx.face_map.layout_indices() {
|
for font_id in ctx.font_map.layout_indices() {
|
||||||
let type0_ref = ctx.alloc.bump();
|
let type0_ref = ctx.alloc.bump();
|
||||||
let cid_ref = ctx.alloc.bump();
|
let cid_ref = ctx.alloc.bump();
|
||||||
let descriptor_ref = ctx.alloc.bump();
|
let descriptor_ref = ctx.alloc.bump();
|
||||||
let cmap_ref = ctx.alloc.bump();
|
let cmap_ref = ctx.alloc.bump();
|
||||||
let data_ref = ctx.alloc.bump();
|
let data_ref = ctx.alloc.bump();
|
||||||
ctx.face_refs.push(type0_ref);
|
ctx.font_refs.push(type0_ref);
|
||||||
|
|
||||||
let glyphs = &ctx.glyph_sets[&face_id];
|
let glyphs = &ctx.glyph_sets[&font_id];
|
||||||
let face = ctx.fonts.get(face_id);
|
let font = ctx.fonts.get(font_id);
|
||||||
let metrics = face.metrics();
|
let metrics = font.metrics();
|
||||||
let ttf = face.ttf();
|
let ttf = font.ttf();
|
||||||
|
|
||||||
let postscript_name = face
|
let postscript_name = font
|
||||||
.find_name(name_id::POST_SCRIPT_NAME)
|
.find_name(name_id::POST_SCRIPT_NAME)
|
||||||
.unwrap_or_else(|| "unknown".to_string());
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
let mut widths = vec![0.0; num_glyphs as usize];
|
let mut widths = vec![0.0; num_glyphs as usize];
|
||||||
for &g in glyphs {
|
for &g in glyphs {
|
||||||
let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0);
|
let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0);
|
||||||
widths[g as usize] = face.to_em(x).to_font_units();
|
widths[g as usize] = font.to_em(x).to_font_units();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write all non-zero glyph widths.
|
// Write all non-zero glyph widths.
|
||||||
@ -97,10 +97,10 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
|
|
||||||
let global_bbox = ttf.global_bounding_box();
|
let global_bbox = ttf.global_bounding_box();
|
||||||
let bbox = Rect::new(
|
let bbox = Rect::new(
|
||||||
face.to_em(global_bbox.x_min).to_font_units(),
|
font.to_em(global_bbox.x_min).to_font_units(),
|
||||||
face.to_em(global_bbox.y_min).to_font_units(),
|
font.to_em(global_bbox.y_min).to_font_units(),
|
||||||
face.to_em(global_bbox.x_max).to_font_units(),
|
font.to_em(global_bbox.x_max).to_font_units(),
|
||||||
face.to_em(global_bbox.y_max).to_font_units(),
|
font.to_em(global_bbox.y_max).to_font_units(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let italic_angle = ttf.italic_angle().unwrap_or(0.0);
|
let italic_angle = ttf.italic_angle().unwrap_or(0.0);
|
||||||
@ -160,15 +160,15 @@ pub fn write_fonts(ctx: &mut PdfContext) {
|
|||||||
.cmap(cmap_ref, &deflate(&cmap.finish()))
|
.cmap(cmap_ref, &deflate(&cmap.finish()))
|
||||||
.filter(Filter::FlateDecode);
|
.filter(Filter::FlateDecode);
|
||||||
|
|
||||||
// Subset and write the face's bytes.
|
// Subset and write the font's bytes.
|
||||||
let data = face.buffer();
|
let data = font.buffer();
|
||||||
let subsetted = {
|
let subsetted = {
|
||||||
let glyphs: Vec<_> = glyphs.iter().copied().collect();
|
let glyphs: Vec<_> = glyphs.iter().copied().collect();
|
||||||
let profile = subsetter::Profile::pdf(&glyphs);
|
let profile = subsetter::Profile::pdf(&glyphs);
|
||||||
subsetter::subset(data, face.index(), profile)
|
subsetter::subset(data, font.index(), profile)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compress and write the face's byte.
|
// Compress and write the font's byte.
|
||||||
let data = subsetted.as_deref().unwrap_or(data);
|
let data = subsetted.as_deref().unwrap_or(data);
|
||||||
let data = deflate(data);
|
let data = deflate(data);
|
||||||
let mut stream = ctx.writer.stream(data_ref, &data);
|
let mut stream = ctx.writer.stream(data_ref, &data);
|
||||||
|
@ -14,7 +14,7 @@ use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
|
|||||||
|
|
||||||
use self::outline::{Heading, HeadingNode};
|
use self::outline::{Heading, HeadingNode};
|
||||||
use self::page::Page;
|
use self::page::Page;
|
||||||
use crate::font::{FaceId, FontStore};
|
use crate::font::{FontId, FontStore};
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::geom::{Dir, Em, Length};
|
use crate::geom::{Dir, Em, Length};
|
||||||
use crate::image::{ImageId, ImageStore};
|
use crate::image::{ImageId, ImageStore};
|
||||||
@ -51,12 +51,12 @@ pub struct PdfContext<'a> {
|
|||||||
page_heights: Vec<f32>,
|
page_heights: Vec<f32>,
|
||||||
alloc: Ref,
|
alloc: Ref,
|
||||||
page_tree_ref: Ref,
|
page_tree_ref: Ref,
|
||||||
face_refs: Vec<Ref>,
|
font_refs: Vec<Ref>,
|
||||||
image_refs: Vec<Ref>,
|
image_refs: Vec<Ref>,
|
||||||
page_refs: Vec<Ref>,
|
page_refs: Vec<Ref>,
|
||||||
face_map: Remapper<FaceId>,
|
font_map: Remapper<FontId>,
|
||||||
image_map: Remapper<ImageId>,
|
image_map: Remapper<ImageId>,
|
||||||
glyph_sets: HashMap<FaceId, HashSet<u16>>,
|
glyph_sets: HashMap<FontId, HashSet<u16>>,
|
||||||
languages: HashMap<Lang, usize>,
|
languages: HashMap<Lang, usize>,
|
||||||
heading_tree: Vec<HeadingNode>,
|
heading_tree: Vec<HeadingNode>,
|
||||||
}
|
}
|
||||||
@ -74,9 +74,9 @@ impl<'a> PdfContext<'a> {
|
|||||||
alloc,
|
alloc,
|
||||||
page_tree_ref,
|
page_tree_ref,
|
||||||
page_refs: vec![],
|
page_refs: vec![],
|
||||||
face_refs: vec![],
|
font_refs: vec![],
|
||||||
image_refs: vec![],
|
image_refs: vec![],
|
||||||
face_map: Remapper::new(),
|
font_map: Remapper::new(),
|
||||||
image_map: Remapper::new(),
|
image_map: Remapper::new(),
|
||||||
glyph_sets: HashMap::new(),
|
glyph_sets: HashMap::new(),
|
||||||
languages: HashMap::new(),
|
languages: HashMap::new(),
|
||||||
|
@ -5,7 +5,7 @@ use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
|||||||
use super::{
|
use super::{
|
||||||
deflate, EmExt, Heading, HeadingNode, LengthExt, PdfContext, RefExt, D65_GRAY, SRGB,
|
deflate, EmExt, Heading, HeadingNode, LengthExt, PdfContext, RefExt, D65_GRAY, SRGB,
|
||||||
};
|
};
|
||||||
use crate::font::FaceId;
|
use crate::font::FontId;
|
||||||
use crate::frame::{Destination, Element, Frame, Group, Role, Text};
|
use crate::frame::{Destination, Element, Frame, Group, Role, Text};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
||||||
@ -80,7 +80,7 @@ pub fn write_page_tree(ctx: &mut PdfContext) {
|
|||||||
spaces.finish();
|
spaces.finish();
|
||||||
|
|
||||||
let mut fonts = resources.fonts();
|
let mut fonts = resources.fonts();
|
||||||
for (font_ref, f) in ctx.face_map.pdf_indices(&ctx.face_refs) {
|
for (font_ref, f) in ctx.font_map.pdf_indices(&ctx.font_refs) {
|
||||||
let name = format_eco!("F{}", f);
|
let name = format_eco!("F{}", f);
|
||||||
fonts.pair(Name(name.as_bytes()), font_ref);
|
fonts.pair(Name(name.as_bytes()), font_ref);
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ struct PageContext<'a, 'b> {
|
|||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
struct State {
|
struct State {
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
font: Option<(FaceId, Length)>,
|
font: Option<(FontId, Length)>,
|
||||||
fill: Option<Paint>,
|
fill: Option<Paint>,
|
||||||
fill_space: Option<Name<'static>>,
|
fill_space: Option<Name<'static>>,
|
||||||
stroke: Option<Stroke>,
|
stroke: Option<Stroke>,
|
||||||
@ -200,12 +200,12 @@ impl<'a, 'b> PageContext<'a, 'b> {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_font(&mut self, face_id: FaceId, size: Length) {
|
fn set_font(&mut self, font_id: FontId, size: Length) {
|
||||||
if self.state.font != Some((face_id, size)) {
|
if self.state.font != Some((font_id, size)) {
|
||||||
self.parent.face_map.insert(face_id);
|
self.parent.font_map.insert(font_id);
|
||||||
let name = format_eco!("F{}", self.parent.face_map.map(face_id));
|
let name = format_eco!("F{}", self.parent.font_map.map(font_id));
|
||||||
self.content.set_font(Name(name.as_bytes()), size.to_f32());
|
self.content.set_font(Name(name.as_bytes()), size.to_f32());
|
||||||
self.state.font = Some((face_id, size));
|
self.state.font = Some((font_id, size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,14 +329,14 @@ fn write_text(ctx: &mut PageContext, x: f32, y: f32, text: &Text) {
|
|||||||
*ctx.parent.languages.entry(text.lang).or_insert(0) += text.glyphs.len();
|
*ctx.parent.languages.entry(text.lang).or_insert(0) += text.glyphs.len();
|
||||||
ctx.parent
|
ctx.parent
|
||||||
.glyph_sets
|
.glyph_sets
|
||||||
.entry(text.face_id)
|
.entry(text.font_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
.extend(text.glyphs.iter().map(|g| g.id));
|
.extend(text.glyphs.iter().map(|g| g.id));
|
||||||
|
|
||||||
let face = ctx.parent.fonts.get(text.face_id);
|
let font = ctx.parent.fonts.get(text.font_id);
|
||||||
|
|
||||||
ctx.set_fill(text.fill);
|
ctx.set_fill(text.fill);
|
||||||
ctx.set_font(text.face_id, text.size);
|
ctx.set_font(text.font_id, text.size);
|
||||||
ctx.content.begin_text();
|
ctx.content.begin_text();
|
||||||
|
|
||||||
// Position the text.
|
// Position the text.
|
||||||
@ -364,7 +364,7 @@ fn write_text(ctx: &mut PageContext, x: f32, y: f32, text: &Text) {
|
|||||||
encoded.push((glyph.id >> 8) as u8);
|
encoded.push((glyph.id >> 8) as u8);
|
||||||
encoded.push((glyph.id & 0xff) as u8);
|
encoded.push((glyph.id & 0xff) as u8);
|
||||||
|
|
||||||
if let Some(advance) = face.advance(glyph.id) {
|
if let Some(advance) = font.advance(glyph.id) {
|
||||||
adjustment += glyph.x_advance - advance;
|
adjustment += glyph.x_advance - advance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,8 +142,8 @@ fn render_svg_glyph(
|
|||||||
text: &Text,
|
text: &Text,
|
||||||
id: GlyphId,
|
id: GlyphId,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let face = ctx.fonts.get(text.face_id);
|
let font = ctx.fonts.get(text.font_id);
|
||||||
let mut data = face.ttf().glyph_svg_image(id)?;
|
let mut data = font.ttf().glyph_svg_image(id)?;
|
||||||
|
|
||||||
// Decompress SVGZ.
|
// Decompress SVGZ.
|
||||||
let mut decoded = vec![];
|
let mut decoded = vec![];
|
||||||
@ -165,7 +165,7 @@ fn render_svg_glyph(
|
|||||||
|
|
||||||
// If there's no viewbox defined, use the em square for our scale
|
// If there's no viewbox defined, use the em square for our scale
|
||||||
// transformation ...
|
// transformation ...
|
||||||
let upem = face.units_per_em() as f32;
|
let upem = font.units_per_em() as f32;
|
||||||
let (mut width, mut height) = (upem, upem);
|
let (mut width, mut height) = (upem, upem);
|
||||||
|
|
||||||
// ... but if there's a viewbox or width, use that.
|
// ... but if there's a viewbox or width, use that.
|
||||||
@ -195,8 +195,8 @@ fn render_bitmap_glyph(
|
|||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let size = text.size.to_f32();
|
let size = text.size.to_f32();
|
||||||
let ppem = size * ts.sy;
|
let ppem = size * ts.sy;
|
||||||
let face = ctx.fonts.get(text.face_id);
|
let font = ctx.fonts.get(text.font_id);
|
||||||
let raster = face.ttf().glyph_raster_image(id, ppem as u16)?;
|
let raster = font.ttf().glyph_raster_image(id, ppem as u16)?;
|
||||||
let img = RasterImage::parse(&raster.data).ok()?;
|
let img = RasterImage::parse(&raster.data).ok()?;
|
||||||
|
|
||||||
// FIXME: Vertical alignment isn't quite right for Apple Color Emoji,
|
// FIXME: Vertical alignment isn't quite right for Apple Color Emoji,
|
||||||
@ -225,10 +225,10 @@ fn render_outline_glyph(
|
|||||||
// rasterization can't be used due to very large text size or weird
|
// rasterization can't be used due to very large text size or weird
|
||||||
// scale/skewing transforms.
|
// scale/skewing transforms.
|
||||||
if ppem > 100.0 || ts.kx != 0.0 || ts.ky != 0.0 || ts.sx != ts.sy {
|
if ppem > 100.0 || ts.kx != 0.0 || ts.ky != 0.0 || ts.sx != ts.sy {
|
||||||
let face = ctx.fonts.get(text.face_id);
|
let font = ctx.fonts.get(text.font_id);
|
||||||
let path = {
|
let path = {
|
||||||
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
|
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
|
||||||
face.ttf().outline_glyph(id, &mut builder)?;
|
font.ttf().outline_glyph(id, &mut builder)?;
|
||||||
builder.0.finish()?
|
builder.0.finish()?
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -237,7 +237,7 @@ fn render_outline_glyph(
|
|||||||
|
|
||||||
// Flip vertically because font design coordinate
|
// Flip vertically because font design coordinate
|
||||||
// system is Y-up.
|
// system is Y-up.
|
||||||
let scale = text.size.to_f32() / face.units_per_em() as f32;
|
let scale = text.size.to_f32() / font.units_per_em() as f32;
|
||||||
let ts = ts.pre_scale(scale, -scale);
|
let ts = ts.pre_scale(scale, -scale);
|
||||||
canvas.fill_path(&path, &paint, rule, ts, mask)?;
|
canvas.fill_path(&path, &paint, rule, ts, mask)?;
|
||||||
return Some(());
|
return Some(());
|
||||||
@ -246,7 +246,7 @@ fn render_outline_glyph(
|
|||||||
// Rasterize the glyph with `pixglyph`.
|
// Rasterize the glyph with `pixglyph`.
|
||||||
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
||||||
// doesn't exist, yet.
|
// doesn't exist, yet.
|
||||||
let glyph = pixglyph::Glyph::load(ctx.fonts.get(text.face_id).ttf(), id)?;
|
let glyph = pixglyph::Glyph::load(ctx.fonts.get(text.font_id).ttf(), id)?;
|
||||||
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
|
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
|
||||||
|
|
||||||
let cw = canvas.width() as i32;
|
let cw = canvas.width() as i32;
|
||||||
|
176
src/font.rs
176
src/font.rs
@ -15,12 +15,12 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
use crate::geom::Em;
|
use crate::geom::Em;
|
||||||
use crate::loading::{FileHash, Loader};
|
use crate::loading::{FileHash, Loader};
|
||||||
|
|
||||||
/// A unique identifier for a loaded font face.
|
/// A unique identifier for a loaded font.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct FaceId(u32);
|
pub struct FontId(u32);
|
||||||
|
|
||||||
impl FaceId {
|
impl FontId {
|
||||||
/// Create a face id from the raw underlying value.
|
/// Create a font id from the raw underlying value.
|
||||||
///
|
///
|
||||||
/// This should only be called with values returned by
|
/// This should only be called with values returned by
|
||||||
/// [`into_raw`](Self::into_raw).
|
/// [`into_raw`](Self::into_raw).
|
||||||
@ -34,38 +34,38 @@ impl FaceId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Storage for loaded and parsed font faces.
|
/// Storage for loaded and parsed fonts.
|
||||||
pub struct FontStore {
|
pub struct FontStore {
|
||||||
loader: Arc<dyn Loader>,
|
loader: Arc<dyn Loader>,
|
||||||
failed: Vec<bool>,
|
failed: Vec<bool>,
|
||||||
faces: Vec<Option<Face>>,
|
fonts: Vec<Option<Font>>,
|
||||||
families: BTreeMap<String, Vec<FaceId>>,
|
families: BTreeMap<String, Vec<FontId>>,
|
||||||
buffers: HashMap<FileHash, Arc<Vec<u8>>>,
|
buffers: HashMap<FileHash, Arc<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontStore {
|
impl FontStore {
|
||||||
/// Create a new, empty font store.
|
/// Create a new, empty font store.
|
||||||
pub fn new(loader: Arc<dyn Loader>) -> Self {
|
pub fn new(loader: Arc<dyn Loader>) -> Self {
|
||||||
let mut faces = vec![];
|
let mut fonts = vec![];
|
||||||
let mut failed = vec![];
|
let mut failed = vec![];
|
||||||
let mut families = BTreeMap::<String, Vec<FaceId>>::new();
|
let mut families = BTreeMap::<String, Vec<FontId>>::new();
|
||||||
|
|
||||||
let infos = loader.faces();
|
let infos = loader.fonts();
|
||||||
for (i, info) in infos.iter().enumerate() {
|
for (i, info) in infos.iter().enumerate() {
|
||||||
let id = FaceId(i as u32);
|
let id = FontId(i as u32);
|
||||||
faces.push(None);
|
fonts.push(None);
|
||||||
failed.push(false);
|
failed.push(false);
|
||||||
families.entry(info.family.to_lowercase()).or_default().push(id);
|
families.entry(info.family.to_lowercase()).or_default().push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for faces in families.values_mut() {
|
for fonts in families.values_mut() {
|
||||||
faces.sort_by_key(|id| infos[id.0 as usize].variant);
|
fonts.sort_by_key(|id| infos[id.0 as usize].variant);
|
||||||
faces.dedup_by_key(|id| infos[id.0 as usize].variant);
|
fonts.dedup_by_key(|id| infos[id.0 as usize].variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
loader,
|
loader,
|
||||||
faces,
|
fonts,
|
||||||
failed,
|
failed,
|
||||||
families,
|
families,
|
||||||
buffers: HashMap::new(),
|
buffers: HashMap::new(),
|
||||||
@ -73,73 +73,73 @@ impl FontStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An ordered iterator over all font families this loader knows and details
|
/// An ordered iterator over all font families this loader knows and details
|
||||||
/// about the faces that are part of them.
|
/// about the fonts that are part of them.
|
||||||
pub fn families(
|
pub fn families(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = (&str, impl Iterator<Item = &FaceInfo>)> + '_ {
|
) -> impl Iterator<Item = (&str, impl Iterator<Item = &FontInfo>)> + '_ {
|
||||||
// Since the keys are lowercased, we instead use the family field of the
|
// Since the keys are lowercased, we instead use the family field of the
|
||||||
// first face's info.
|
// first font's info.
|
||||||
let faces = self.loader.faces();
|
let fonts = self.loader.fonts();
|
||||||
self.families.values().map(|ids| {
|
self.families.values().map(|ids| {
|
||||||
let family = faces[ids[0].0 as usize].family.as_str();
|
let family = fonts[ids[0].0 as usize].family.as_str();
|
||||||
let infos = ids.iter().map(|&id| &faces[id.0 as usize]);
|
let infos = ids.iter().map(|&id| &fonts[id.0 as usize]);
|
||||||
(family, infos)
|
(family, infos)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a loaded face.
|
/// Get a reference to a loaded font.
|
||||||
///
|
///
|
||||||
/// This panics if the face with this `id` was not loaded. This function
|
/// This panics if the font with this `id` was not loaded. This function
|
||||||
/// should only be called with ids returned by this store's
|
/// should only be called with ids returned by this store's
|
||||||
/// [`select()`](Self::select) and
|
/// [`select()`](Self::select) and
|
||||||
/// [`select_fallback()`](Self::select_fallback) methods.
|
/// [`select_fallback()`](Self::select_fallback) methods.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn get(&self, id: FaceId) -> &Face {
|
pub fn get(&self, id: FontId) -> &Font {
|
||||||
self.faces[id.0 as usize].as_ref().expect("font face was not loaded")
|
self.fonts[id.0 as usize].as_ref().expect("font was not loaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to find and load a font face from the given `family` that matches
|
/// Try to find and load a font from the given `family` that matches
|
||||||
/// the given `variant` as closely as possible.
|
/// the given `variant` as closely as possible.
|
||||||
pub fn select(&mut self, family: &str, variant: FontVariant) -> Option<FaceId> {
|
pub fn select(&mut self, family: &str, variant: FontVariant) -> Option<FontId> {
|
||||||
let ids = self.families.get(family)?;
|
let ids = self.families.get(family)?;
|
||||||
let id = self.find_best_variant(None, variant, ids.iter().copied())?;
|
let id = self.find_best_variant(None, variant, ids.iter().copied())?;
|
||||||
self.load(id)
|
self.load(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to find and load a fallback font that
|
/// Try to find and load a fallback font that
|
||||||
/// - is as close as possible to the face `like` (if any)
|
/// - is as close as possible to the font `like` (if any)
|
||||||
/// - is as close as possible to the given `variant`
|
/// - is as close as possible to the given `variant`
|
||||||
/// - is suitable for shaping the given `text`
|
/// - is suitable for shaping the given `text`
|
||||||
pub fn select_fallback(
|
pub fn select_fallback(
|
||||||
&mut self,
|
&mut self,
|
||||||
like: Option<FaceId>,
|
like: Option<FontId>,
|
||||||
variant: FontVariant,
|
variant: FontVariant,
|
||||||
text: &str,
|
text: &str,
|
||||||
) -> Option<FaceId> {
|
) -> Option<FontId> {
|
||||||
// Find the faces that contain the text's first char ...
|
// Find the fonts that contain the text's first char ...
|
||||||
let c = text.chars().next()?;
|
let c = text.chars().next()?;
|
||||||
let ids = self
|
let ids = self
|
||||||
.loader
|
.loader
|
||||||
.faces()
|
.fonts()
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, info)| info.coverage.contains(c as u32))
|
.filter(|(_, info)| info.coverage.contains(c as u32))
|
||||||
.map(|(i, _)| FaceId(i as u32));
|
.map(|(i, _)| FontId(i as u32));
|
||||||
|
|
||||||
// ... and find the best variant among them.
|
// ... and find the best variant among them.
|
||||||
let id = self.find_best_variant(like, variant, ids)?;
|
let id = self.find_best_variant(like, variant, ids)?;
|
||||||
self.load(id)
|
self.load(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the face in the passed iterator that
|
/// Find the font in the passed iterator that
|
||||||
/// - is closest to the face `like` (if any)
|
/// - is closest to the font `like` (if any)
|
||||||
/// - is closest to the given `variant`
|
/// - is closest to the given `variant`
|
||||||
///
|
///
|
||||||
/// To do that we compute a key for all variants and select the one with the
|
/// To do that we compute a key for all variants and select the one with the
|
||||||
/// minimal key. This key prioritizes:
|
/// minimal key. This key prioritizes:
|
||||||
/// - If `like` is some other face:
|
/// - If `like` is some other font:
|
||||||
/// - Are both faces (not) monospaced?
|
/// - Are both fonts (not) monospaced?
|
||||||
/// - Do both faces (not) have serifs?
|
/// - Do both fonts (not) have serifs?
|
||||||
/// - How many words do the families share in their prefix? E.g. "Noto
|
/// - How many words do the families share in their prefix? E.g. "Noto
|
||||||
/// Sans" and "Noto Sans Arabic" share two words, whereas "IBM Plex
|
/// Sans" and "Noto Sans Arabic" share two words, whereas "IBM Plex
|
||||||
/// Arabic" shares none with "Noto Sans", so prefer "Noto Sans Arabic"
|
/// Arabic" shares none with "Noto Sans", so prefer "Noto Sans Arabic"
|
||||||
@ -154,11 +154,11 @@ impl FontStore {
|
|||||||
/// - The absolute distance to the target weight.
|
/// - The absolute distance to the target weight.
|
||||||
fn find_best_variant(
|
fn find_best_variant(
|
||||||
&self,
|
&self,
|
||||||
like: Option<FaceId>,
|
like: Option<FontId>,
|
||||||
variant: FontVariant,
|
variant: FontVariant,
|
||||||
ids: impl IntoIterator<Item = FaceId>,
|
ids: impl IntoIterator<Item = FontId>,
|
||||||
) -> Option<FaceId> {
|
) -> Option<FontId> {
|
||||||
let infos = self.loader.faces();
|
let infos = self.loader.fonts();
|
||||||
let like = like.map(|id| &infos[id.0 as usize]);
|
let like = like.map(|id| &infos[id.0 as usize]);
|
||||||
|
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
@ -190,12 +190,12 @@ impl FontStore {
|
|||||||
best
|
best
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the face with the given id.
|
/// Load the font with the given id.
|
||||||
///
|
///
|
||||||
/// Returns `Some(id)` if the face was loaded successfully.
|
/// Returns `Some(id)` if the font was loaded successfully.
|
||||||
fn load(&mut self, id: FaceId) -> Option<FaceId> {
|
fn load(&mut self, id: FontId) -> Option<FontId> {
|
||||||
let idx = id.0 as usize;
|
let idx = id.0 as usize;
|
||||||
let slot = &mut self.faces[idx];
|
let slot = &mut self.fonts[idx];
|
||||||
if slot.is_some() {
|
if slot.is_some() {
|
||||||
return Some(id);
|
return Some(id);
|
||||||
}
|
}
|
||||||
@ -204,11 +204,11 @@ impl FontStore {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let FaceInfo { ref path, index, .. } = self.loader.faces()[idx];
|
let FontInfo { ref path, index, .. } = self.loader.fonts()[idx];
|
||||||
self.failed[idx] = true;
|
self.failed[idx] = true;
|
||||||
|
|
||||||
// Check the buffer cache since multiple faces may
|
// Check the buffer cache since multiple fonts may refer to the same
|
||||||
// refer to the same data (font collection).
|
// data (font collection).
|
||||||
let hash = self.loader.resolve(path).ok()?;
|
let hash = self.loader.resolve(path).ok()?;
|
||||||
let buffer = match self.buffers.entry(hash) {
|
let buffer = match self.buffers.entry(hash) {
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
@ -218,8 +218,8 @@ impl FontStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let face = Face::new(Arc::clone(buffer), index)?;
|
let font = Font::new(Arc::clone(buffer), index)?;
|
||||||
*slot = Some(face);
|
*slot = Some(font);
|
||||||
self.failed[idx] = false;
|
self.failed[idx] = false;
|
||||||
|
|
||||||
Some(id)
|
Some(id)
|
||||||
@ -234,24 +234,24 @@ fn shared_prefix_words(left: &str, right: &str) -> usize {
|
|||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A font face.
|
/// An OpenType font.
|
||||||
pub struct Face {
|
pub struct Font {
|
||||||
/// The raw face data, possibly shared with other faces from the same
|
/// The raw font data, possibly shared with other fonts from the same
|
||||||
/// collection. The vector's allocation must not move, because `ttf` points
|
/// collection. The vector's allocation must not move, because `ttf` points
|
||||||
/// into it using unsafe code.
|
/// into it using unsafe code.
|
||||||
buffer: Arc<Vec<u8>>,
|
buffer: Arc<Vec<u8>>,
|
||||||
/// The face's index in the collection (zero if not a collection).
|
/// The font's index in the collection (zero if not a collection).
|
||||||
index: u32,
|
index: u32,
|
||||||
/// The underlying ttf-parser/rustybuzz face.
|
/// The underlying ttf-parser/rustybuzz face.
|
||||||
ttf: rustybuzz::Face<'static>,
|
ttf: rustybuzz::Face<'static>,
|
||||||
/// The face's metrics.
|
/// The font's metrics.
|
||||||
metrics: FaceMetrics,
|
metrics: FontMetrics,
|
||||||
/// The parsed ReX math header.
|
/// The parsed ReX math header.
|
||||||
math: OnceCell<Option<MathHeader>>,
|
math: OnceCell<Option<MathHeader>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Face {
|
impl Font {
|
||||||
/// Parse a font face from a buffer and collection index.
|
/// Parse a font from a buffer and collection index.
|
||||||
pub fn new(buffer: Arc<Vec<u8>>, index: u32) -> Option<Self> {
|
pub fn new(buffer: Arc<Vec<u8>>, index: u32) -> Option<Self> {
|
||||||
// Safety:
|
// Safety:
|
||||||
// - The slices's location is stable in memory:
|
// - The slices's location is stable in memory:
|
||||||
@ -263,7 +263,7 @@ impl Face {
|
|||||||
unsafe { std::slice::from_raw_parts(buffer.as_ptr(), buffer.len()) };
|
unsafe { std::slice::from_raw_parts(buffer.as_ptr(), buffer.len()) };
|
||||||
|
|
||||||
let ttf = rustybuzz::Face::from_slice(slice, index)?;
|
let ttf = rustybuzz::Face::from_slice(slice, index)?;
|
||||||
let metrics = FaceMetrics::from_ttf(&ttf);
|
let metrics = FontMetrics::from_ttf(&ttf);
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
buffer,
|
buffer,
|
||||||
@ -296,8 +296,8 @@ impl Face {
|
|||||||
self.metrics.units_per_em
|
self.metrics.units_per_em
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the face's metrics.
|
/// Access the font's metrics.
|
||||||
pub fn metrics(&self) -> &FaceMetrics {
|
pub fn metrics(&self) -> &FontMetrics {
|
||||||
&self.metrics
|
&self.metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,9 +329,9 @@ impl Face {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metrics for a font face.
|
/// Metrics for a font.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct FaceMetrics {
|
pub struct FontMetrics {
|
||||||
/// How many font units represent one em unit.
|
/// How many font units represent one em unit.
|
||||||
pub units_per_em: f64,
|
pub units_per_em: f64,
|
||||||
/// The distance from the baseline to the typographic ascender.
|
/// The distance from the baseline to the typographic ascender.
|
||||||
@ -350,8 +350,8 @@ pub struct FaceMetrics {
|
|||||||
pub overline: LineMetrics,
|
pub overline: LineMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FaceMetrics {
|
impl FontMetrics {
|
||||||
/// Extract the face's metrics.
|
/// Extract the font's metrics.
|
||||||
pub fn from_ttf(ttf: &ttf_parser::Face) -> Self {
|
pub fn from_ttf(ttf: &ttf_parser::Face) -> Self {
|
||||||
let units_per_em = f64::from(ttf.units_per_em());
|
let units_per_em = f64::from(ttf.units_per_em());
|
||||||
let to_em = |units| Em::from_units(units, units_per_em);
|
let to_em = |units| Em::from_units(units, units_per_em);
|
||||||
@ -437,36 +437,36 @@ pub enum VerticalFontMetric {
|
|||||||
Descender,
|
Descender,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Properties of a single font face.
|
/// Properties of a single font.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FaceInfo {
|
pub struct FontInfo {
|
||||||
/// The path to the font file.
|
/// The path to the font file.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
/// The collection index in the font file.
|
/// The collection index in the font file.
|
||||||
pub index: u32,
|
pub index: u32,
|
||||||
/// The typographic font family this face is part of.
|
/// The typographic font family this font is part of.
|
||||||
pub family: String,
|
pub family: String,
|
||||||
/// Properties that distinguish this face from other faces in the same
|
/// Properties that distinguish this font from other fonts in the same
|
||||||
/// family.
|
/// family.
|
||||||
pub variant: FontVariant,
|
pub variant: FontVariant,
|
||||||
/// Whether the face is monospaced.
|
/// Whether the font is monospaced.
|
||||||
pub monospaced: bool,
|
pub monospaced: bool,
|
||||||
/// Whether the face has serifs (if known).
|
/// Whether the font has serifs (if known).
|
||||||
pub serif: Option<bool>,
|
pub serif: Option<bool>,
|
||||||
/// The unicode coverage of the face.
|
/// The unicode coverage of the font.
|
||||||
pub coverage: Coverage,
|
pub coverage: Coverage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FaceInfo {
|
impl FontInfo {
|
||||||
/// Compute metadata for all faces in the given data.
|
/// Compute metadata for all fonts in the given data.
|
||||||
pub fn from_data<'a>(
|
pub fn from_data<'a>(
|
||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
) -> impl Iterator<Item = FaceInfo> + 'a {
|
) -> impl Iterator<Item = FontInfo> + 'a {
|
||||||
let count = ttf_parser::fonts_in_collection(data).unwrap_or(1);
|
let count = ttf_parser::fonts_in_collection(data).unwrap_or(1);
|
||||||
(0 .. count).filter_map(move |index| {
|
(0 .. count).filter_map(move |index| {
|
||||||
let face = ttf_parser::Face::from_slice(data, index).ok()?;
|
let ttf = ttf_parser::Face::from_slice(data, index).ok()?;
|
||||||
Self::from_ttf(path, index, &face)
|
Self::from_ttf(path, index, &ttf)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,7 +477,7 @@ impl FaceInfo {
|
|||||||
// variants (e.g. Display variants of Noto fonts) and then some
|
// variants (e.g. Display variants of Noto fonts) and then some
|
||||||
// variants become inaccessible from Typst. And even though the
|
// variants become inaccessible from Typst. And even though the
|
||||||
// fsSelection bit WWS should help us decide whether that is the
|
// fsSelection bit WWS should help us decide whether that is the
|
||||||
// case, it's wrong for some fonts (e.g. for some faces of "Noto
|
// case, it's wrong for some fonts (e.g. for certain variants of "Noto
|
||||||
// Sans Display").
|
// Sans Display").
|
||||||
//
|
//
|
||||||
// So, instead we use Name ID 1 "Family" and trim many common
|
// So, instead we use Name ID 1 "Family" and trim many common
|
||||||
@ -539,7 +539,7 @@ impl FaceInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(FaceInfo {
|
Some(FontInfo {
|
||||||
path: path.to_owned(),
|
path: path.to_owned(),
|
||||||
index,
|
index,
|
||||||
family,
|
family,
|
||||||
@ -648,15 +648,15 @@ fn trim_styles(mut family: &str) -> &str {
|
|||||||
&family[.. len]
|
&family[.. len]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Properties that distinguish a face from other faces in the same family.
|
/// Properties that distinguish a font from other fonts in the same family.
|
||||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct FontVariant {
|
pub struct FontVariant {
|
||||||
/// The style of the face (normal / italic / oblique).
|
/// The style of the font (normal / italic / oblique).
|
||||||
pub style: FontStyle,
|
pub style: FontStyle,
|
||||||
/// How heavy the face is (100 - 900).
|
/// How heavy the font is (100 - 900).
|
||||||
pub weight: FontWeight,
|
pub weight: FontWeight,
|
||||||
/// How condensed or expanded the face is (0.5 - 2.0).
|
/// How condensed or expanded the font is (0.5 - 2.0).
|
||||||
pub stretch: FontStretch,
|
pub stretch: FontStretch,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,7 +673,7 @@ impl Debug for FontVariant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The style of a font face.
|
/// The style of a font.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@ -705,7 +705,7 @@ impl Default for FontStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The weight of a font face.
|
/// The weight of a font.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
@ -773,7 +773,7 @@ impl Debug for FontWeight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The width of a font face.
|
/// The width of a font.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
|
@ -5,7 +5,7 @@ use std::num::NonZeroUsize;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::eval::{Dict, Value};
|
use crate::eval::{Dict, Value};
|
||||||
use crate::font::FaceId;
|
use crate::font::FontId;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
||||||
};
|
};
|
||||||
@ -356,8 +356,8 @@ impl Debug for Group {
|
|||||||
/// A run of shaped text.
|
/// A run of shaped text.
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
/// The font face the glyphs are contained in.
|
/// The font the glyphs are contained in.
|
||||||
pub face_id: FaceId,
|
pub font_id: FontId,
|
||||||
/// The font size.
|
/// The font size.
|
||||||
pub size: Length,
|
pub size: Length,
|
||||||
/// Glyph color.
|
/// Glyph color.
|
||||||
@ -391,7 +391,7 @@ impl Debug for Text {
|
|||||||
/// A glyph in a run of shaped text.
|
/// A glyph in a run of shaped text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub struct Glyph {
|
pub struct Glyph {
|
||||||
/// The glyph's index in the face.
|
/// The glyph's index in the font.
|
||||||
pub id: u16,
|
pub id: u16,
|
||||||
/// The advance width of the glyph.
|
/// The advance width of the glyph.
|
||||||
pub x_advance: Em,
|
pub x_advance: Em,
|
||||||
|
@ -78,7 +78,7 @@ pub struct Context {
|
|||||||
pub loader: Arc<dyn Loader>,
|
pub loader: Arc<dyn Loader>,
|
||||||
/// Stores loaded source files.
|
/// Stores loaded source files.
|
||||||
pub sources: SourceStore,
|
pub sources: SourceStore,
|
||||||
/// Stores parsed font faces.
|
/// Stores parsed fonts.
|
||||||
pub fonts: FontStore,
|
pub fonts: FontStore,
|
||||||
/// Stores decoded images.
|
/// Stores decoded images.
|
||||||
pub images: ImageStore,
|
pub images: ImageStore,
|
||||||
|
@ -4,7 +4,7 @@ use rex::layout::{LayoutSettings, Style};
|
|||||||
use rex::parser::color::RGBA;
|
use rex::parser::color::RGBA;
|
||||||
use rex::render::{Backend, Cursor, Renderer};
|
use rex::render::{Backend, Cursor, Renderer};
|
||||||
|
|
||||||
use crate::font::FaceId;
|
use crate::font::FontId;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{variant, FontFamily, Lang, TextNode};
|
use crate::library::text::{variant, FontFamily, Lang, TextNode};
|
||||||
|
|
||||||
@ -28,17 +28,17 @@ impl Layout for RexNode {
|
|||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
// Load the font.
|
// Load the font.
|
||||||
let span = self.tex.span;
|
let span = self.tex.span;
|
||||||
let face_id = ctx
|
let font_id = ctx
|
||||||
.fonts
|
.fonts
|
||||||
.select(self.family.as_str(), variant(styles))
|
.select(self.family.as_str(), variant(styles))
|
||||||
.ok_or("failed to find math font")
|
.ok_or("failed to find math font")
|
||||||
.at(span)?;
|
.at(span)?;
|
||||||
|
|
||||||
// Prepare the font context.
|
// Prepare the font context.
|
||||||
let face = ctx.fonts.get(face_id);
|
let font = ctx.fonts.get(font_id);
|
||||||
let ctx = face
|
let ctx = font
|
||||||
.math()
|
.math()
|
||||||
.map(|math| FontContext::new(face.ttf(), math))
|
.map(|math| FontContext::new(font.ttf(), math))
|
||||||
.ok_or("font is not suitable for math")
|
.ok_or("font is not suitable for math")
|
||||||
.at(span)?;
|
.at(span)?;
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ impl Layout for RexNode {
|
|||||||
let mut top = Length::pt(y1);
|
let mut top = Length::pt(y1);
|
||||||
let mut bottom = Length::pt(-y0);
|
let mut bottom = Length::pt(-y0);
|
||||||
if !self.display {
|
if !self.display {
|
||||||
let metrics = face.metrics();
|
let metrics = font.metrics();
|
||||||
top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
|
top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
|
||||||
bottom = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, metrics);
|
bottom = -styles.get(TextNode::BOTTOM_EDGE).resolve(styles, metrics);
|
||||||
};
|
};
|
||||||
@ -76,7 +76,7 @@ impl Layout for RexNode {
|
|||||||
frame
|
frame
|
||||||
},
|
},
|
||||||
baseline: top,
|
baseline: top,
|
||||||
face_id,
|
font_id,
|
||||||
fill: styles.get(TextNode::FILL),
|
fill: styles.get(TextNode::FILL),
|
||||||
lang: styles.get(TextNode::LANG),
|
lang: styles.get(TextNode::LANG),
|
||||||
colors: vec![],
|
colors: vec![],
|
||||||
@ -93,7 +93,7 @@ impl Layout for RexNode {
|
|||||||
struct FrameBackend {
|
struct FrameBackend {
|
||||||
frame: Frame,
|
frame: Frame,
|
||||||
baseline: Length,
|
baseline: Length,
|
||||||
face_id: FaceId,
|
font_id: FontId,
|
||||||
fill: Paint,
|
fill: Paint,
|
||||||
lang: Lang,
|
lang: Lang,
|
||||||
colors: Vec<RGBA>,
|
colors: Vec<RGBA>,
|
||||||
@ -119,7 +119,7 @@ impl Backend for FrameBackend {
|
|||||||
self.frame.push(
|
self.frame.push(
|
||||||
self.transform(pos),
|
self.transform(pos),
|
||||||
Element::Text(Text {
|
Element::Text(Text {
|
||||||
face_id: self.face_id,
|
font_id: self.font_id,
|
||||||
size: Length::pt(scale),
|
size: Length::pt(scale),
|
||||||
fill: self.fill(),
|
fill: self.fill(),
|
||||||
lang: self.lang,
|
lang: self.lang,
|
||||||
|
@ -94,12 +94,12 @@ pub fn decorate(
|
|||||||
pos: Point,
|
pos: Point,
|
||||||
width: Length,
|
width: Length,
|
||||||
) {
|
) {
|
||||||
let face = fonts.get(text.face_id);
|
let font = fonts.get(text.font_id);
|
||||||
let face_metrics = face.metrics();
|
let font_metrics = font.metrics();
|
||||||
let metrics = match deco.line {
|
let metrics = match deco.line {
|
||||||
STRIKETHROUGH => face_metrics.strikethrough,
|
STRIKETHROUGH => font_metrics.strikethrough,
|
||||||
OVERLINE => face_metrics.overline,
|
OVERLINE => font_metrics.overline,
|
||||||
UNDERLINE | _ => face_metrics.underline,
|
UNDERLINE | _ => font_metrics.underline,
|
||||||
};
|
};
|
||||||
|
|
||||||
let evade = deco.evade && deco.line != STRIKETHROUGH;
|
let evade = deco.evade && deco.line != STRIKETHROUGH;
|
||||||
@ -141,9 +141,9 @@ pub fn decorate(
|
|||||||
for glyph in text.glyphs.iter() {
|
for glyph in text.glyphs.iter() {
|
||||||
let dx = glyph.x_offset.at(text.size) + x;
|
let dx = glyph.x_offset.at(text.size) + x;
|
||||||
let mut builder =
|
let mut builder =
|
||||||
BezPathBuilder::new(face_metrics.units_per_em, text.size, dx.to_raw());
|
BezPathBuilder::new(font_metrics.units_per_em, text.size, dx.to_raw());
|
||||||
|
|
||||||
let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
|
let bbox = font.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
|
||||||
let path = builder.finish();
|
let path = builder.finish();
|
||||||
|
|
||||||
x += glyph.x_advance.at(text.size);
|
x += glyph.x_advance.at(text.size);
|
||||||
@ -151,8 +151,8 @@ pub fn decorate(
|
|||||||
// Only do the costly segments intersection test if the line
|
// Only do the costly segments intersection test if the line
|
||||||
// intersects the bounding box.
|
// intersects the bounding box.
|
||||||
if bbox.map_or(false, |bbox| {
|
if bbox.map_or(false, |bbox| {
|
||||||
let y_min = -face.to_em(bbox.y_max).at(text.size);
|
let y_min = -font.to_em(bbox.y_max).at(text.size);
|
||||||
let y_max = -face.to_em(bbox.y_min).at(text.size);
|
let y_max = -font.to_em(bbox.y_min).at(text.size);
|
||||||
|
|
||||||
offset >= y_min && offset <= y_max
|
offset >= y_min && offset <= y_max
|
||||||
}) {
|
}) {
|
||||||
|
@ -25,7 +25,7 @@ use std::borrow::Cow;
|
|||||||
use ttf_parser::Tag;
|
use ttf_parser::Tag;
|
||||||
|
|
||||||
use crate::font::{
|
use crate::font::{
|
||||||
Face, FaceMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric,
|
Font, FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric,
|
||||||
};
|
};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -269,8 +269,8 @@ pub enum TextEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextEdge {
|
impl TextEdge {
|
||||||
/// Resolve the value of the text edge given a font face.
|
/// Resolve the value of the text edge given a font's metrics.
|
||||||
pub fn resolve(self, styles: StyleChain, metrics: &FaceMetrics) -> Length {
|
pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Length {
|
||||||
match self {
|
match self {
|
||||||
Self::Metric(metric) => metrics.vertical(metric).resolve(styles),
|
Self::Metric(metric) => metrics.vertical(metric).resolve(styles),
|
||||||
Self::Length(length) => length.resolve(styles),
|
Self::Length(length) => length.resolve(styles),
|
||||||
@ -333,7 +333,7 @@ impl Resolve for Smart<Hyphenate> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A stylistic set in a font face.
|
/// A stylistic set in a font.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct StylisticSet(u8);
|
pub struct StylisticSet(u8);
|
||||||
|
|
||||||
@ -514,7 +514,7 @@ impl Show for StrongNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emphasized text, rendered with an italic face by default.
|
/// Emphasized text, rendered with an italic font by default.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct EmphNode(pub Content);
|
pub struct EmphNode(pub Content);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ use std::str::FromStr;
|
|||||||
use rustybuzz::{Feature, UnicodeBuffer};
|
use rustybuzz::{Feature, UnicodeBuffer};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::font::{FaceId, FontStore, FontVariant};
|
use crate::font::{FontId, FontStore, FontVariant};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::util::SliceExt;
|
use crate::util::SliceExt;
|
||||||
|
|
||||||
@ -33,9 +33,9 @@ pub struct ShapedText<'a> {
|
|||||||
/// A single glyph resulting from shaping.
|
/// A single glyph resulting from shaping.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct ShapedGlyph {
|
pub struct ShapedGlyph {
|
||||||
/// The font face the glyph is contained in.
|
/// The font the glyph is contained in.
|
||||||
pub face_id: FaceId,
|
pub font_id: FontId,
|
||||||
/// The glyph's index in the face.
|
/// The glyph's index in the font.
|
||||||
pub glyph_id: u16,
|
pub glyph_id: u16,
|
||||||
/// The advance width of the glyph.
|
/// The advance width of the glyph.
|
||||||
pub x_advance: Em,
|
pub x_advance: Em,
|
||||||
@ -94,8 +94,8 @@ impl<'a> ShapedText<'a> {
|
|||||||
let fill = self.styles.get(TextNode::FILL);
|
let fill = self.styles.get(TextNode::FILL);
|
||||||
let link = self.styles.get(TextNode::LINK);
|
let link = self.styles.get(TextNode::LINK);
|
||||||
|
|
||||||
for ((face_id, y_offset), group) in
|
for ((font_id, y_offset), group) in
|
||||||
self.glyphs.as_ref().group_by_key(|g| (g.face_id, g.y_offset))
|
self.glyphs.as_ref().group_by_key(|g| (g.font_id, g.y_offset))
|
||||||
{
|
{
|
||||||
let pos = Point::new(offset, top + shift + y_offset.at(self.size));
|
let pos = Point::new(offset, top + shift + y_offset.at(self.size));
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let text = Text {
|
let text = Text {
|
||||||
face_id,
|
font_id,
|
||||||
size: self.size,
|
size: self.size,
|
||||||
lang,
|
lang,
|
||||||
fill,
|
fill,
|
||||||
@ -151,9 +151,9 @@ impl<'a> ShapedText<'a> {
|
|||||||
let top_edge = self.styles.get(TextNode::TOP_EDGE);
|
let top_edge = self.styles.get(TextNode::TOP_EDGE);
|
||||||
let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE);
|
let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE);
|
||||||
|
|
||||||
// Expand top and bottom by reading the face's vertical metrics.
|
// Expand top and bottom by reading the font's vertical metrics.
|
||||||
let mut expand = |face: &Face| {
|
let mut expand = |font: &Font| {
|
||||||
let metrics = face.metrics();
|
let metrics = font.metrics();
|
||||||
top.set_max(top_edge.resolve(self.styles, metrics));
|
top.set_max(top_edge.resolve(self.styles, metrics));
|
||||||
bottom.set_max(-bottom_edge.resolve(self.styles, metrics));
|
bottom.set_max(-bottom_edge.resolve(self.styles, metrics));
|
||||||
};
|
};
|
||||||
@ -162,15 +162,14 @@ impl<'a> ShapedText<'a> {
|
|||||||
// When there are no glyphs, we just use the vertical metrics of the
|
// When there are no glyphs, we just use the vertical metrics of the
|
||||||
// first available font.
|
// first available font.
|
||||||
for family in families(self.styles) {
|
for family in families(self.styles) {
|
||||||
if let Some(face_id) = fonts.select(family, self.variant) {
|
if let Some(font_id) = fonts.select(family, self.variant) {
|
||||||
expand(fonts.get(face_id));
|
expand(fonts.get(font_id));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (face_id, _) in self.glyphs.group_by_key(|g| g.face_id) {
|
for (font_id, _) in self.glyphs.group_by_key(|g| g.font_id) {
|
||||||
let face = fonts.get(face_id);
|
expand(fonts.get(font_id));
|
||||||
expand(face);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,15 +216,15 @@ 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) {
|
||||||
families(self.styles).find_map(|family| {
|
families(self.styles).find_map(|family| {
|
||||||
let face_id = fonts.select(family, self.variant)?;
|
let font_id = fonts.select(family, self.variant)?;
|
||||||
let face = fonts.get(face_id);
|
let font = fonts.get(font_id);
|
||||||
let ttf = face.ttf();
|
let ttf = font.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 = font.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.width += x_advance.at(self.size);
|
self.width += x_advance.at(self.size);
|
||||||
self.glyphs.to_mut().push(ShapedGlyph {
|
self.glyphs.to_mut().push(ShapedGlyph {
|
||||||
face_id,
|
font_id,
|
||||||
glyph_id: glyph_id.0,
|
glyph_id: glyph_id.0,
|
||||||
x_advance,
|
x_advance,
|
||||||
x_offset: Em::zero(),
|
x_offset: Em::zero(),
|
||||||
@ -303,7 +302,7 @@ impl Debug for ShapedText<'_> {
|
|||||||
struct ShapingContext<'a> {
|
struct ShapingContext<'a> {
|
||||||
fonts: &'a mut FontStore,
|
fonts: &'a mut FontStore,
|
||||||
glyphs: Vec<ShapedGlyph>,
|
glyphs: Vec<ShapedGlyph>,
|
||||||
used: Vec<FaceId>,
|
used: Vec<FontId>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
size: Length,
|
size: Length,
|
||||||
variant: FontVariant,
|
variant: FontVariant,
|
||||||
@ -378,17 +377,17 @@ fn shape_segment<'a>(
|
|||||||
.filter(|id| !ctx.used.contains(id));
|
.filter(|id| !ctx.used.contains(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the face id or shape notdef glyphs if we couldn't find any face.
|
// Extract the font id or shape notdef glyphs if we couldn't find any font.
|
||||||
let face_id = if let Some(id) = selection {
|
let font_id = if let Some(id) = selection {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
if let Some(&face_id) = ctx.used.first() {
|
if let Some(&font_id) = ctx.used.first() {
|
||||||
shape_tofus(ctx, base, text, face_id);
|
shape_tofus(ctx, base, text, font_id);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.used.push(face_id);
|
ctx.used.push(font_id);
|
||||||
|
|
||||||
// Fill the buffer with our text.
|
// Fill the buffer with our text.
|
||||||
let mut buffer = UnicodeBuffer::new();
|
let mut buffer = UnicodeBuffer::new();
|
||||||
@ -401,8 +400,8 @@ fn shape_segment<'a>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Shape!
|
// Shape!
|
||||||
let mut face = ctx.fonts.get(face_id);
|
let mut font = ctx.fonts.get(font_id);
|
||||||
let buffer = rustybuzz::shape(face.ttf(), &ctx.tags, buffer);
|
let buffer = rustybuzz::shape(font.ttf(), &ctx.tags, buffer);
|
||||||
let infos = buffer.glyph_infos();
|
let infos = buffer.glyph_infos();
|
||||||
let pos = buffer.glyph_positions();
|
let pos = buffer.glyph_positions();
|
||||||
|
|
||||||
@ -417,11 +416,11 @@ fn shape_segment<'a>(
|
|||||||
// Add the glyph to the shaped output.
|
// Add the glyph to the shaped output.
|
||||||
// TODO: Don't ignore y_advance.
|
// TODO: Don't ignore y_advance.
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.glyphs.push(ShapedGlyph {
|
||||||
face_id,
|
font_id,
|
||||||
glyph_id: info.glyph_id as u16,
|
glyph_id: info.glyph_id as u16,
|
||||||
x_advance: face.to_em(pos[i].x_advance),
|
x_advance: font.to_em(pos[i].x_advance),
|
||||||
x_offset: face.to_em(pos[i].x_offset),
|
x_offset: font.to_em(pos[i].x_offset),
|
||||||
y_offset: face.to_em(pos[i].y_offset),
|
y_offset: font.to_em(pos[i].y_offset),
|
||||||
cluster: base + cluster,
|
cluster: base + cluster,
|
||||||
safe_to_break: !info.unsafe_to_break(),
|
safe_to_break: !info.unsafe_to_break(),
|
||||||
c: text[cluster ..].chars().next().unwrap(),
|
c: text[cluster ..].chars().next().unwrap(),
|
||||||
@ -473,7 +472,7 @@ fn shape_segment<'a>(
|
|||||||
// Recursively shape the tofu sequence with the next family.
|
// Recursively shape the tofu sequence with the next family.
|
||||||
shape_segment(ctx, base + range.start, &text[range], families.clone());
|
shape_segment(ctx, base + range.start, &text[range], families.clone());
|
||||||
|
|
||||||
face = ctx.fonts.get(face_id);
|
font = ctx.fonts.get(font_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
@ -482,13 +481,13 @@ fn shape_segment<'a>(
|
|||||||
ctx.used.pop();
|
ctx.used.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shape the text with tofus from the given face.
|
/// Shape the text with tofus from the given font.
|
||||||
fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, face_id: FaceId) {
|
fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, font_id: FontId) {
|
||||||
let face = ctx.fonts.get(face_id);
|
let font = ctx.fonts.get(font_id);
|
||||||
let x_advance = face.advance(0).unwrap_or_default();
|
let x_advance = font.advance(0).unwrap_or_default();
|
||||||
for (cluster, c) in text.char_indices() {
|
for (cluster, c) in text.char_indices() {
|
||||||
ctx.glyphs.push(ShapedGlyph {
|
ctx.glyphs.push(ShapedGlyph {
|
||||||
face_id,
|
font_id,
|
||||||
glyph_id: 0,
|
glyph_id: 0,
|
||||||
x_advance,
|
x_advance,
|
||||||
x_offset: Em::zero(),
|
x_offset: Em::zero(),
|
||||||
@ -512,8 +511,8 @@ fn track_and_space(ctx: &mut ShapingContext) {
|
|||||||
while let Some(glyph) = glyphs.next() {
|
while let Some(glyph) = glyphs.next() {
|
||||||
// Make non-breaking space same width as normal space.
|
// Make non-breaking space same width as normal space.
|
||||||
if glyph.c == '\u{00A0}' {
|
if glyph.c == '\u{00A0}' {
|
||||||
let face = ctx.fonts.get(glyph.face_id);
|
let font = ctx.fonts.get(glyph.font_id);
|
||||||
glyph.x_advance -= nbsp_delta(face).unwrap_or_default();
|
glyph.x_advance -= nbsp_delta(font).unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
if glyph.is_space() {
|
if glyph.is_space() {
|
||||||
@ -527,10 +526,10 @@ fn track_and_space(ctx: &mut ShapingContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Difference between non-breaking and normal space.
|
/// Difference between non-breaking and normal space.
|
||||||
fn nbsp_delta(face: &Face) -> Option<Em> {
|
fn nbsp_delta(font: &Font) -> Option<Em> {
|
||||||
let space = face.ttf().glyph_index(' ')?.0;
|
let space = font.ttf().glyph_index(' ')?.0;
|
||||||
let nbsp = face.ttf().glyph_index('\u{00A0}')?.0;
|
let nbsp = font.ttf().glyph_index('\u{00A0}')?.0;
|
||||||
Some(face.advance(nbsp)? - face.advance(space)?)
|
Some(font.advance(nbsp)? - font.advance(space)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the font variant with `BOLD` and `ITALIC` factored in.
|
/// Resolve the font variant with `BOLD` and `ITALIC` factored in.
|
||||||
|
@ -94,8 +94,8 @@ fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
|
|||||||
/// given string.
|
/// given string.
|
||||||
fn is_shapable(fonts: &mut FontStore, text: &str, styles: StyleChain) -> bool {
|
fn is_shapable(fonts: &mut FontStore, text: &str, styles: StyleChain) -> bool {
|
||||||
for family in styles.get(TextNode::FAMILY).iter() {
|
for family in styles.get(TextNode::FAMILY).iter() {
|
||||||
if let Some(face_id) = fonts.select(family.as_str(), variant(styles)) {
|
if let Some(font_id) = fonts.select(family.as_str(), variant(styles)) {
|
||||||
let ttf = fonts.get(face_id).ttf();
|
let ttf = fonts.get(font_id).ttf();
|
||||||
return text.chars().all(|c| ttf.glyph_index(c).is_some());
|
return text.chars().all(|c| ttf.glyph_index(c).is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ pub use string::*;
|
|||||||
|
|
||||||
use crate::eval::{Eval, Machine, Scopes};
|
use crate::eval::{Eval, Machine, Scopes};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::source::SourceFile;
|
use crate::source::Source;
|
||||||
|
|
||||||
/// The name of a value's type.
|
/// The name of a value's type.
|
||||||
pub fn type_(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn type_(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||||
@ -33,7 +33,7 @@ pub fn eval(vm: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
let Spanned { v: src, span } = args.expect::<Spanned<String>>("source")?;
|
let Spanned { v: src, span } = args.expect::<Spanned<String>>("source")?;
|
||||||
|
|
||||||
// Parse the source and set a synthetic span for all nodes.
|
// Parse the source and set a synthetic span for all nodes.
|
||||||
let source = SourceFile::synthesized(src, span);
|
let source = Source::synthesized(src, span);
|
||||||
let ast = source.ast()?;
|
let ast = source.ast()?;
|
||||||
|
|
||||||
// Evaluate the source.
|
// Evaluate the source.
|
||||||
|
@ -7,19 +7,19 @@ use same_file::Handle;
|
|||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use super::{FileHash, Loader};
|
use super::{FileHash, Loader};
|
||||||
use crate::font::FaceInfo;
|
use crate::font::FontInfo;
|
||||||
|
|
||||||
/// Loads fonts and files from the local file system.
|
/// Loads fonts and files from the local file system.
|
||||||
///
|
///
|
||||||
/// _This is only available when the `fs` feature is enabled._
|
/// _This is only available when the `fs` feature is enabled._
|
||||||
pub struct FsLoader {
|
pub struct FsLoader {
|
||||||
faces: Vec<FaceInfo>,
|
fonts: Vec<FontInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FsLoader {
|
impl FsLoader {
|
||||||
/// Create a new loader without any fonts.
|
/// Create a new loader without any fonts.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { faces: vec![] }
|
Self { fonts: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder-style variant of [`search_system`](Self::search_system).
|
/// Builder-style variant of [`search_system`](Self::search_system).
|
||||||
@ -100,24 +100,24 @@ impl FsLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Index the font faces in the file at the given path.
|
/// Index the fonts in the file at the given path.
|
||||||
///
|
///
|
||||||
/// The file may form a font collection and contain multiple font faces,
|
/// The file may form a font collection and contain multiple fonts,
|
||||||
/// which will then all be indexed.
|
/// which will then all be indexed.
|
||||||
fn search_file(&mut self, path: impl AsRef<Path>) {
|
fn search_file(&mut self, path: impl AsRef<Path>) {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let path = path.strip_prefix(".").unwrap_or(path);
|
let path = path.strip_prefix(".").unwrap_or(path);
|
||||||
if let Ok(file) = File::open(path) {
|
if let Ok(file) = File::open(path) {
|
||||||
if let Ok(mmap) = unsafe { Mmap::map(&file) } {
|
if let Ok(mmap) = unsafe { Mmap::map(&file) } {
|
||||||
self.faces.extend(FaceInfo::from_data(path, &mmap));
|
self.fonts.extend(FontInfo::from_data(path, &mmap));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Loader for FsLoader {
|
impl Loader for FsLoader {
|
||||||
fn faces(&self) -> &[FaceInfo] {
|
fn fonts(&self) -> &[FontInfo] {
|
||||||
&self.faces
|
&self.fonts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, path: &Path) -> io::Result<FileHash> {
|
fn resolve(&self, path: &Path) -> io::Result<FileHash> {
|
||||||
|
@ -4,20 +4,20 @@ use std::io;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::{FileHash, Loader};
|
use super::{FileHash, Loader};
|
||||||
use crate::font::FaceInfo;
|
use crate::font::FontInfo;
|
||||||
use crate::util::PathExt;
|
use crate::util::PathExt;
|
||||||
|
|
||||||
/// Loads fonts and files from an in-memory storage.
|
/// Loads fonts and files from an in-memory storage.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MemLoader {
|
pub struct MemLoader {
|
||||||
faces: Vec<FaceInfo>,
|
fonts: Vec<FontInfo>,
|
||||||
files: HashMap<PathBuf, Cow<'static, [u8]>>,
|
files: HashMap<PathBuf, Cow<'static, [u8]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemLoader {
|
impl MemLoader {
|
||||||
/// Create a new from-memory loader.
|
/// Create a new from-memory loader.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { faces: vec![], files: HashMap::new() }
|
Self { fonts: vec![], files: HashMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder-style variant of [`insert`](Self::insert).
|
/// Builder-style variant of [`insert`](Self::insert).
|
||||||
@ -42,14 +42,14 @@ impl MemLoader {
|
|||||||
{
|
{
|
||||||
let path = path.as_ref().normalize();
|
let path = path.as_ref().normalize();
|
||||||
let data = data.into();
|
let data = data.into();
|
||||||
self.faces.extend(FaceInfo::from_data(&path, &data));
|
self.fonts.extend(FontInfo::from_data(&path, &data));
|
||||||
self.files.insert(path, data);
|
self.files.insert(path, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Loader for MemLoader {
|
impl Loader for MemLoader {
|
||||||
fn faces(&self) -> &[FaceInfo] {
|
fn fonts(&self) -> &[FontInfo] {
|
||||||
&self.faces
|
&self.fonts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, path: &Path) -> io::Result<FileHash> {
|
fn resolve(&self, path: &Path) -> io::Result<FileHash> {
|
||||||
@ -80,13 +80,13 @@ mod tests {
|
|||||||
let path = Path::new("PTSans.ttf");
|
let path = Path::new("PTSans.ttf");
|
||||||
let loader = MemLoader::new().with(path, &data[..]);
|
let loader = MemLoader::new().with(path, &data[..]);
|
||||||
|
|
||||||
// Test that the face was found.
|
// Test that the font was found.
|
||||||
let info = &loader.faces[0];
|
let info = &loader.fonts[0];
|
||||||
assert_eq!(info.path, path);
|
assert_eq!(info.path, path);
|
||||||
assert_eq!(info.index, 0);
|
assert_eq!(info.index, 0);
|
||||||
assert_eq!(info.family, "PT Sans");
|
assert_eq!(info.family, "PT Sans");
|
||||||
assert_eq!(info.variant, FontVariant::default());
|
assert_eq!(info.variant, FontVariant::default());
|
||||||
assert_eq!(loader.faces.len(), 1);
|
assert_eq!(loader.fonts.len(), 1);
|
||||||
|
|
||||||
// Test that the file can be loaded.
|
// Test that the file can be loaded.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -11,7 +11,7 @@ pub use mem::*;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::font::FaceInfo;
|
use crate::font::FontInfo;
|
||||||
|
|
||||||
/// A hash that identifies a file.
|
/// A hash that identifies a file.
|
||||||
///
|
///
|
||||||
@ -21,8 +21,8 @@ pub struct FileHash(pub u64);
|
|||||||
|
|
||||||
/// Loads resources from a local or remote source.
|
/// Loads resources from a local or remote source.
|
||||||
pub trait Loader {
|
pub trait Loader {
|
||||||
/// Descriptions of all font faces this loader serves.
|
/// Descriptions of all fonts this loader serves.
|
||||||
fn faces(&self) -> &[FaceInfo];
|
fn fonts(&self) -> &[FontInfo];
|
||||||
|
|
||||||
/// Resolve a hash that is the same for this and all other paths pointing to
|
/// Resolve a hash that is the same for this and all other paths pointing to
|
||||||
/// the same file.
|
/// the same file.
|
||||||
@ -36,7 +36,7 @@ pub trait Loader {
|
|||||||
pub struct BlankLoader;
|
pub struct BlankLoader;
|
||||||
|
|
||||||
impl Loader for BlankLoader {
|
impl Loader for BlankLoader {
|
||||||
fn faces(&self) -> &[FaceInfo] {
|
fn fonts(&self) -> &[FontInfo] {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use same_file::is_same_file;
|
|||||||
use termcolor::{ColorChoice, StandardStream, WriteColor};
|
use termcolor::{ColorChoice, StandardStream, WriteColor};
|
||||||
|
|
||||||
use typst::diag::{Error, StrResult};
|
use typst::diag::{Error, StrResult};
|
||||||
use typst::font::{FaceInfo, FontStore};
|
use typst::font::{FontInfo, FontStore};
|
||||||
use typst::library::text::THEME;
|
use typst::library::text::THEME;
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::parse::TokenMode;
|
use typst::parse::TokenMode;
|
||||||
@ -276,7 +276,7 @@ fn fonts(command: FontsCommand) -> StrResult<()> {
|
|||||||
for (name, infos) in fonts.families() {
|
for (name, infos) in fonts.families() {
|
||||||
println!("{name}");
|
println!("{name}");
|
||||||
if command.variants {
|
if command.variants {
|
||||||
for &FaceInfo { variant, .. } in infos {
|
for &FontInfo { variant, .. } in infos {
|
||||||
println!(
|
println!(
|
||||||
"- Style: {:?}, Weight: {:?}, Stretch: {:?}",
|
"- Style: {:?}, Weight: {:?}, Stretch: {:?}",
|
||||||
variant.style, variant.weight, variant.stretch,
|
variant.style, variant.weight, variant.stretch,
|
||||||
|
@ -390,11 +390,11 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::parse::parse;
|
use crate::parse::parse;
|
||||||
use crate::parse::tests::check;
|
use crate::parse::tests::check;
|
||||||
use crate::source::SourceFile;
|
use crate::source::Source;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(prev: &str, range: Range<usize>, with: &str, goal: Range<usize>) {
|
fn test(prev: &str, range: Range<usize>, with: &str, goal: Range<usize>) {
|
||||||
let mut source = SourceFile::detached(prev);
|
let mut source = Source::detached(prev);
|
||||||
let range = source.edit(range, with);
|
let range = source.edit(range, with);
|
||||||
check(source.src(), source.root(), &parse(source.src()));
|
check(source.src(), source.root(), &parse(source.src()));
|
||||||
assert_eq!(range, goal);
|
assert_eq!(range, goal);
|
||||||
|
@ -46,7 +46,7 @@ impl SourceId {
|
|||||||
pub struct SourceStore {
|
pub struct SourceStore {
|
||||||
loader: Arc<dyn Loader>,
|
loader: Arc<dyn Loader>,
|
||||||
files: HashMap<FileHash, SourceId>,
|
files: HashMap<FileHash, SourceId>,
|
||||||
sources: Vec<SourceFile>,
|
sources: Vec<Source>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceStore {
|
impl SourceStore {
|
||||||
@ -65,7 +65,7 @@ impl SourceStore {
|
|||||||
/// should only be called with ids returned by this store's
|
/// should only be called with ids returned by this store's
|
||||||
/// [`load()`](Self::load) and [`provide()`](Self::provide) methods.
|
/// [`load()`](Self::load) and [`provide()`](Self::provide) methods.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn get(&self, id: SourceId) -> &SourceFile {
|
pub fn get(&self, id: SourceId) -> &Source {
|
||||||
&self.sources[id.0 as usize]
|
&self.sources[id.0 as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ impl SourceStore {
|
|||||||
|
|
||||||
// No existing file yet, so we allocate a new id.
|
// No existing file yet, so we allocate a new id.
|
||||||
let id = SourceId(self.sources.len() as u16);
|
let id = SourceId(self.sources.len() as u16);
|
||||||
self.sources.push(SourceFile::new(id, path, src));
|
self.sources.push(Source::new(id, path, src));
|
||||||
|
|
||||||
// Register in file map if the path was known to the loader.
|
// Register in file map if the path was known to the loader.
|
||||||
if let Some(hash) = hash {
|
if let Some(hash) = hash {
|
||||||
@ -157,7 +157,7 @@ impl SourceStore {
|
|||||||
///
|
///
|
||||||
/// _Note_: All line and column indices start at zero, just like byte indices.
|
/// _Note_: All line and column indices start at zero, just like byte indices.
|
||||||
/// Only for user-facing display, you should add 1 to them.
|
/// Only for user-facing display, you should add 1 to them.
|
||||||
pub struct SourceFile {
|
pub struct Source {
|
||||||
id: SourceId,
|
id: SourceId,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
src: String,
|
src: String,
|
||||||
@ -166,7 +166,7 @@ pub struct SourceFile {
|
|||||||
rev: usize,
|
rev: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceFile {
|
impl Source {
|
||||||
/// Create a new source file.
|
/// Create a new source file.
|
||||||
pub fn new(id: SourceId, path: &Path, src: String) -> Self {
|
pub fn new(id: SourceId, path: &Path, src: String) -> Self {
|
||||||
let lines = std::iter::once(Line { byte_idx: 0, utf16_idx: 0 })
|
let lines = std::iter::once(Line { byte_idx: 0, utf16_idx: 0 })
|
||||||
@ -485,7 +485,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_source_file_new() {
|
fn test_source_file_new() {
|
||||||
let source = SourceFile::detached(TEST);
|
let source = Source::detached(TEST);
|
||||||
assert_eq!(source.lines, [
|
assert_eq!(source.lines, [
|
||||||
Line { byte_idx: 0, utf16_idx: 0 },
|
Line { byte_idx: 0, utf16_idx: 0 },
|
||||||
Line { byte_idx: 7, utf16_idx: 6 },
|
Line { byte_idx: 7, utf16_idx: 6 },
|
||||||
@ -496,7 +496,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_source_file_pos_to_line() {
|
fn test_source_file_pos_to_line() {
|
||||||
let source = SourceFile::detached(TEST);
|
let source = Source::detached(TEST);
|
||||||
assert_eq!(source.byte_to_line(0), Some(0));
|
assert_eq!(source.byte_to_line(0), Some(0));
|
||||||
assert_eq!(source.byte_to_line(2), Some(0));
|
assert_eq!(source.byte_to_line(2), Some(0));
|
||||||
assert_eq!(source.byte_to_line(6), Some(0));
|
assert_eq!(source.byte_to_line(6), Some(0));
|
||||||
@ -509,7 +509,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_source_file_pos_to_column() {
|
fn test_source_file_pos_to_column() {
|
||||||
let source = SourceFile::detached(TEST);
|
let source = Source::detached(TEST);
|
||||||
assert_eq!(source.byte_to_column(0), Some(0));
|
assert_eq!(source.byte_to_column(0), Some(0));
|
||||||
assert_eq!(source.byte_to_column(2), Some(1));
|
assert_eq!(source.byte_to_column(2), Some(1));
|
||||||
assert_eq!(source.byte_to_column(6), Some(5));
|
assert_eq!(source.byte_to_column(6), Some(5));
|
||||||
@ -521,14 +521,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_source_file_utf16() {
|
fn test_source_file_utf16() {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn roundtrip(source: &SourceFile, byte_idx: usize, utf16_idx: usize) {
|
fn roundtrip(source: &Source, byte_idx: usize, utf16_idx: usize) {
|
||||||
let middle = source.byte_to_utf16(byte_idx).unwrap();
|
let middle = source.byte_to_utf16(byte_idx).unwrap();
|
||||||
let result = source.utf16_to_byte(middle).unwrap();
|
let result = source.utf16_to_byte(middle).unwrap();
|
||||||
assert_eq!(middle, utf16_idx);
|
assert_eq!(middle, utf16_idx);
|
||||||
assert_eq!(result, byte_idx);
|
assert_eq!(result, byte_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = SourceFile::detached(TEST);
|
let source = Source::detached(TEST);
|
||||||
roundtrip(&source, 0, 0);
|
roundtrip(&source, 0, 0);
|
||||||
roundtrip(&source, 2, 1);
|
roundtrip(&source, 2, 1);
|
||||||
roundtrip(&source, 3, 2);
|
roundtrip(&source, 3, 2);
|
||||||
@ -542,14 +542,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_source_file_roundtrip() {
|
fn test_source_file_roundtrip() {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn roundtrip(source: &SourceFile, byte_idx: usize) {
|
fn roundtrip(source: &Source, byte_idx: usize) {
|
||||||
let line = source.byte_to_line(byte_idx).unwrap();
|
let line = source.byte_to_line(byte_idx).unwrap();
|
||||||
let column = source.byte_to_column(byte_idx).unwrap();
|
let column = source.byte_to_column(byte_idx).unwrap();
|
||||||
let result = source.line_column_to_byte(line, column).unwrap();
|
let result = source.line_column_to_byte(line, column).unwrap();
|
||||||
assert_eq!(result, byte_idx);
|
assert_eq!(result, byte_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = SourceFile::detached(TEST);
|
let source = Source::detached(TEST);
|
||||||
roundtrip(&source, 0);
|
roundtrip(&source, 0);
|
||||||
roundtrip(&source, 7);
|
roundtrip(&source, 7);
|
||||||
roundtrip(&source, 12);
|
roundtrip(&source, 12);
|
||||||
@ -560,8 +560,8 @@ mod tests {
|
|||||||
fn test_source_file_edit() {
|
fn test_source_file_edit() {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(prev: &str, range: Range<usize>, with: &str, after: &str) {
|
fn test(prev: &str, range: Range<usize>, with: &str, after: &str) {
|
||||||
let mut source = SourceFile::detached(prev);
|
let mut source = Source::detached(prev);
|
||||||
let result = SourceFile::detached(after);
|
let result = Source::detached(after);
|
||||||
source.edit(range, with);
|
source.edit(range, with);
|
||||||
assert_eq!(source.src, result.src);
|
assert_eq!(source.src, result.src);
|
||||||
assert_eq!(source.root, result.root);
|
assert_eq!(source.root, result.root);
|
||||||
|
@ -377,7 +377,7 @@ impl Category {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::source::SourceFile;
|
use crate::source::Source;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_highlighting() {
|
fn test_highlighting() {
|
||||||
@ -386,7 +386,7 @@ mod tests {
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(src: &str, goal: &[(Range<usize>, Category)]) {
|
fn test(src: &str, goal: &[(Range<usize>, Category)]) {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
let source = SourceFile::detached(src);
|
let source = Source::detached(src);
|
||||||
let full = 0 .. src.len();
|
let full = 0 .. src.len();
|
||||||
highlight_node(source.root(), full, &mut |range, category| {
|
highlight_node(source.root(), full, &mut |range, category| {
|
||||||
vec.push((range, category));
|
vec.push((range, category));
|
||||||
|
@ -16,7 +16,7 @@ use typst::library::layout::PageNode;
|
|||||||
use typst::library::text::{TextNode, TextSize};
|
use typst::library::text::{TextNode, TextSize};
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::model::StyleMap;
|
use typst::model::StyleMap;
|
||||||
use typst::source::SourceFile;
|
use typst::source::Source;
|
||||||
use typst::syntax::SyntaxNode;
|
use typst::syntax::SyntaxNode;
|
||||||
use typst::{bail, Config, Context};
|
use typst::{bail, Config, Context};
|
||||||
|
|
||||||
@ -349,7 +349,7 @@ fn test_part(
|
|||||||
(ok, compare_ref, frames)
|
(ok, compare_ref, frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_metadata(source: &SourceFile) -> (Option<bool>, Vec<(Range<usize>, String)>) {
|
fn parse_metadata(source: &Source) -> (Option<bool>, Vec<(Range<usize>, String)>) {
|
||||||
let mut compare_ref = None;
|
let mut compare_ref = None;
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
|
|
||||||
@ -395,11 +395,7 @@ fn parse_metadata(source: &SourceFile) -> (Option<bool>, Vec<(Range<usize>, Stri
|
|||||||
(compare_ref, errors)
|
(compare_ref, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_error(
|
fn print_error(source: &Source, line: usize, (range, message): &(Range<usize>, String)) {
|
||||||
source: &SourceFile,
|
|
||||||
line: usize,
|
|
||||||
(range, message): &(Range<usize>, String),
|
|
||||||
) {
|
|
||||||
let start_line = 1 + line + source.byte_to_line(range.start).unwrap();
|
let start_line = 1 + line + source.byte_to_line(range.start).unwrap();
|
||||||
let start_col = 1 + source.byte_to_column(range.start).unwrap();
|
let start_col = 1 + source.byte_to_column(range.start).unwrap();
|
||||||
let end_line = 1 + line + source.byte_to_line(range.end).unwrap();
|
let end_line = 1 + line + source.byte_to_line(range.end).unwrap();
|
||||||
@ -445,7 +441,7 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
let apply = |replace: std::ops::Range<usize>, with| {
|
let apply = |replace: std::ops::Range<usize>, with| {
|
||||||
let mut incr_source = SourceFile::detached(src);
|
let mut incr_source = Source::detached(src);
|
||||||
if incr_source.root().len() != src.len() {
|
if incr_source.root().len() != src.len() {
|
||||||
println!(
|
println!(
|
||||||
" Subtest {i} tree length {} does not match string length {} ❌",
|
" Subtest {i} tree length {} does not match string length {} ❌",
|
||||||
@ -459,7 +455,7 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
|
|
||||||
let edited_src = incr_source.src();
|
let edited_src = incr_source.src();
|
||||||
let incr_root = incr_source.root();
|
let incr_root = incr_source.root();
|
||||||
let ref_source = SourceFile::detached(edited_src);
|
let ref_source = Source::detached(edited_src);
|
||||||
let ref_root = ref_source.root();
|
let ref_root = ref_source.root();
|
||||||
let mut ok = incr_root == ref_root;
|
let mut ok = incr_root == ref_root;
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -498,7 +494,7 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool {
|
|||||||
ok &= apply(start .. end, supplement);
|
ok &= apply(start .. end, supplement);
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = SourceFile::detached(src);
|
let source = Source::detached(src);
|
||||||
let leafs = source.root().leafs();
|
let leafs = source.root().leafs();
|
||||||
let start = source.range(leafs[pick(0 .. leafs.len())].span()).start;
|
let start = source.range(leafs[pick(0 .. leafs.len())].span()).start;
|
||||||
let supplement = supplements[pick(0 .. supplements.len())];
|
let supplement = supplements[pick(0 .. supplements.len())];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user