mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Formatting, documentation and small improvements 🧽
This commit is contained in:
parent
5a8f2fb73d
commit
dbfb3d2ced
@ -25,7 +25,7 @@ fs = ["fontdock/fs"]
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures-executor = "0.3"
|
futures-executor = "0.3"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
raqote = { version = "0.7", default-features = false }
|
raqote = { version = "0.8", default-features = false }
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "test-typeset"
|
name = "test-typeset"
|
||||||
|
43
main/main.rs
43
main/main.rs
@ -1,44 +1,37 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::error::Error;
|
use std::fs::{read_to_string, File};
|
||||||
use std::fs::{File, read_to_string};
|
|
||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use futures_executor::block_on;
|
|
||||||
|
|
||||||
use fontdock::fs::{FsIndex, FsProvider};
|
use fontdock::fs::{FsIndex, FsProvider};
|
||||||
use fontdock::FontLoader;
|
use fontdock::FontLoader;
|
||||||
use typstc::Typesetter;
|
use futures_executor::block_on;
|
||||||
use typstc::font::DynProvider;
|
|
||||||
use typstc::export::pdf;
|
use typstc::export::pdf;
|
||||||
|
use typstc::font::DynProvider;
|
||||||
|
use typstc::Typesetter;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(err) = run() {
|
let args: Vec<_> = std::env::args().collect();
|
||||||
eprintln!("error: {}", err);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run() -> Result<(), Box<dyn Error>> {
|
|
||||||
let args: Vec<String> = std::env::args().collect();
|
|
||||||
if args.len() < 2 || args.len() > 3 {
|
if args.len() < 2 || args.len() > 3 {
|
||||||
println!("Usage: typst src.typ [out.pdf]");
|
println!("Usage: typst src.typ [out.pdf]");
|
||||||
std::process::exit(0);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = Path::new(&args[1]);
|
let src_path = Path::new(&args[1]);
|
||||||
let dest = if args.len() <= 2 {
|
let dest_path = if args.len() <= 2 {
|
||||||
source.with_extension("pdf")
|
src_path.with_extension("pdf")
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from(&args[2])
|
PathBuf::from(&args[2])
|
||||||
};
|
};
|
||||||
|
|
||||||
if source == dest {
|
if src_path == dest_path {
|
||||||
Err("source and destination path are the same")?;
|
panic!("source and destination path are the same");
|
||||||
}
|
}
|
||||||
|
|
||||||
let src = read_to_string(source)
|
let src = read_to_string(src_path)
|
||||||
.map_err(|_| "failed to read from source file")?;
|
.expect("failed to read from source file");
|
||||||
|
|
||||||
let mut index = FsIndex::new();
|
let mut index = FsIndex::new();
|
||||||
index.search_dir("fonts");
|
index.search_dir("fonts");
|
||||||
@ -53,8 +46,10 @@ fn run() -> Result<(), Box<dyn Error>> {
|
|||||||
let typesetter = Typesetter::new(loader.clone());
|
let typesetter = Typesetter::new(loader.clone());
|
||||||
let layouts = block_on(typesetter.typeset(&src)).output;
|
let layouts = block_on(typesetter.typeset(&src)).output;
|
||||||
|
|
||||||
let writer = BufWriter::new(File::create(&dest)?);
|
let file = File::create(&dest_path)
|
||||||
pdf::export(&layouts, &loader, writer)?;
|
.expect("failed to create output file");
|
||||||
|
|
||||||
Ok(())
|
let writer = BufWriter::new(file);
|
||||||
|
pdf::export(&layouts, &loader, writer)
|
||||||
|
.expect("failed to export pdf");
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
//! Diagnostics (errors / warnings) in source code.
|
//! Diagnostics for source code.
|
||||||
//!
|
//!
|
||||||
//! There are no fatal errors. The document will always compile and yield a
|
//! There are no fatal errors. The document will always compile and yield a
|
||||||
//! layout. However, this is a best effort process and bad things will still
|
//! layout on a best effort process, generating diagnostics for incorrect
|
||||||
//! generate errors and warnings.
|
//! things.
|
||||||
|
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -1,28 +1,31 @@
|
|||||||
//! Exporting of layouts into _PDF_ documents.
|
//! Exporting into _PDF_ documents.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use tide::{PdfWriter, Rect, Ref, Trailer, Version};
|
use fontdock::FaceId;
|
||||||
use tide::content::Content;
|
use tide::content::Content;
|
||||||
use tide::doc::{Catalog, Page, PageTree, Resource, Text};
|
use tide::doc::{Catalog, Page, PageTree, Resource, Text};
|
||||||
use tide::font::{
|
use tide::font::{
|
||||||
CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags, Type0Font,
|
CIDFont, CIDFontType, CIDSystemInfo, CMap, CMapEncoding, FontDescriptor,
|
||||||
CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord,
|
FontFlags, FontStream, GlyphUnit, Type0Font, WidthRecord,
|
||||||
};
|
};
|
||||||
|
use tide::{PdfWriter, Rect, Ref, Trailer, Version};
|
||||||
use fontdock::FaceId;
|
|
||||||
use ttf_parser::{name_id, GlyphId};
|
use ttf_parser::{name_id, GlyphId};
|
||||||
|
|
||||||
use crate::SharedFontLoader;
|
|
||||||
use crate::layout::{MultiLayout, BoxLayout};
|
|
||||||
use crate::layout::elements::LayoutElement;
|
use crate::layout::elements::LayoutElement;
|
||||||
|
use crate::layout::{BoxLayout, MultiLayout};
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
|
use crate::SharedFontLoader;
|
||||||
|
|
||||||
/// Export a layouted list of boxes. The same font loader as used for
|
/// Export a list of layouts into a _PDF_ document.
|
||||||
/// layouting needs to be passed in here since the layout only contains
|
///
|
||||||
/// indices referencing the loaded faces. The raw PDF ist written into the
|
/// This creates one page per layout. Additionally to the layouts, you need to
|
||||||
/// target writable, returning the number of bytes written.
|
/// pass in the font loader used for typesetting such that the fonts can be
|
||||||
|
/// included in the _PDF_.
|
||||||
|
///
|
||||||
|
/// The raw _PDF_ is written into the `target` writable, returning the number of
|
||||||
|
/// bytes written.
|
||||||
pub fn export<W: Write>(
|
pub fn export<W: Write>(
|
||||||
layout: &MultiLayout,
|
layout: &MultiLayout,
|
||||||
loader: &SharedFontLoader,
|
loader: &SharedFontLoader,
|
||||||
@ -31,22 +34,20 @@ pub fn export<W: Write>(
|
|||||||
PdfExporter::new(layout, loader, target)?.write()
|
PdfExporter::new(layout, loader, target)?.write()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data relevant to the export of one document.
|
|
||||||
struct PdfExporter<'a, W: Write> {
|
struct PdfExporter<'a, W: Write> {
|
||||||
writer: PdfWriter<W>,
|
writer: PdfWriter<W>,
|
||||||
layouts: &'a MultiLayout,
|
layouts: &'a MultiLayout,
|
||||||
loader: &'a SharedFontLoader,
|
loader: &'a SharedFontLoader,
|
||||||
/// Since we cross-reference pages and faces with their IDs already in the
|
/// We need to know exactly which indirect reference id will be used for
|
||||||
/// document catalog, we need to know exactly which ID is used for what from
|
/// which objects up-front to correctly declare the document catalogue, page
|
||||||
/// the beginning. Thus, we compute a range for each category of object and
|
/// tree and so on. These offsets are computed in the beginning and stored
|
||||||
/// stored these here.
|
/// here.
|
||||||
offsets: Offsets,
|
offsets: Offsets,
|
||||||
// Font remapping, see below at `remap_fonts`.
|
// Font remapping, see below at `remap_fonts`.
|
||||||
to_pdf: HashMap<FaceId, usize>,
|
to_pdf: HashMap<FaceId, usize>,
|
||||||
to_fontdock: Vec<FaceId>,
|
to_layout: Vec<FaceId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates which range of PDF IDs will be used for which contents.
|
|
||||||
struct Offsets {
|
struct Offsets {
|
||||||
catalog: Ref,
|
catalog: Ref,
|
||||||
page_tree: Ref,
|
page_tree: Ref,
|
||||||
@ -58,27 +59,24 @@ struct Offsets {
|
|||||||
const NUM_OBJECTS_PER_FONT: u32 = 5;
|
const NUM_OBJECTS_PER_FONT: u32 = 5;
|
||||||
|
|
||||||
impl<'a, W: Write> PdfExporter<'a, W> {
|
impl<'a, W: Write> PdfExporter<'a, W> {
|
||||||
/// Prepare the export. Only once [`ExportProcess::write`] is called the
|
|
||||||
/// writing really happens.
|
|
||||||
fn new(
|
fn new(
|
||||||
layouts: &'a MultiLayout,
|
layouts: &'a MultiLayout,
|
||||||
loader: &'a SharedFontLoader,
|
loader: &'a SharedFontLoader,
|
||||||
target: W,
|
target: W,
|
||||||
) -> io::Result<PdfExporter<'a, W>> {
|
) -> io::Result<Self> {
|
||||||
let (to_pdf, to_fontdock) = remap_fonts(layouts);
|
let (to_pdf, to_fontdock) = remap_fonts(layouts);
|
||||||
let offsets = calculate_offsets(layouts.len(), to_pdf.len());
|
let offsets = calculate_offsets(layouts.len(), to_pdf.len());
|
||||||
|
|
||||||
Ok(PdfExporter {
|
Ok(Self {
|
||||||
writer: PdfWriter::new(target),
|
writer: PdfWriter::new(target),
|
||||||
layouts,
|
layouts,
|
||||||
offsets,
|
offsets,
|
||||||
to_pdf,
|
to_pdf,
|
||||||
to_fontdock,
|
to_layout: to_fontdock,
|
||||||
loader,
|
loader,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write everything (writing entry point).
|
|
||||||
fn write(&mut self) -> io::Result<usize> {
|
fn write(&mut self) -> io::Result<usize> {
|
||||||
self.writer.write_header(Version::new(1, 7))?;
|
self.writer.write_header(Version::new(1, 7))?;
|
||||||
self.write_preface()?;
|
self.write_preface()?;
|
||||||
@ -89,14 +87,13 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
Ok(self.writer.written())
|
Ok(self.writer.written())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the document catalog and page tree.
|
|
||||||
fn write_preface(&mut self) -> io::Result<()> {
|
fn write_preface(&mut self) -> io::Result<()> {
|
||||||
// The document catalog.
|
// The document catalog.
|
||||||
self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?;
|
self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?;
|
||||||
|
|
||||||
// The font resources.
|
// The font resources.
|
||||||
let start = self.offsets.fonts.0;
|
let start = self.offsets.fonts.0;
|
||||||
let fonts = (0 .. self.to_pdf.len() as u32).map(|i| {
|
let fonts = (0..self.to_pdf.len() as u32).map(|i| {
|
||||||
Resource::Font(i + 1, start + (NUM_OBJECTS_PER_FONT * i))
|
Resource::Font(i + 1, start + (NUM_OBJECTS_PER_FONT * i))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -109,11 +106,10 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// The page objects (non-root nodes in the page tree).
|
// The page objects (non-root nodes in the page tree).
|
||||||
let iter = ids(self.offsets.pages)
|
for ((page_id, content_id), page) in ids(self.offsets.pages)
|
||||||
.zip(ids(self.offsets.contents))
|
.zip(ids(self.offsets.contents))
|
||||||
.zip(self.layouts);
|
.zip(self.layouts)
|
||||||
|
{
|
||||||
for ((page_id, content_id), page) in iter {
|
|
||||||
let rect = Rect::new(
|
let rect = Rect::new(
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
@ -132,7 +128,6 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the contents of all pages.
|
|
||||||
fn write_pages(&mut self) -> io::Result<()> {
|
fn write_pages(&mut self) -> io::Result<()> {
|
||||||
for (id, page) in ids(self.offsets.contents).zip(self.layouts) {
|
for (id, page) in ids(self.offsets.contents).zip(self.layouts) {
|
||||||
self.write_page(id, &page)?;
|
self.write_page(id, &page)?;
|
||||||
@ -140,11 +135,11 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the content of a page.
|
|
||||||
fn write_page(&mut self, id: u32, page: &BoxLayout) -> io::Result<()> {
|
fn write_page(&mut self, id: u32, page: &BoxLayout) -> io::Result<()> {
|
||||||
// Moves and face switches are always cached and only flushed once
|
|
||||||
// needed.
|
|
||||||
let mut text = Text::new();
|
let mut text = Text::new();
|
||||||
|
|
||||||
|
// Font switching actions are only written when the face used for
|
||||||
|
// shaped text changes. Hence, we need to remember the active face.
|
||||||
let mut face = FaceId::MAX;
|
let mut face = FaceId::MAX;
|
||||||
let mut size = 0.0;
|
let mut size = 0.0;
|
||||||
|
|
||||||
@ -163,7 +158,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
let x = Length::raw(pos.x).as_pt();
|
let x = Length::raw(pos.x).as_pt();
|
||||||
let y = Length::raw(page.size.y - pos.y - size).as_pt();
|
let y = Length::raw(page.size.y - pos.y - size).as_pt();
|
||||||
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
|
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
|
||||||
text.tj(shaped.encode_glyphs());
|
text.tj(shaped.encode_glyphs_be());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,11 +168,10 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write all the fonts.
|
|
||||||
fn write_fonts(&mut self) -> io::Result<()> {
|
fn write_fonts(&mut self) -> io::Result<()> {
|
||||||
let mut id = self.offsets.fonts.0;
|
let mut id = self.offsets.fonts.0;
|
||||||
|
|
||||||
for &face_id in &self.to_fontdock {
|
for &face_id in &self.to_layout {
|
||||||
let loader = self.loader.borrow();
|
let loader = self.loader.borrow();
|
||||||
let face = loader.get_loaded(face_id);
|
let face = loader.get_loaded(face_id);
|
||||||
|
|
||||||
@ -223,6 +217,23 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
flags.insert(FontFlags::SYMBOLIC);
|
flags.insert(FontFlags::SYMBOLIC);
|
||||||
flags.insert(FontFlags::SMALL_CAP);
|
flags.insert(FontFlags::SMALL_CAP);
|
||||||
|
|
||||||
|
let num_glyphs = face.number_of_glyphs();
|
||||||
|
let widths: Vec<_> = (0..num_glyphs)
|
||||||
|
.map(|g| face.glyph_hor_advance(GlyphId(g)).unwrap_or(0))
|
||||||
|
.map(|w| to_glyph_unit(w as f64))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut mapping = vec![];
|
||||||
|
for subtable in face.character_mapping_subtables() {
|
||||||
|
subtable.codepoints(|n| {
|
||||||
|
if let Some(c) = std::char::from_u32(n) {
|
||||||
|
if let Some(g) = face.glyph_index(c) {
|
||||||
|
mapping.push((g.0, c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Write the base font object referencing the CID font.
|
// Write the base font object referencing the CID font.
|
||||||
self.writer.write_obj(
|
self.writer.write_obj(
|
||||||
id,
|
id,
|
||||||
@ -234,12 +245,6 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
.to_unicode(id + 3),
|
.to_unicode(id + 3),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let num_glyphs = face.number_of_glyphs();
|
|
||||||
let widths: Vec<_> = (0 .. num_glyphs)
|
|
||||||
.map(|g| face.glyph_hor_advance(GlyphId(g)).unwrap_or(0))
|
|
||||||
.map(|w| to_glyph_unit(w as f64))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Write the CID font referencing the font descriptor.
|
// Write the CID font referencing the font descriptor.
|
||||||
self.writer.write_obj(
|
self.writer.write_obj(
|
||||||
id + 1,
|
id + 1,
|
||||||
@ -252,8 +257,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
.widths(vec![WidthRecord::Start(0, widths)]),
|
.widths(vec![WidthRecord::Start(0, widths)]),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Write the font descriptor (contains the global information about
|
// Write the font descriptor (contains metrics about the font).
|
||||||
// the font).
|
|
||||||
self.writer.write_obj(id + 2,
|
self.writer.write_obj(id + 2,
|
||||||
FontDescriptor::new(base_font, flags, italic_angle)
|
FontDescriptor::new(base_font, flags, italic_angle)
|
||||||
.font_bbox(bbox)
|
.font_bbox(bbox)
|
||||||
@ -264,25 +268,15 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
.font_file_2(id + 4)
|
.font_file_2(id + 4)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut mapping = vec![];
|
// Write the CMap, which maps glyph ids back to unicode codepoints
|
||||||
for subtable in face.character_mapping_subtables() {
|
// to enable copying out of the PDF.
|
||||||
subtable.codepoints(|n| {
|
|
||||||
if let Some(c) = std::char::from_u32(n) {
|
|
||||||
if let Some(g) = face.glyph_index(c) {
|
|
||||||
mapping.push((g.0, c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the CMap, which maps glyph ID's to unicode codepoints.
|
|
||||||
self.writer.write_obj(id + 3, &CMap::new(
|
self.writer.write_obj(id + 3, &CMap::new(
|
||||||
"Custom",
|
"Custom",
|
||||||
system_info,
|
system_info,
|
||||||
mapping,
|
mapping,
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
// Finally write the subsetted font bytes.
|
// Write the face's bytes.
|
||||||
self.writer.write_obj(id + 4, &FontStream::new(face.data()))?;
|
self.writer.write_obj(id + 4, &FontStream::new(face.data()))?;
|
||||||
|
|
||||||
id += NUM_OBJECTS_PER_FONT;
|
id += NUM_OBJECTS_PER_FONT;
|
||||||
@ -294,28 +288,28 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
|
|
||||||
/// Assigns a new PDF-internal index to each used face and returns two mappings:
|
/// Assigns a new PDF-internal index to each used face and returns two mappings:
|
||||||
/// - Forwards from the old face ids to the new pdf indices (hash map)
|
/// - Forwards from the old face ids to the new pdf indices (hash map)
|
||||||
/// - Backwards from the pdf indices to the old ids (vec)
|
/// - Backwards from the pdf indices to the old face ids (vec)
|
||||||
fn remap_fonts(layouts: &MultiLayout) -> (HashMap<FaceId, usize>, Vec<FaceId>) {
|
fn remap_fonts(layouts: &MultiLayout) -> (HashMap<FaceId, usize>, Vec<FaceId>) {
|
||||||
let mut to_pdf = HashMap::new();
|
let mut to_pdf = HashMap::new();
|
||||||
let mut to_fontdock = vec![];
|
let mut to_layout = vec![];
|
||||||
|
|
||||||
// We want to find out which fonts are used at all. To do that, look at each
|
// We want to find out which font faces are used at all. To do that, look at
|
||||||
// text element to find out which font is uses.
|
// each text element to find out which face is uses.
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
for (_, element) in &layout.elements.0 {
|
for (_, element) in &layout.elements.0 {
|
||||||
let LayoutElement::Text(shaped) = element;
|
let LayoutElement::Text(shaped) = element;
|
||||||
to_pdf.entry(shaped.face).or_insert_with(|| {
|
to_pdf.entry(shaped.face).or_insert_with(|| {
|
||||||
let next_id = to_fontdock.len();
|
let next_id = to_layout.len();
|
||||||
to_fontdock.push(shaped.face);
|
to_layout.push(shaped.face);
|
||||||
next_id
|
next_id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(to_pdf, to_fontdock)
|
(to_pdf, to_layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We need to know in advance which IDs to use for which objects to
|
/// We need to know in advance which ids to use for which objects to
|
||||||
/// cross-reference them. Therefore, we calculate the indices in the beginning.
|
/// cross-reference them. Therefore, we calculate the indices in the beginning.
|
||||||
fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets {
|
fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets {
|
||||||
let catalog = 1;
|
let catalog = 1;
|
||||||
@ -333,7 +327,6 @@ fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an iterator from a reference pair.
|
fn ids((start, end): (Ref, Ref)) -> impl Iterator<Item = Ref> {
|
||||||
fn ids((start, end): (Ref, Ref)) -> impl Iterator<Item=Ref> {
|
start..=end
|
||||||
start ..= end
|
|
||||||
}
|
}
|
||||||
|
21
src/font.rs
21
src/font.rs
@ -3,14 +3,15 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use fontdock::{ContainsChar, FaceFromVec, FontLoader, FontProvider};
|
||||||
use ttf_parser::Face;
|
use ttf_parser::Face;
|
||||||
use fontdock::{FontLoader, FontProvider, ContainsChar, FaceFromVec};
|
|
||||||
|
|
||||||
/// A referenced-count shared font loader backed by a dynamic provider.
|
/// A referenced-count shared font loader backed by a dynamic provider.
|
||||||
pub type SharedFontLoader = Rc<RefCell<FontLoader<Box<DynProvider>>>>;
|
pub type SharedFontLoader = Rc<RefCell<FontLoader<Box<DynProvider>>>>;
|
||||||
|
|
||||||
/// The dynamic font provider type backing the font loader.
|
/// The dynamic font provider type backing the font loader.
|
||||||
pub type DynProvider = dyn FontProvider<Face=OwnedFace>;
|
pub type DynProvider = dyn FontProvider<Face = OwnedFace>;
|
||||||
|
|
||||||
/// An owned font face.
|
/// An owned font face.
|
||||||
pub struct OwnedFace {
|
pub struct OwnedFace {
|
||||||
@ -18,6 +19,13 @@ pub struct OwnedFace {
|
|||||||
face: Face<'static>,
|
face: Face<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OwnedFace {
|
||||||
|
/// The raw face data.
|
||||||
|
pub fn data(&self) -> &[u8] {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FaceFromVec for OwnedFace {
|
impl FaceFromVec for OwnedFace {
|
||||||
fn from_vec(vec: Vec<u8>, i: u32) -> Option<Self> {
|
fn from_vec(vec: Vec<u8>, i: u32) -> Option<Self> {
|
||||||
// The vec's location is stable in memory since we don't touch it and
|
// The vec's location is stable in memory since we don't touch it and
|
||||||
@ -26,20 +34,13 @@ impl FaceFromVec for OwnedFace {
|
|||||||
std::slice::from_raw_parts(vec.as_ptr(), vec.len())
|
std::slice::from_raw_parts(vec.as_ptr(), vec.len())
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(OwnedFace {
|
Some(Self {
|
||||||
data: vec,
|
data: vec,
|
||||||
face: Face::from_slice(slice, i).ok()?,
|
face: Face::from_slice(slice, i).ok()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OwnedFace {
|
|
||||||
/// The raw face data.
|
|
||||||
pub fn data(&self) -> &[u8] {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContainsChar for OwnedFace {
|
impl ContainsChar for OwnedFace {
|
||||||
fn contains_char(&self, c: char) -> bool {
|
fn contains_char(&self, c: char) -> bool {
|
||||||
self.glyph_index(c).is_some()
|
self.glyph_index(c).is_some()
|
||||||
|
27
src/func.rs
27
src/func.rs
@ -1,20 +1,20 @@
|
|||||||
//! Tools for building custom functions.
|
//! Tools for building custom functions.
|
||||||
|
|
||||||
use crate::Feedback;
|
|
||||||
use crate::syntax::span::{Span, Spanned};
|
|
||||||
use crate::syntax::parsing::{parse, ParseState};
|
|
||||||
use crate::syntax::tree::SyntaxTree;
|
|
||||||
|
|
||||||
/// Useful things for creating functions.
|
/// Useful things for creating functions.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::layout::prelude::*;
|
pub use crate::layout::prelude::*;
|
||||||
pub use crate::layout::Command::{self, *};
|
pub use crate::layout::Command::{self, *};
|
||||||
pub use crate::syntax::prelude::*;
|
|
||||||
pub use crate::style::*;
|
pub use crate::style::*;
|
||||||
pub use super::{OptionExt, parse_maybe_body, expect_no_body};
|
pub use crate::syntax::prelude::*;
|
||||||
|
pub use super::{expect_no_body, parse_maybe_body, OptionExt};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra methods on [`Options`](Option) used for function argument parsing.
|
use crate::syntax::parsing::{parse, ParseState};
|
||||||
|
use crate::syntax::span::{Span, Spanned};
|
||||||
|
use crate::syntax::tree::SyntaxTree;
|
||||||
|
use crate::Feedback;
|
||||||
|
|
||||||
|
/// Extra methods on `Option`s used for function argument parsing.
|
||||||
pub trait OptionExt<T>: Sized {
|
pub trait OptionExt<T>: Sized {
|
||||||
/// Calls `f` with `val` if this is `Some(val)`.
|
/// Calls `f` with `val` if this is `Some(val)`.
|
||||||
fn with(self, f: impl FnOnce(T));
|
fn with(self, f: impl FnOnce(T));
|
||||||
@ -62,8 +62,8 @@ pub fn expect_no_body(body: Option<Spanned<&str>>, f: &mut Feedback) {
|
|||||||
/// Implement a custom function concisely.
|
/// Implement a custom function concisely.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// Look at the source code of the [`library`](crate::library) module for
|
/// Look at the source code of the `library` module for examples on how the
|
||||||
/// examples on how the macro works.
|
/// macro works.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! function {
|
macro_rules! function {
|
||||||
// Entry point.
|
// Entry point.
|
||||||
@ -85,7 +85,7 @@ macro_rules! function {
|
|||||||
|
|
||||||
// Parse trait.
|
// Parse trait.
|
||||||
(@parse($($a:tt)*) parse(default) $($r:tt)*) => {
|
(@parse($($a:tt)*) parse(default) $($r:tt)*) => {
|
||||||
function!(@parse($($a)*) parse(_h, _b, _c, _f, _m) {Default::default() } $($r)*);
|
function!(@parse($($a)*) parse(_h, _b, _c, _f, _m) { Default::default() } $($r)*);
|
||||||
};
|
};
|
||||||
(@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $f:ident) $($r:tt)* ) => {
|
(@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $f:ident) $($r:tt)* ) => {
|
||||||
function!(@parse($($a)*) parse($h, $b, $c, $f, _metadata) $($r)*);
|
function!(@parse($($a)*) parse($h, $b, $c, $f, _metadata) $($r)*);
|
||||||
@ -104,7 +104,10 @@ macro_rules! function {
|
|||||||
#[allow(unused)] mut call: $crate::syntax::parsing::FuncCall,
|
#[allow(unused)] mut call: $crate::syntax::parsing::FuncCall,
|
||||||
#[allow(unused)] $state: &$crate::syntax::parsing::ParseState,
|
#[allow(unused)] $state: &$crate::syntax::parsing::ParseState,
|
||||||
#[allow(unused)] $metadata: Self::Meta,
|
#[allow(unused)] $metadata: Self::Meta,
|
||||||
) -> $crate::Pass<Self> where Self: Sized {
|
) -> $crate::Pass<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
let mut feedback = $crate::Feedback::new();
|
let mut feedback = $crate::Feedback::new();
|
||||||
#[allow(unused)] let $header = &mut call.header;
|
#[allow(unused)] let $header = &mut call.header;
|
||||||
#[allow(unused)] let $body = call.body;
|
#[allow(unused)] let $body = call.body;
|
||||||
|
66
src/geom.rs
66
src/geom.rs
@ -16,32 +16,24 @@ pub struct Value2<T> {
|
|||||||
|
|
||||||
impl<T: Clone> Value2<T> {
|
impl<T: Clone> Value2<T> {
|
||||||
/// Create a new 2D-value from two values.
|
/// Create a new 2D-value from two values.
|
||||||
pub fn new(x: T, y: T) -> Value2<T> { Value2 { x, y } }
|
pub fn new(x: T, y: T) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new 2D-value with `x` set to a value and `y` to default.
|
/// Create a new 2D-value with `x` set to a value and `y` to default.
|
||||||
pub fn with_x(x: T) -> Value2<T> where T: Default {
|
pub fn with_x(x: T) -> Self where T: Default {
|
||||||
Value2 { x, y: T::default() }
|
Self { x, y: T::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new 2D-value with `y` set to a value and `x` to default.
|
/// Create a new 2D-value with `y` set to a value and `x` to default.
|
||||||
pub fn with_y(y: T) -> Value2<T> where T: Default {
|
pub fn with_y(y: T) -> Self where T: Default {
|
||||||
Value2 { x: T::default(), y }
|
Self { x: T::default(), y }
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new 2D-value with the primary axis set to a value and the other
|
|
||||||
/// one to default.
|
|
||||||
pub fn with_primary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
|
|
||||||
Value2::with_x(v).generalized(axes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new 2D-value with the secondary axis set to a value and the
|
|
||||||
/// other one to default.
|
|
||||||
pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
|
|
||||||
Value2::with_y(v).generalized(axes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a 2D-value with `x` and `y` set to the same value `s`.
|
/// Create a 2D-value with `x` and `y` set to the same value `s`.
|
||||||
pub fn with_all(s: T) -> Value2<T> { Value2 { x: s.clone(), y: s } }
|
pub fn with_all(s: T) -> Self {
|
||||||
|
Self { x: s.clone(), y: s }
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the specificed component.
|
/// Get the specificed component.
|
||||||
pub fn get(self, axis: SpecAxis) -> T {
|
pub fn get(self, axis: SpecAxis) -> T {
|
||||||
@ -83,16 +75,16 @@ impl<T: Clone> Value2<T> {
|
|||||||
/// axes, that is:
|
/// axes, that is:
|
||||||
/// - `x` describes the primary axis instead of the horizontal one.
|
/// - `x` describes the primary axis instead of the horizontal one.
|
||||||
/// - `y` describes the secondary axis instead of the vertical one.
|
/// - `y` describes the secondary axis instead of the vertical one.
|
||||||
pub fn generalized(self, axes: LayoutAxes) -> Value2<T> {
|
pub fn generalized(self, axes: LayoutAxes) -> Self {
|
||||||
match axes.primary.axis() {
|
match axes.primary.axis() {
|
||||||
Horizontal => self,
|
Horizontal => self,
|
||||||
Vertical => Value2 { x: self.y, y: self.x },
|
Vertical => Self { x: self.y, y: self.x },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the specialized version of this generalized Size2D (inverse to
|
/// Returns the specialized version of this generalized Size2D (inverse to
|
||||||
/// `generalized`).
|
/// `generalized`).
|
||||||
pub fn specialized(self, axes: LayoutAxes) -> Value2<T> {
|
pub fn specialized(self, axes: LayoutAxes) -> Self {
|
||||||
// In fact, generalized is its own inverse. For reasons of clarity
|
// In fact, generalized is its own inverse. For reasons of clarity
|
||||||
// at the call site, we still have this second function.
|
// at the call site, we still have this second function.
|
||||||
self.generalized(axes)
|
self.generalized(axes)
|
||||||
@ -104,7 +96,7 @@ impl<T: Clone> Value2<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Debug for Value2<T> where T: Debug {
|
impl<T: Debug> Debug for Value2<T> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_list()
|
f.debug_list()
|
||||||
.entry(&self.x)
|
.entry(&self.x)
|
||||||
@ -118,16 +110,16 @@ pub type Size = Value2<f64>;
|
|||||||
|
|
||||||
impl Size {
|
impl Size {
|
||||||
/// The zeroed size.
|
/// The zeroed size.
|
||||||
pub const ZERO: Size = Size { x: 0.0, y: 0.0 };
|
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
|
||||||
|
|
||||||
/// Whether the given size fits into this one, that is, both coordinate
|
/// Whether the given size fits into this one, that is, both coordinate
|
||||||
/// values are smaller or equal.
|
/// values are smaller or equal.
|
||||||
pub fn fits(self, other: Size) -> bool {
|
pub fn fits(self, other: Self) -> bool {
|
||||||
self.x >= other.x && self.y >= other.y
|
self.x >= other.x && self.y >= other.y
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a size padded by the paddings of the given box.
|
/// Return a size padded by the paddings of the given box.
|
||||||
pub fn padded(self, padding: Margins) -> Size {
|
pub fn padded(self, padding: Margins) -> Self {
|
||||||
Size {
|
Size {
|
||||||
x: self.x + padding.left + padding.right,
|
x: self.x + padding.left + padding.right,
|
||||||
y: self.y + padding.top + padding.bottom,
|
y: self.y + padding.top + padding.bottom,
|
||||||
@ -135,7 +127,7 @@ impl Size {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a size reduced by the paddings of the given box.
|
/// Return a size reduced by the paddings of the given box.
|
||||||
pub fn unpadded(self, padding: Margins) -> Size {
|
pub fn unpadded(self, padding: Margins) -> Self {
|
||||||
Size {
|
Size {
|
||||||
x: self.x - padding.left - padding.right,
|
x: self.x - padding.left - padding.right,
|
||||||
y: self.y - padding.top - padding.bottom,
|
y: self.y - padding.top - padding.bottom,
|
||||||
@ -147,7 +139,7 @@ impl Size {
|
|||||||
///
|
///
|
||||||
/// This assumes the size to be generalized such that `x` corresponds to the
|
/// This assumes the size to be generalized such that `x` corresponds to the
|
||||||
/// primary axis.
|
/// primary axis.
|
||||||
pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Size {
|
pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Self {
|
||||||
Size {
|
Size {
|
||||||
x: anchor(self.x, align.primary, axes.primary),
|
x: anchor(self.x, align.primary, axes.primary),
|
||||||
y: anchor(self.y, align.secondary, axes.secondary),
|
y: anchor(self.y, align.secondary, axes.secondary),
|
||||||
@ -189,17 +181,17 @@ pub struct Value4<T> {
|
|||||||
|
|
||||||
impl<T: Clone> Value4<T> {
|
impl<T: Clone> Value4<T> {
|
||||||
/// Create a new box from four sizes.
|
/// Create a new box from four sizes.
|
||||||
pub fn new(left: T, top: T, right: T, bottom: T) -> Value4<T> {
|
pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
|
||||||
Value4 { left, top, right, bottom }
|
Value4 { left, top, right, bottom }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a box with all four fields set to the same value `s`.
|
/// Create a box with all four fields set to the same value `s`.
|
||||||
pub fn with_all(value: T) -> Value4<T> {
|
pub fn with_all(value: T) -> Self {
|
||||||
Value4 {
|
Value4 {
|
||||||
left: value.clone(),
|
left: value.clone(),
|
||||||
top: value.clone(),
|
top: value.clone(),
|
||||||
right: value.clone(),
|
right: value.clone(),
|
||||||
bottom: value
|
bottom: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +205,7 @@ impl<T: Clone> Value4<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match dir {
|
match dir {
|
||||||
LTT => &mut self.left,
|
LTR => &mut self.left,
|
||||||
RTL => &mut self.right,
|
RTL => &mut self.right,
|
||||||
TTB => &mut self.top,
|
TTB => &mut self.top,
|
||||||
BTT => &mut self.bottom,
|
BTT => &mut self.bottom,
|
||||||
@ -224,18 +216,6 @@ impl<T: Clone> Value4<T> {
|
|||||||
pub fn set_all(&mut self, value: T) {
|
pub fn set_all(&mut self, value: T) {
|
||||||
*self = Value4::with_all(value);
|
*self = Value4::with_all(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the `left` and `right` values.
|
|
||||||
pub fn set_horizontal(&mut self, value: T) {
|
|
||||||
self.left = value.clone();
|
|
||||||
self.right = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the `top` and `bottom` values.
|
|
||||||
pub fn set_vertical(&mut self, value: T) {
|
|
||||||
self.top = value.clone();
|
|
||||||
self.bottom = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A length in four dimensions.
|
/// A length in four dimensions.
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
//! The elements layouts are composed of.
|
//! Basic building blocks of layouts.
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use ttf_parser::GlyphId;
|
|
||||||
use fontdock::FaceId;
|
use fontdock::FaceId;
|
||||||
|
use ttf_parser::GlyphId;
|
||||||
|
|
||||||
use crate::geom::Size;
|
use crate::geom::Size;
|
||||||
|
|
||||||
/// A sequence of positioned layout elements.
|
/// A collection of absolutely positioned layout elements.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct LayoutElements(pub Vec<(Size, LayoutElement)>);
|
pub struct LayoutElements(pub Vec<(Size, LayoutElement)>);
|
||||||
|
|
||||||
impl LayoutElements {
|
impl LayoutElements {
|
||||||
/// Create an empty sequence.
|
/// Create an new empty collection.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
LayoutElements(vec![])
|
Self(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an element at a position.
|
/// Add an element at a position.
|
||||||
@ -21,7 +22,9 @@ impl LayoutElements {
|
|||||||
self.0.push((pos, element));
|
self.0.push((pos, element));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a sequence of elements offset by an `offset`.
|
/// Add all elements of another collection, offsetting each by the given
|
||||||
|
/// `offset`. This can be used to place a sublayout at a position in another
|
||||||
|
/// layout.
|
||||||
pub fn extend_offset(&mut self, offset: Size, more: Self) {
|
pub fn extend_offset(&mut self, offset: Size, more: Self) {
|
||||||
for (subpos, element) in more.0 {
|
for (subpos, element) in more.0 {
|
||||||
self.0.push((subpos + offset, element));
|
self.0.push((subpos + offset, element));
|
||||||
@ -29,16 +32,9 @@ impl LayoutElements {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LayoutElements {
|
/// A layout element, the basic building block layouts are composed of.
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A layout element, which is the basic building block layouts are composed of.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum LayoutElement {
|
pub enum LayoutElement {
|
||||||
/// Shaped text.
|
|
||||||
Text(Shaped),
|
Text(Shaped),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,14 +44,17 @@ pub struct Shaped {
|
|||||||
pub text: String,
|
pub text: String,
|
||||||
pub face: FaceId,
|
pub face: FaceId,
|
||||||
pub glyphs: Vec<GlyphId>,
|
pub glyphs: Vec<GlyphId>,
|
||||||
|
/// The horizontal offsets of the glyphs with the same indices. Vertical
|
||||||
|
/// offets are not yet supported.
|
||||||
pub offsets: Vec<f64>,
|
pub offsets: Vec<f64>,
|
||||||
|
/// The font size.
|
||||||
pub size: f64,
|
pub size: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shaped {
|
impl Shaped {
|
||||||
/// Create an empty shape run.
|
/// Create a new shape run with empty `text`, `glyphs` and `offsets`.
|
||||||
pub fn new(face: FaceId, size: f64) -> Shaped {
|
pub fn new(face: FaceId, size: f64) -> Self {
|
||||||
Shaped {
|
Self {
|
||||||
text: String::new(),
|
text: String::new(),
|
||||||
face,
|
face,
|
||||||
glyphs: vec![],
|
glyphs: vec![],
|
||||||
@ -65,12 +64,11 @@ impl Shaped {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Encode the glyph ids into a big-endian byte buffer.
|
/// Encode the glyph ids into a big-endian byte buffer.
|
||||||
pub fn encode_glyphs(&self) -> Vec<u8> {
|
pub fn encode_glyphs_be(&self) -> Vec<u8> {
|
||||||
const BYTES_PER_GLYPH: usize = 2;
|
let mut bytes = Vec::with_capacity(2 * self.glyphs.len());
|
||||||
let mut bytes = Vec::with_capacity(BYTES_PER_GLYPH * self.glyphs.len());
|
for &GlyphId(g) in &self.glyphs {
|
||||||
for g in &self.glyphs {
|
bytes.push((g >> 8) as u8);
|
||||||
bytes.push((g.0 >> 8) as u8);
|
bytes.push((g & 0xff) as u8);
|
||||||
bytes.push((g.0 & 0xff) as u8);
|
|
||||||
}
|
}
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
@ -1,70 +1,67 @@
|
|||||||
//! The line layouter arranges boxes into lines.
|
//! Arranging boxes into lines.
|
||||||
//!
|
//!
|
||||||
//! Along the primary axis, the boxes are laid out next to each other while they
|
//! Along the primary axis, the boxes are laid out next to each other as long as
|
||||||
//! fit into a line. When a line break is necessary, the line is finished and a
|
//! they fit into a line. When necessary, a line break is inserted and the new
|
||||||
//! new line is started offset on the secondary axis by the height of previous
|
//! line is offset along the secondary axis by the height of the previous line
|
||||||
//! line and the extra line spacing.
|
//! plus extra line spacing.
|
||||||
//!
|
//!
|
||||||
//! Internally, the line layouter uses a stack layouter to arrange the finished
|
//! Internally, the line layouter uses a stack layouter to stack the finished
|
||||||
//! lines.
|
//! lines on top of each.
|
||||||
|
|
||||||
use super::stack::{StackLayouter, StackContext};
|
use super::stack::{StackContext, StackLayouter};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Performs the line layouting.
|
/// Performs the line layouting.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LineLayouter {
|
pub struct LineLayouter {
|
||||||
/// The context for layouting.
|
|
||||||
ctx: LineContext,
|
ctx: LineContext,
|
||||||
/// The underlying stack layouter.
|
|
||||||
stack: StackLayouter,
|
stack: StackLayouter,
|
||||||
/// The currently written line.
|
/// The in-progress line.
|
||||||
run: LineRun,
|
run: LineRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for line layouting.
|
/// The context for line layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LineContext {
|
pub struct LineContext {
|
||||||
/// The spaces to layout in.
|
/// The spaces to layout into.
|
||||||
pub spaces: LayoutSpaces,
|
pub spaces: LayoutSpaces,
|
||||||
/// The initial layouting axes, which can be updated by the
|
/// The initial layouting axes, which can be updated through `set_axes`.
|
||||||
/// [`LineLayouter::set_axes`] method.
|
|
||||||
pub axes: LayoutAxes,
|
pub axes: LayoutAxes,
|
||||||
/// Which alignment to set on the resulting layout. This affects how it will
|
/// The alignment of the _resulting_ layout. This does not effect the line
|
||||||
/// be positioned in a parent box.
|
/// layouting itself, but rather how the finished layout will be positioned
|
||||||
|
/// in a parent layout.
|
||||||
pub align: LayoutAlign,
|
pub align: LayoutAlign,
|
||||||
/// Whether to have repeated spaces or to use only the first and only once.
|
/// Whether to spill over into copies of the last space or finish layouting
|
||||||
|
/// when the last space is used up.
|
||||||
pub repeat: bool,
|
pub repeat: bool,
|
||||||
/// The line spacing.
|
/// The spacing to be inserted between each pair of lines.
|
||||||
pub line_spacing: f64,
|
pub line_spacing: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A line run is a sequence of boxes with the same alignment that are arranged
|
/// A sequence of boxes with the same alignment. A real line can consist of
|
||||||
/// in a line. A real line can consist of multiple runs with different
|
/// multiple runs with different alignments.
|
||||||
/// alignments.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct LineRun {
|
struct LineRun {
|
||||||
/// The so-far accumulated layouts in the line.
|
/// The so-far accumulated items of the run.
|
||||||
layouts: Vec<(f64, BoxLayout)>,
|
layouts: Vec<(f64, BoxLayout)>,
|
||||||
/// The width and maximal height of the line.
|
/// The summed width and maximal height of the run.
|
||||||
size: Size,
|
size: Size,
|
||||||
/// The alignment of all layouts in the line.
|
/// The alignment of all layouts in the line.
|
||||||
///
|
///
|
||||||
/// When a new run is created the alignment is yet to be determined. Once a
|
/// When a new run is created the alignment is yet to be determined and
|
||||||
/// layout is added, it is decided which alignment the run has and all
|
/// `None` as such. Once a layout is added, its alignment decides the
|
||||||
/// further elements of the run must have this alignment.
|
/// alignment for the whole run.
|
||||||
align: Option<LayoutAlign>,
|
align: Option<LayoutAlign>,
|
||||||
/// If another line run with different alignment already took up some space
|
/// The amount of space left by another run on the same line or `None` if
|
||||||
/// of the line, this run has less space and how much is stored here.
|
/// this is the only run so far.
|
||||||
usable: Option<f64>,
|
usable: Option<f64>,
|
||||||
/// A possibly cached soft spacing or spacing state.
|
/// The spacing state. This influences how new spacing is handled, e.g. hard
|
||||||
|
/// spacing may override soft spacing.
|
||||||
last_spacing: LastSpacing,
|
last_spacing: LastSpacing,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LineLayouter {
|
impl LineLayouter {
|
||||||
/// Create a new line layouter.
|
/// Create a new line layouter.
|
||||||
pub fn new(ctx: LineContext) -> LineLayouter {
|
pub fn new(ctx: LineContext) -> Self {
|
||||||
LineLayouter {
|
Self {
|
||||||
stack: StackLayouter::new(StackContext {
|
stack: StackLayouter::new(StackContext {
|
||||||
spaces: ctx.spaces.clone(),
|
spaces: ctx.spaces.clone(),
|
||||||
axes: ctx.axes,
|
axes: ctx.axes,
|
||||||
@ -76,14 +73,14 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a layout to the run.
|
/// Add a layout.
|
||||||
pub fn add(&mut self, layout: BoxLayout) {
|
pub fn add(&mut self, layout: BoxLayout) {
|
||||||
let axes = self.ctx.axes;
|
let axes = self.ctx.axes;
|
||||||
|
|
||||||
if let Some(align) = self.run.align {
|
if let Some(align) = self.run.align {
|
||||||
if layout.align.secondary != align.secondary {
|
if layout.align.secondary != align.secondary {
|
||||||
// TODO: Issue warning for non-fitting alignment in
|
// TODO: Issue warning for non-fitting alignment in
|
||||||
// non-repeating context.
|
// non-repeating context.
|
||||||
let fitting = self.stack.is_fitting_alignment(layout.align);
|
let fitting = self.stack.is_fitting_alignment(layout.align);
|
||||||
if !fitting && self.ctx.repeat {
|
if !fitting && self.ctx.repeat {
|
||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
@ -92,7 +89,6 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
} else if layout.align.primary < align.primary {
|
} else if layout.align.primary < align.primary {
|
||||||
self.finish_line();
|
self.finish_line();
|
||||||
|
|
||||||
} else if layout.align.primary > align.primary {
|
} else if layout.align.primary > align.primary {
|
||||||
let mut rest_run = LineRun::new();
|
let mut rest_run = LineRun::new();
|
||||||
|
|
||||||
@ -137,25 +133,24 @@ impl LineLayouter {
|
|||||||
self.run.last_spacing = LastSpacing::None;
|
self.run.last_spacing = LastSpacing::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple layouts to the run.
|
/// Add multiple layouts.
|
||||||
///
|
///
|
||||||
/// This function simply calls `add` repeatedly for each layout.
|
/// This is equivalent to calling `add` repeatedly for each layout.
|
||||||
pub fn add_multiple(&mut self, layouts: MultiLayout) {
|
pub fn add_multiple(&mut self, layouts: MultiLayout) {
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
self.add(layout);
|
self.add(layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining usable size of the run.
|
/// The remaining usable size of the line.
|
||||||
///
|
///
|
||||||
/// This specifies how much more fits before a line break needs to be
|
/// This specifies how much more would fit before a line break would be
|
||||||
/// issued.
|
/// needed.
|
||||||
fn usable(&self) -> Size {
|
fn usable(&self) -> Size {
|
||||||
// The base is the usable space per stack layouter.
|
// The base is the usable space of the stack layouter.
|
||||||
let mut usable = self.stack.usable().generalized(self.ctx.axes);
|
let mut usable = self.stack.usable().generalized(self.ctx.axes);
|
||||||
|
|
||||||
// If this is a alignment-continuing line, we override the primary
|
// If there was another run already, override the stack's size.
|
||||||
// usable size.
|
|
||||||
if let Some(primary) = self.run.usable {
|
if let Some(primary) = self.run.usable {
|
||||||
usable.x = primary;
|
usable.x = primary;
|
||||||
}
|
}
|
||||||
@ -164,18 +159,17 @@ impl LineLayouter {
|
|||||||
usable
|
usable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add spacing along the primary axis to the line.
|
/// Add spacing to the line.
|
||||||
pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
||||||
match kind {
|
match kind {
|
||||||
// A hard space is simply an empty box.
|
|
||||||
SpacingKind::Hard => {
|
SpacingKind::Hard => {
|
||||||
spacing = spacing.min(self.usable().x);
|
spacing = spacing.min(self.usable().x);
|
||||||
self.run.size.x += spacing;
|
self.run.size.x += spacing;
|
||||||
self.run.last_spacing = LastSpacing::Hard;
|
self.run.last_spacing = LastSpacing::Hard;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A soft space is cached if it is not consumed by a hard space or
|
// A soft space is cached since it might be consumed by a hard
|
||||||
// previous soft space with higher level.
|
// spacing.
|
||||||
SpacingKind::Soft(level) => {
|
SpacingKind::Soft(level) => {
|
||||||
let consumes = match self.run.last_spacing {
|
let consumes = match self.run.last_spacing {
|
||||||
LastSpacing::None => true,
|
LastSpacing::None => true,
|
||||||
@ -190,23 +184,23 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the line and add secondary spacing to the underlying stack.
|
/// Finish the line and add spacing to the underlying stack.
|
||||||
pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) {
|
pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) {
|
||||||
self.finish_line_if_not_empty();
|
self.finish_line_if_not_empty();
|
||||||
self.stack.add_spacing(spacing, kind)
|
self.stack.add_spacing(spacing, kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the layouting axes used by this layouter.
|
/// Update the layouting axes.
|
||||||
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||||
self.finish_line_if_not_empty();
|
self.finish_line_if_not_empty();
|
||||||
self.ctx.axes = axes;
|
self.ctx.axes = axes;
|
||||||
self.stack.set_axes(axes)
|
self.stack.set_axes(axes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the layouting spaces to use.
|
/// Update the layouting spaces.
|
||||||
///
|
///
|
||||||
/// If `replace_empty` is true, the current space is replaced if there are
|
/// If `replace_empty` is true, the current space is replaced if there are
|
||||||
/// no boxes laid into it yet. Otherwise, only the followup spaces are
|
/// no boxes laid out into it yet. Otherwise, the followup spaces are
|
||||||
/// replaced.
|
/// replaced.
|
||||||
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
|
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
|
||||||
self.stack.set_spaces(spaces, replace_empty && self.line_is_empty());
|
self.stack.set_spaces(spaces, replace_empty && self.line_is_empty());
|
||||||
@ -217,13 +211,11 @@ impl LineLayouter {
|
|||||||
self.ctx.line_spacing = line_spacing;
|
self.ctx.line_spacing = line_spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining inner layout spaces. Inner means, that padding is already
|
/// The remaining inner spaces. If something is laid out into these spaces,
|
||||||
/// subtracted and the spaces are unexpanding. This can be used to signal
|
/// it will fit into this layouter's underlying stack.
|
||||||
/// a function how much space it has to layout itself.
|
|
||||||
pub fn remaining(&self) -> LayoutSpaces {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
let mut spaces = self.stack.remaining();
|
let mut spaces = self.stack.remaining();
|
||||||
*spaces[0].size.secondary_mut(self.ctx.axes)
|
*spaces[0].size.secondary_mut(self.ctx.axes) -= self.run.size.y;
|
||||||
-= self.run.size.y;
|
|
||||||
spaces
|
spaces
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,13 +224,13 @@ impl LineLayouter {
|
|||||||
self.run.size == Size::ZERO && self.run.layouts.is_empty()
|
self.run.size == Size::ZERO && self.run.layouts.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the last line and compute the final list of boxes.
|
/// Finish everything up and return the final collection of boxes.
|
||||||
pub fn finish(mut self) -> MultiLayout {
|
pub fn finish(mut self) -> MultiLayout {
|
||||||
self.finish_line_if_not_empty();
|
self.finish_line_if_not_empty();
|
||||||
self.stack.finish()
|
self.stack.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the currently active space and start a new one.
|
/// Finish the active space and start a new one.
|
||||||
///
|
///
|
||||||
/// At the top level, this is a page break.
|
/// At the top level, this is a page break.
|
||||||
pub fn finish_space(&mut self, hard: bool) {
|
pub fn finish_space(&mut self, hard: bool) {
|
||||||
@ -246,7 +238,7 @@ impl LineLayouter {
|
|||||||
self.stack.finish_space(hard)
|
self.stack.finish_space(hard)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the line and start a new one.
|
/// Finish the active line and start a new one.
|
||||||
pub fn finish_line(&mut self) {
|
pub fn finish_line(&mut self) {
|
||||||
let mut elements = LayoutElements::new();
|
let mut elements = LayoutElements::new();
|
||||||
|
|
||||||
@ -265,9 +257,8 @@ impl LineLayouter {
|
|||||||
|
|
||||||
self.stack.add(BoxLayout {
|
self.stack.add(BoxLayout {
|
||||||
size: self.run.size.specialized(self.ctx.axes),
|
size: self.run.size.specialized(self.ctx.axes),
|
||||||
align: self.run.align
|
align: self.run.align.unwrap_or(LayoutAlign::new(Start, Start)),
|
||||||
.unwrap_or(LayoutAlign::new(Start, Start)),
|
elements
|
||||||
elements
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.run = LineRun::new();
|
self.run = LineRun::new();
|
||||||
@ -275,7 +266,6 @@ impl LineLayouter {
|
|||||||
self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE);
|
self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the current line if it is not empty.
|
|
||||||
fn finish_line_if_not_empty(&mut self) {
|
fn finish_line_if_not_empty(&mut self) {
|
||||||
if !self.line_is_empty() {
|
if !self.line_is_empty() {
|
||||||
self.finish_line()
|
self.finish_line()
|
||||||
@ -284,8 +274,8 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LineRun {
|
impl LineRun {
|
||||||
fn new() -> LineRun {
|
fn new() -> Self {
|
||||||
LineRun {
|
Self {
|
||||||
layouts: vec![],
|
layouts: vec![],
|
||||||
size: Size::ZERO,
|
size: Size::ZERO,
|
||||||
align: None,
|
align: None,
|
||||||
|
@ -1,37 +1,37 @@
|
|||||||
//! Layouting types and engines.
|
//! Layouting of syntax trees into box layouts.
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use crate::Pass;
|
|
||||||
use crate::font::SharedFontLoader;
|
|
||||||
use crate::geom::{Size, Margins};
|
|
||||||
use crate::style::{LayoutStyle, TextStyle, PageStyle};
|
|
||||||
use crate::syntax::tree::SyntaxTree;
|
|
||||||
|
|
||||||
use elements::LayoutElements;
|
|
||||||
use tree::TreeLayouter;
|
|
||||||
use prelude::*;
|
|
||||||
|
|
||||||
pub mod elements;
|
pub mod elements;
|
||||||
pub mod line;
|
pub mod line;
|
||||||
pub mod primitive;
|
pub mod primitive;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod tree;
|
mod tree;
|
||||||
|
|
||||||
pub use primitive::*;
|
|
||||||
|
|
||||||
/// Basic types used across the layouting engine.
|
/// Basic types used across the layouting engine.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::layout;
|
|
||||||
pub use super::primitive::*;
|
pub use super::primitive::*;
|
||||||
|
pub use super::layout;
|
||||||
pub use Dir::*;
|
pub use Dir::*;
|
||||||
pub use GenAxis::*;
|
|
||||||
pub use SpecAxis::*;
|
|
||||||
pub use GenAlign::*;
|
pub use GenAlign::*;
|
||||||
|
pub use GenAxis::*;
|
||||||
pub use SpecAlign::*;
|
pub use SpecAlign::*;
|
||||||
|
pub use SpecAxis::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use primitive::*;
|
||||||
|
pub use tree::layout_tree as layout;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::font::SharedFontLoader;
|
||||||
|
use crate::geom::{Margins, Size};
|
||||||
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
|
use crate::syntax::tree::SyntaxTree;
|
||||||
|
use crate::Pass;
|
||||||
|
|
||||||
|
use elements::LayoutElements;
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
/// A collection of layouts.
|
/// A collection of layouts.
|
||||||
pub type MultiLayout = Vec<BoxLayout>;
|
pub type MultiLayout = Vec<BoxLayout>;
|
||||||
|
|
||||||
@ -40,47 +40,41 @@ pub type MultiLayout = Vec<BoxLayout>;
|
|||||||
pub struct BoxLayout {
|
pub struct BoxLayout {
|
||||||
/// The size of the box.
|
/// The size of the box.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
/// How to align this layout in a parent container.
|
/// How to align this box in a parent container.
|
||||||
pub align: LayoutAlign,
|
pub align: LayoutAlign,
|
||||||
/// The elements composing this layout.
|
/// The elements composing this layout.
|
||||||
pub elements: LayoutElements,
|
pub elements: LayoutElements,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layouting of elements.
|
/// Comamnd-based layout.
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub trait Layout {
|
pub trait Layout {
|
||||||
/// Layout self into a sequence of layouting commands.
|
/// Create a sequence of layouting commands to execute.
|
||||||
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>>;
|
async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout a syntax tree into a list of boxes.
|
|
||||||
pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_>) -> Pass<MultiLayout> {
|
|
||||||
let mut layouter = TreeLayouter::new(ctx);
|
|
||||||
layouter.layout_tree(tree).await;
|
|
||||||
layouter.finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutContext<'a> {
|
pub struct LayoutContext<'a> {
|
||||||
/// The font loader to retrieve fonts from when typesetting text
|
/// The font loader to query fonts from when typesetting text.
|
||||||
/// using [`layout_text`].
|
|
||||||
pub loader: &'a SharedFontLoader,
|
pub loader: &'a SharedFontLoader,
|
||||||
/// The style for pages and text.
|
/// The style for pages and text.
|
||||||
pub style: &'a LayoutStyle,
|
pub style: &'a LayoutStyle,
|
||||||
/// The base unpadded size of this container (for relative sizing).
|
/// The unpadded size of this container (the base 100% for relative sizes).
|
||||||
pub base: Size,
|
pub base: Size,
|
||||||
/// The spaces to layout in.
|
/// The spaces to layout into.
|
||||||
pub spaces: LayoutSpaces,
|
pub spaces: LayoutSpaces,
|
||||||
/// Whether to have repeated spaces or to use only the first and only once.
|
/// Whether to spill over into copies of the last space or finish layouting
|
||||||
|
/// when the last space is used up.
|
||||||
pub repeat: bool,
|
pub repeat: bool,
|
||||||
/// The initial axes along which content is laid out.
|
/// The axes along which content is laid out.
|
||||||
pub axes: LayoutAxes,
|
pub axes: LayoutAxes,
|
||||||
/// The alignment of the finished layout.
|
/// The alignment of the _resulting_ layout. This does not effect the line
|
||||||
|
/// layouting itself, but rather how the finished layout will be positioned
|
||||||
|
/// in a parent layout.
|
||||||
pub align: LayoutAlign,
|
pub align: LayoutAlign,
|
||||||
/// Whether the layout that is to be created will be nested in a parent
|
/// Whether this layouting process is the root page-building process.
|
||||||
/// container.
|
pub root: bool,
|
||||||
pub nested: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of layout spaces.
|
/// A collection of layout spaces.
|
||||||
@ -89,17 +83,17 @@ pub type LayoutSpaces = Vec<LayoutSpace>;
|
|||||||
/// The space into which content is laid out.
|
/// The space into which content is laid out.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct LayoutSpace {
|
pub struct LayoutSpace {
|
||||||
/// The maximum size of the box to layout in.
|
/// The maximum size of the rectangle to layout into.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
/// Padding that should be respected on each side.
|
/// Padding that should be respected on each side.
|
||||||
pub padding: Margins,
|
pub padding: Margins,
|
||||||
/// Whether to expand the size of the resulting layout to the full size of
|
/// Whether to expand the size of the resulting layout to the full size of
|
||||||
/// this space or to shrink them to fit the content.
|
/// this space or to shrink it to fit the content.
|
||||||
pub expansion: LayoutExpansion,
|
pub expansion: LayoutExpansion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutSpace {
|
impl LayoutSpace {
|
||||||
/// The offset from the origin to the start of content, that is,
|
/// The offset from the origin to the start of content, i.e.
|
||||||
/// `(padding.left, padding.top)`.
|
/// `(padding.left, padding.top)`.
|
||||||
pub fn start(&self) -> Size {
|
pub fn start(&self) -> Size {
|
||||||
Size::new(self.padding.left, self.padding.top)
|
Size::new(self.padding.left, self.padding.top)
|
||||||
@ -110,9 +104,10 @@ impl LayoutSpace {
|
|||||||
self.size.unpadded(self.padding)
|
self.size.unpadded(self.padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layout space without padding and size reduced by the padding.
|
/// The inner layout space with size reduced by the padding, zero padding of
|
||||||
pub fn usable_space(&self) -> LayoutSpace {
|
/// its own and no layout expansion.
|
||||||
LayoutSpace {
|
pub fn inner(&self) -> Self {
|
||||||
|
Self {
|
||||||
size: self.usable(),
|
size: self.usable(),
|
||||||
padding: Margins::ZERO,
|
padding: Margins::ZERO,
|
||||||
expansion: LayoutExpansion::new(false, false),
|
expansion: LayoutExpansion::new(false, false),
|
||||||
@ -123,34 +118,33 @@ impl LayoutSpace {
|
|||||||
/// A sequence of layouting commands.
|
/// A sequence of layouting commands.
|
||||||
pub type Commands<'a> = Vec<Command<'a>>;
|
pub type Commands<'a> = Vec<Command<'a>>;
|
||||||
|
|
||||||
/// Commands issued to the layouting engine by trees.
|
/// Commands executable by the layouting engine.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
/// Layout the given tree in the current context (i.e. not nested). The
|
/// Layout the given tree in the current context (i.e. not nested). The
|
||||||
/// content of the tree is not laid out into a separate box and then added,
|
/// content of the tree is not laid out into a separate box and then added,
|
||||||
/// but simply laid out flat in the active layouting process.
|
/// but simply laid out flatly in the active layouting process.
|
||||||
///
|
///
|
||||||
/// This has the effect that the content fits nicely into the active line
|
/// This has the effect that the content fits nicely into the active line
|
||||||
/// layouting, enabling functions to e.g. change the style of some piece of
|
/// layouting, enabling functions to e.g. change the style of some piece of
|
||||||
/// text while keeping it integrated in the current paragraph.
|
/// text while keeping it part of the current paragraph.
|
||||||
LayoutSyntaxTree(&'a SyntaxTree),
|
LayoutSyntaxTree(&'a SyntaxTree),
|
||||||
|
|
||||||
/// Add a already computed layout.
|
/// Add a finished layout.
|
||||||
Add(BoxLayout),
|
Add(BoxLayout),
|
||||||
/// Add multiple layouts, one after another. This is equivalent to multiple
|
/// Add multiple layouts, one after another. This is equivalent to multiple
|
||||||
/// [Add](Command::Add) commands.
|
/// `Add` commands.
|
||||||
AddMultiple(MultiLayout),
|
AddMultiple(MultiLayout),
|
||||||
|
|
||||||
/// Add spacing of given [kind](super::SpacingKind) along the primary or
|
/// Add spacing of the given kind along the primary or secondary axis. The
|
||||||
/// secondary axis. The spacing kind defines how the spacing interacts with
|
/// kind defines how the spacing interacts with surrounding spacing.
|
||||||
/// surrounding spacing.
|
|
||||||
AddSpacing(f64, SpacingKind, GenAxis),
|
AddSpacing(f64, SpacingKind, GenAxis),
|
||||||
|
|
||||||
/// Start a new line.
|
/// Start a new line.
|
||||||
BreakLine,
|
BreakLine,
|
||||||
/// Start a new paragraph.
|
/// Start a new paragraph.
|
||||||
BreakParagraph,
|
BreakParagraph,
|
||||||
/// Start a new page, which will exist in the finished layout even if it
|
/// Start a new page, which will be part of the finished layout even if it
|
||||||
/// stays empty (since the page break is a _hard_ space break).
|
/// stays empty (since the page break is a _hard_ space break).
|
||||||
BreakPage,
|
BreakPage,
|
||||||
|
|
||||||
@ -162,6 +156,6 @@ pub enum Command<'a> {
|
|||||||
/// Update the alignment for future boxes added to this layouting process.
|
/// Update the alignment for future boxes added to this layouting process.
|
||||||
SetAlignment(LayoutAlign),
|
SetAlignment(LayoutAlign),
|
||||||
/// Update the layouting axes along which future boxes will be laid
|
/// Update the layouting axes along which future boxes will be laid
|
||||||
/// out. This finishes the current line.
|
/// out. This ends the current line.
|
||||||
SetAxes(LayoutAxes),
|
SetAxes(LayoutAxes),
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,27 @@
|
|||||||
//! Layouting primitives.
|
//! Layouting primitives.
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
/// Specifies along which axes content is laid out.
|
/// Specifies the axes along content is laid out.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct LayoutAxes {
|
pub struct LayoutAxes {
|
||||||
/// The primary layouting direction.
|
|
||||||
pub primary: Dir,
|
pub primary: Dir,
|
||||||
/// The secondary layouting direction.
|
|
||||||
pub secondary: Dir,
|
pub secondary: Dir,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutAxes {
|
impl LayoutAxes {
|
||||||
/// Create a new instance from the two values.
|
/// Create a new instance from the two directions.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// This function panics if the axes are aligned, that is, they are
|
/// This function panics if the directions are aligned, i.e. if they are
|
||||||
/// on the same axis.
|
/// on the same axis.
|
||||||
pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes {
|
pub fn new(primary: Dir, secondary: Dir) -> Self {
|
||||||
if primary.axis() == secondary.axis() {
|
if primary.axis() == secondary.axis() {
|
||||||
panic!("invalid aligned axes {} and {}", primary, secondary);
|
panic!("directions {} and {} are aligned", primary, secondary);
|
||||||
}
|
}
|
||||||
|
Self { primary, secondary }
|
||||||
LayoutAxes { primary, secondary }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the direction of the specified generic axis.
|
/// Return the direction of the specified generic axis.
|
||||||
@ -46,9 +44,13 @@ impl LayoutAxes {
|
|||||||
/// Directions along which content is laid out.
|
/// Directions along which content is laid out.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Dir {
|
pub enum Dir {
|
||||||
LTT,
|
/// Left to right.
|
||||||
|
LTR,
|
||||||
|
/// Right to left.
|
||||||
RTL,
|
RTL,
|
||||||
|
/// Top to bottom.
|
||||||
TTB,
|
TTB,
|
||||||
|
/// Bottom to top.
|
||||||
BTT,
|
BTT,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,34 +58,34 @@ impl Dir {
|
|||||||
/// The specific axis this direction belongs to.
|
/// The specific axis this direction belongs to.
|
||||||
pub fn axis(self) -> SpecAxis {
|
pub fn axis(self) -> SpecAxis {
|
||||||
match self {
|
match self {
|
||||||
LTT | RTL => Horizontal,
|
LTR | RTL => Horizontal,
|
||||||
TTB | BTT => Vertical,
|
TTB | BTT => Vertical,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this axis points into the positive coordinate direction.
|
/// Whether this direction points into the positive coordinate direction.
|
||||||
///
|
///
|
||||||
/// The positive axes are left-to-right and top-to-bottom.
|
/// The positive directions are left-to-right and top-to-bottom.
|
||||||
pub fn is_positive(self) -> bool {
|
pub fn is_positive(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
LTT | TTB => true,
|
LTR | TTB => true,
|
||||||
RTL | BTT => false,
|
RTL | BTT => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The factor for this direction.
|
/// The factor for this direction.
|
||||||
///
|
///
|
||||||
/// - `1` if the direction is positive.
|
/// - `1.0` if the direction is positive.
|
||||||
/// - `-1` if the direction is negative.
|
/// - `-1.0` if the direction is negative.
|
||||||
pub fn factor(self) -> f64 {
|
pub fn factor(self) -> f64 {
|
||||||
if self.is_positive() { 1.0 } else { -1.0 }
|
if self.is_positive() { 1.0 } else { -1.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inverse axis.
|
/// The inverse direction.
|
||||||
pub fn inv(self) -> Dir {
|
pub fn inv(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
LTT => RTL,
|
LTR => RTL,
|
||||||
RTL => LTT,
|
RTL => LTR,
|
||||||
TTB => BTT,
|
TTB => BTT,
|
||||||
BTT => TTB,
|
BTT => TTB,
|
||||||
}
|
}
|
||||||
@ -93,7 +95,7 @@ impl Dir {
|
|||||||
impl Display for Dir {
|
impl Display for Dir {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.pad(match self {
|
f.pad(match self {
|
||||||
LTT => "ltr",
|
LTR => "ltr",
|
||||||
RTL => "rtl",
|
RTL => "rtl",
|
||||||
TTB => "ttb",
|
TTB => "ttb",
|
||||||
BTT => "btt",
|
BTT => "btt",
|
||||||
@ -104,9 +106,9 @@ impl Display for Dir {
|
|||||||
/// The two generic layouting axes.
|
/// The two generic layouting axes.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum GenAxis {
|
pub enum GenAxis {
|
||||||
/// The primary axis along which words are laid out.
|
/// The primary layouting direction along which text and lines flow.
|
||||||
Primary,
|
Primary,
|
||||||
/// The secondary axis along which lines and paragraphs are laid out.
|
/// The secondary layouting direction along which paragraphs grow.
|
||||||
Secondary,
|
Secondary,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,19 +156,17 @@ impl Display for SpecAxis {
|
|||||||
/// Specifies where to align a layout in a parent container.
|
/// Specifies where to align a layout in a parent container.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct LayoutAlign {
|
pub struct LayoutAlign {
|
||||||
/// The alignment along the primary axis.
|
|
||||||
pub primary: GenAlign,
|
pub primary: GenAlign,
|
||||||
/// The alignment along the secondary axis.
|
|
||||||
pub secondary: GenAlign,
|
pub secondary: GenAlign,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutAlign {
|
impl LayoutAlign {
|
||||||
/// Create a new instance from the two values.
|
/// Create a new instance from the two alignments.
|
||||||
pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign {
|
pub fn new(primary: GenAlign, secondary: GenAlign) -> Self {
|
||||||
LayoutAlign { primary, secondary }
|
Self { primary, secondary }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the alignment of the specified generic axis.
|
/// Return the alignment for the specified generic axis.
|
||||||
pub fn get(self, axis: GenAxis) -> GenAlign {
|
pub fn get(self, axis: GenAxis) -> GenAlign {
|
||||||
match axis {
|
match axis {
|
||||||
Primary => self.primary,
|
Primary => self.primary,
|
||||||
@ -174,7 +174,7 @@ impl LayoutAlign {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the alignment of the specified generic axis mutably.
|
/// Borrow the alignment for the specified generic axis mutably.
|
||||||
pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
|
pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
|
||||||
match axis {
|
match axis {
|
||||||
Primary => &mut self.primary,
|
Primary => &mut self.primary,
|
||||||
@ -183,7 +183,7 @@ impl LayoutAlign {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where to align content along a generic context.
|
/// Where to align content along an axis in a generic context.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum GenAlign {
|
pub enum GenAlign {
|
||||||
Start,
|
Start,
|
||||||
@ -193,7 +193,7 @@ pub enum GenAlign {
|
|||||||
|
|
||||||
impl GenAlign {
|
impl GenAlign {
|
||||||
/// The inverse alignment.
|
/// The inverse alignment.
|
||||||
pub fn inv(self) -> GenAlign {
|
pub fn inv(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Start => End,
|
Start => End,
|
||||||
Center => Center,
|
Center => Center,
|
||||||
@ -212,7 +212,7 @@ impl Display for GenAlign {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where to align content in a specific context.
|
/// Where to align content along an axis in a specific context.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum SpecAlign {
|
pub enum SpecAlign {
|
||||||
Left,
|
Left,
|
||||||
@ -225,7 +225,7 @@ pub enum SpecAlign {
|
|||||||
impl SpecAlign {
|
impl SpecAlign {
|
||||||
/// The specific axis this alignment refers to.
|
/// The specific axis this alignment refers to.
|
||||||
///
|
///
|
||||||
/// Returns `None` if this is center.
|
/// Returns `None` if this is `Center` since the axis is unknown.
|
||||||
pub fn axis(self) -> Option<SpecAxis> {
|
pub fn axis(self) -> Option<SpecAxis> {
|
||||||
match self {
|
match self {
|
||||||
Self::Left => Some(Horizontal),
|
Self::Left => Some(Horizontal),
|
||||||
@ -236,7 +236,7 @@ impl SpecAlign {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this to a generic alignment.
|
/// The generic version of this alignment in the given system of axes.
|
||||||
pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
|
pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
|
||||||
let get = |spec: SpecAxis, align: GenAlign| {
|
let get = |spec: SpecAxis, align: GenAlign| {
|
||||||
let axis = spec.to_generic(axes);
|
let axis = spec.to_generic(axes);
|
||||||
@ -277,8 +277,8 @@ pub struct LayoutExpansion {
|
|||||||
|
|
||||||
impl LayoutExpansion {
|
impl LayoutExpansion {
|
||||||
/// Create a new instance from the two values.
|
/// Create a new instance from the two values.
|
||||||
pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion {
|
pub fn new(horizontal: bool, vertical: bool) -> Self {
|
||||||
LayoutExpansion { horizontal, vertical }
|
Self { horizontal, vertical }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the expansion value for the given specific axis.
|
/// Return the expansion value for the given specific axis.
|
||||||
@ -298,8 +298,7 @@ impl LayoutExpansion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines how a given spacing interacts with (possibly existing) surrounding
|
/// Defines how spacing interacts with surrounding spacing.
|
||||||
/// spacing.
|
|
||||||
///
|
///
|
||||||
/// There are two options for interaction: Hard and soft spacing. Typically,
|
/// There are two options for interaction: Hard and soft spacing. Typically,
|
||||||
/// hard spacing is used when a fixed amount of space needs to be inserted no
|
/// hard spacing is used when a fixed amount of space needs to be inserted no
|
||||||
@ -317,31 +316,31 @@ pub enum SpacingKind {
|
|||||||
|
|
||||||
impl SpacingKind {
|
impl SpacingKind {
|
||||||
/// The standard spacing kind used for paragraph spacing.
|
/// The standard spacing kind used for paragraph spacing.
|
||||||
pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1);
|
pub const PARAGRAPH: Self = Self::Soft(1);
|
||||||
|
|
||||||
/// The standard spacing kind used for line spacing.
|
/// The standard spacing kind used for line spacing.
|
||||||
pub const LINE: SpacingKind = SpacingKind::Soft(2);
|
pub const LINE: Self = Self::Soft(2);
|
||||||
|
|
||||||
/// The standard spacing kind used for word spacing.
|
/// The standard spacing kind used for word spacing.
|
||||||
pub const WORD: SpacingKind = SpacingKind::Soft(1);
|
pub const WORD: Self = Self::Soft(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The spacing kind of the most recently inserted item in a layouting process.
|
/// The spacing kind of the most recently inserted item in a layouting process.
|
||||||
/// This is not about the last _spacing item_, but the last _item_, which is why
|
///
|
||||||
/// this can be `None`.
|
/// Since the last inserted item may not be spacing at all, this can be `None`.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub(crate) enum LastSpacing {
|
pub(crate) enum LastSpacing {
|
||||||
/// The last item was hard spacing.
|
/// The last item was hard spacing.
|
||||||
Hard,
|
Hard,
|
||||||
/// The last item was soft spacing with the given width and level.
|
/// The last item was soft spacing with the given width and level.
|
||||||
Soft(f64, u32),
|
Soft(f64, u32),
|
||||||
/// The last item was not spacing.
|
/// The last item wasn't spacing.
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LastSpacing {
|
impl LastSpacing {
|
||||||
/// The width of the soft space if this is a soft space or zero otherwise.
|
/// The width of the soft space if this is a soft space or zero otherwise.
|
||||||
pub(crate) fn soft_or_zero(self) -> f64 {
|
pub fn soft_or_zero(self) -> f64 {
|
||||||
match self {
|
match self {
|
||||||
LastSpacing::Soft(space, _) => space,
|
LastSpacing::Soft(space, _) => space,
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
//! The stack layouter arranges boxes along the secondary layouting axis.
|
//! Arranging boxes into a stack along the secondary axis.
|
||||||
//!
|
//!
|
||||||
//! Individual layouts can be aligned at origin / center / end on both axes and
|
//! Individual layouts can be aligned at `Start`, `Center` or `End` along both
|
||||||
//! these alignments are with respect to the growable layout space and not the
|
//! axes. These alignments are with respect to the size of the finished layout
|
||||||
//! total possible size.
|
//! and not the total usable size. This means that a later layout can have
|
||||||
//!
|
//! influence on the position of an earlier one. Consider the following example.
|
||||||
//! This means that a later layout can have influence on the position of an
|
|
||||||
//! earlier one. Consider, for example, the following code:
|
|
||||||
//! ```typst
|
//! ```typst
|
||||||
//! [align: right][A word.]
|
//! [align: right][A word.]
|
||||||
//! [align: left][A sentence with a couple more words.]
|
//! [align: left][A sentence with a couple more words.]
|
||||||
@ -25,38 +23,35 @@ use crate::geom::Value4;
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Performs the stack layouting.
|
/// Performs the stack layouting.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct StackLayouter {
|
pub struct StackLayouter {
|
||||||
/// The context for layouting.
|
|
||||||
ctx: StackContext,
|
ctx: StackContext,
|
||||||
/// The output layouts.
|
|
||||||
layouts: MultiLayout,
|
layouts: MultiLayout,
|
||||||
/// The currently active layout space.
|
/// The in-progress space.
|
||||||
space: Space,
|
space: Space,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for stack layouting.
|
/// The context for stack layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StackContext {
|
pub struct StackContext {
|
||||||
/// The spaces to layout in.
|
/// The spaces to layout into.
|
||||||
pub spaces: LayoutSpaces,
|
pub spaces: LayoutSpaces,
|
||||||
/// The initial layouting axes, which can be updated by the
|
/// The initial layouting axes, which can be updated through `set_axes`.
|
||||||
/// [`StackLayouter::set_axes`] method.
|
|
||||||
pub axes: LayoutAxes,
|
pub axes: LayoutAxes,
|
||||||
/// Which alignment to set on the resulting layout. This affects how it will
|
/// The alignment of the _resulting_ layout. This does not effect the line
|
||||||
/// be positioned in a parent box.
|
/// layouting itself, but rather how the finished layout will be positioned
|
||||||
|
/// in a parent layout.
|
||||||
pub align: LayoutAlign,
|
pub align: LayoutAlign,
|
||||||
/// Whether to have repeated spaces or to use only the first and only once.
|
/// Whether to spill over into copies of the last space or finish layouting
|
||||||
|
/// when the last space is used up.
|
||||||
pub repeat: bool,
|
pub repeat: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layout space composed of subspaces which can have different axes and
|
/// A layout space composed of subspaces which can have different axes and
|
||||||
/// alignments.
|
/// alignments.
|
||||||
#[derive(Debug)]
|
|
||||||
struct Space {
|
struct Space {
|
||||||
/// The index of this space in the list of spaces.
|
/// The index of this space in `ctx.spaces`.
|
||||||
index: usize,
|
index: usize,
|
||||||
/// Whether to add the layout for this space even if it would be empty.
|
/// Whether to include a layout for this space even if it would be empty.
|
||||||
hard: bool,
|
hard: bool,
|
||||||
/// The so-far accumulated layouts.
|
/// The so-far accumulated layouts.
|
||||||
layouts: Vec<(LayoutAxes, BoxLayout)>,
|
layouts: Vec<(LayoutAxes, BoxLayout)>,
|
||||||
@ -66,18 +61,20 @@ struct Space {
|
|||||||
usable: Size,
|
usable: Size,
|
||||||
/// The specialized extra-needed size to affect the size at all.
|
/// The specialized extra-needed size to affect the size at all.
|
||||||
extra: Size,
|
extra: Size,
|
||||||
/// The rulers of a space dictate which alignments for new boxes are still
|
/// Dictate which alignments for new boxes are still allowed and which
|
||||||
/// allowed and which require a new space to be started.
|
/// require a new space to be started. For example, after an `End`-aligned
|
||||||
|
/// item, no `Start`-aligned one can follow.
|
||||||
rulers: Value4<GenAlign>,
|
rulers: Value4<GenAlign>,
|
||||||
/// The last added spacing if the last added thing was spacing.
|
/// The spacing state. This influences how new spacing is handled, e.g. hard
|
||||||
|
/// spacing may override soft spacing.
|
||||||
last_spacing: LastSpacing,
|
last_spacing: LastSpacing,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackLayouter {
|
impl StackLayouter {
|
||||||
/// Create a new stack layouter.
|
/// Create a new stack layouter.
|
||||||
pub fn new(ctx: StackContext) -> StackLayouter {
|
pub fn new(ctx: StackContext) -> Self {
|
||||||
let space = ctx.spaces[0];
|
let space = ctx.spaces[0];
|
||||||
StackLayouter {
|
Self {
|
||||||
ctx,
|
ctx,
|
||||||
layouts: MultiLayout::new(),
|
layouts: MultiLayout::new(),
|
||||||
space: Space::new(0, true, space.usable()),
|
space: Space::new(0, true, space.usable()),
|
||||||
@ -87,8 +84,8 @@ impl StackLayouter {
|
|||||||
/// Add a layout to the stack.
|
/// Add a layout to the stack.
|
||||||
pub fn add(&mut self, layout: BoxLayout) {
|
pub fn add(&mut self, layout: BoxLayout) {
|
||||||
// If the alignment cannot be fitted in this space, finish it.
|
// If the alignment cannot be fitted in this space, finish it.
|
||||||
// TODO: Issue warning for non-fitting alignment in
|
// TODO: Issue warning for non-fitting alignment in non-repeating
|
||||||
// non-repeating context.
|
// context.
|
||||||
if !self.update_rulers(layout.align) && self.ctx.repeat {
|
if !self.update_rulers(layout.align) && self.ctx.repeat {
|
||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
}
|
}
|
||||||
@ -116,14 +113,14 @@ impl StackLayouter {
|
|||||||
|
|
||||||
/// Add multiple layouts to the stack.
|
/// Add multiple layouts to the stack.
|
||||||
///
|
///
|
||||||
/// This function simply calls `add` repeatedly for each layout.
|
/// This is equivalent to calling `add` repeatedly for each layout.
|
||||||
pub fn add_multiple(&mut self, layouts: MultiLayout) {
|
pub fn add_multiple(&mut self, layouts: MultiLayout) {
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
self.add(layout);
|
self.add(layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add secondary spacing to the stack.
|
/// Add spacing to the stack.
|
||||||
pub fn add_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
pub fn add_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
||||||
match kind {
|
match kind {
|
||||||
// A hard space is simply an empty box.
|
// A hard space is simply an empty box.
|
||||||
@ -133,11 +130,14 @@ impl StackLayouter {
|
|||||||
let size = Size::with_y(spacing);
|
let size = Size::with_y(spacing);
|
||||||
|
|
||||||
self.update_metrics(size);
|
self.update_metrics(size);
|
||||||
self.space.layouts.push((self.ctx.axes, BoxLayout {
|
self.space.layouts.push((
|
||||||
size: size.specialized(self.ctx.axes),
|
self.ctx.axes,
|
||||||
align: LayoutAlign::new(Start, Start),
|
BoxLayout {
|
||||||
elements: LayoutElements::new(),
|
size: size.specialized(self.ctx.axes),
|
||||||
}));
|
align: LayoutAlign::new(Start, Start),
|
||||||
|
elements: LayoutElements::new(),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
self.space.last_spacing = LastSpacing::Hard;
|
self.space.last_spacing = LastSpacing::Hard;
|
||||||
}
|
}
|
||||||
@ -158,8 +158,6 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the size metrics to reflect that a layout or spacing with the
|
|
||||||
/// given generalized size has been added.
|
|
||||||
fn update_metrics(&mut self, added: Size) {
|
fn update_metrics(&mut self, added: Size) {
|
||||||
let axes = self.ctx.axes;
|
let axes = self.ctx.axes;
|
||||||
|
|
||||||
@ -177,31 +175,29 @@ impl StackLayouter {
|
|||||||
*self.space.usable.secondary_mut(axes) -= added.y;
|
*self.space.usable.secondary_mut(axes) -= added.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the rulers to account for the new layout. Returns true if a
|
/// Returns true if a space break is necessary.
|
||||||
/// space break is necessary.
|
|
||||||
fn update_rulers(&mut self, align: LayoutAlign) -> bool {
|
fn update_rulers(&mut self, align: LayoutAlign) -> bool {
|
||||||
let allowed = self.is_fitting_alignment(align);
|
let allowed = self.is_fitting_alignment(align);
|
||||||
if allowed {
|
if allowed {
|
||||||
*self.space.rulers.get_mut(self.ctx.axes.secondary, Start)
|
*self.space.rulers.get_mut(self.ctx.axes.secondary, Start) =
|
||||||
= align.secondary;
|
align.secondary;
|
||||||
}
|
}
|
||||||
allowed
|
allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a layout with the given alignment can still be layouted in the
|
/// Whether a layout with the given alignment can still be layouted into the
|
||||||
/// active space.
|
/// active space or a space break is necessary.
|
||||||
pub fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool {
|
pub(crate) fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool {
|
||||||
self.is_fitting_axis(self.ctx.axes.primary, align.primary)
|
self.is_fitting_axis(self.ctx.axes.primary, align.primary)
|
||||||
&& self.is_fitting_axis(self.ctx.axes.secondary, align.secondary)
|
&& self.is_fitting_axis(self.ctx.axes.secondary, align.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the given alignment is still allowed according to the rulers.
|
|
||||||
fn is_fitting_axis(&mut self, dir: Dir, align: GenAlign) -> bool {
|
fn is_fitting_axis(&mut self, dir: Dir, align: GenAlign) -> bool {
|
||||||
align >= *self.space.rulers.get_mut(dir, Start)
|
align >= *self.space.rulers.get_mut(dir, Start)
|
||||||
&& align <= self.space.rulers.get_mut(dir, End).inv()
|
&& align <= self.space.rulers.get_mut(dir, End).inv()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the layouting axes used by this layouter.
|
/// Update the layouting axes.
|
||||||
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||||
// Forget the spacing because it is not relevant anymore.
|
// Forget the spacing because it is not relevant anymore.
|
||||||
if axes.secondary != self.ctx.axes.secondary {
|
if axes.secondary != self.ctx.axes.secondary {
|
||||||
@ -211,10 +207,10 @@ impl StackLayouter {
|
|||||||
self.ctx.axes = axes;
|
self.ctx.axes = axes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the layouting spaces to use.
|
/// Update the layouting spaces.
|
||||||
///
|
///
|
||||||
/// If `replace_empty` is true, the current space is replaced if there are
|
/// If `replace_empty` is true, the current space is replaced if there are
|
||||||
/// no boxes laid into it yet. Otherwise, only the followup spaces are
|
/// no boxes laid out into it yet. Otherwise, the followup spaces are
|
||||||
/// replaced.
|
/// replaced.
|
||||||
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
|
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
|
||||||
if replace_empty && self.space_is_empty() {
|
if replace_empty && self.space_is_empty() {
|
||||||
@ -234,13 +230,13 @@ impl StackLayouter {
|
|||||||
if space.usable().fits(size) {
|
if space.usable().fits(size) {
|
||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
self.start_space(start + index, true);
|
self.start_space(start + index, true);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining unpadded, unexpanding spaces. If a function is laid out
|
/// The remaining inner spaces. If something is laid out into these spaces,
|
||||||
/// into these spaces, it will fit into this stack.
|
/// it will fit into this stack.
|
||||||
pub fn remaining(&self) -> LayoutSpaces {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
let size = self.usable();
|
let size = self.usable();
|
||||||
|
|
||||||
@ -251,7 +247,7 @@ impl StackLayouter {
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
for space in &self.ctx.spaces[self.next_space()..] {
|
for space in &self.ctx.spaces[self.next_space()..] {
|
||||||
spaces.push(space.usable_space());
|
spaces.push(space.inner());
|
||||||
}
|
}
|
||||||
|
|
||||||
spaces
|
spaces
|
||||||
@ -264,17 +260,17 @@ impl StackLayouter {
|
|||||||
.specialized(self.ctx.axes)
|
.specialized(self.ctx.axes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the current layout space (not subspace) is empty.
|
/// Whether the current layout space is empty.
|
||||||
pub fn space_is_empty(&self) -> bool {
|
pub fn space_is_empty(&self) -> bool {
|
||||||
self.space.size == Size::ZERO && self.space.layouts.is_empty()
|
self.space.size == Size::ZERO && self.space.layouts.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the current layout space is the last is the followup list.
|
/// Whether the current layout space is the last in the followup list.
|
||||||
pub fn space_is_last(&self) -> bool {
|
pub fn space_is_last(&self) -> bool {
|
||||||
self.space.index == self.ctx.spaces.len() - 1
|
self.space.index == self.ctx.spaces.len() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the finished list of boxes.
|
/// Finish everything up and return the final collection of boxes.
|
||||||
pub fn finish(mut self) -> MultiLayout {
|
pub fn finish(mut self) -> MultiLayout {
|
||||||
if self.space.hard || !self.space_is_empty() {
|
if self.space.hard || !self.space_is_empty() {
|
||||||
self.finish_space(false);
|
self.finish_space(false);
|
||||||
@ -282,7 +278,7 @@ impl StackLayouter {
|
|||||||
self.layouts
|
self.layouts
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the current space and start a new one.
|
/// Finish active current space and start a new one.
|
||||||
pub fn finish_space(&mut self, hard: bool) {
|
pub fn finish_space(&mut self, hard: bool) {
|
||||||
let space = self.ctx.spaces[self.space.index];
|
let space = self.ctx.spaces[self.space.index];
|
||||||
|
|
||||||
@ -322,8 +318,8 @@ impl StackLayouter {
|
|||||||
// layout uses up space from the origin to the end. Thus, it reduces
|
// layout uses up space from the origin to the end. Thus, it reduces
|
||||||
// the usable space for following layouts at it's origin by its
|
// the usable space for following layouts at it's origin by its
|
||||||
// extent along the secondary axis.
|
// extent along the secondary axis.
|
||||||
*bound.get_mut(axes.secondary, Start)
|
*bound.get_mut(axes.secondary, Start) +=
|
||||||
+= axes.secondary.factor() * layout.size.secondary(*axes);
|
axes.secondary.factor() * layout.size.secondary(*axes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
@ -351,8 +347,8 @@ impl StackLayouter {
|
|||||||
// We reduce the bounding box of this layout at it's end by the
|
// We reduce the bounding box of this layout at it's end by the
|
||||||
// accumulated secondary extent of all layouts we have seen so far,
|
// accumulated secondary extent of all layouts we have seen so far,
|
||||||
// which are the layouts after this one since we iterate reversed.
|
// which are the layouts after this one since we iterate reversed.
|
||||||
*bound.get_mut(axes.secondary, End)
|
*bound.get_mut(axes.secondary, End) -=
|
||||||
-= axes.secondary.factor() * extent.y;
|
axes.secondary.factor() * extent.y;
|
||||||
|
|
||||||
// Then, we add this layout's secondary extent to the accumulator.
|
// Then, we add this layout's secondary extent to the accumulator.
|
||||||
let size = layout.size.generalized(*axes);
|
let size = layout.size.generalized(*axes);
|
||||||
@ -395,21 +391,19 @@ impl StackLayouter {
|
|||||||
self.start_space(self.next_space(), hard)
|
self.start_space(self.next_space(), hard)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a new space with the given index.
|
|
||||||
fn start_space(&mut self, index: usize, hard: bool) {
|
fn start_space(&mut self, index: usize, hard: bool) {
|
||||||
let space = self.ctx.spaces[index];
|
let space = self.ctx.spaces[index];
|
||||||
self.space = Space::new(index, hard, space.usable());
|
self.space = Space::new(index, hard, space.usable());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The index of the next space.
|
|
||||||
fn next_space(&self) -> usize {
|
fn next_space(&self) -> usize {
|
||||||
(self.space.index + 1).min(self.ctx.spaces.len() - 1)
|
(self.space.index + 1).min(self.ctx.spaces.len() - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Space {
|
impl Space {
|
||||||
fn new(index: usize, hard: bool, usable: Size) -> Space {
|
fn new(index: usize, hard: bool, usable: Size) -> Self {
|
||||||
Space {
|
Self {
|
||||||
index,
|
index,
|
||||||
hard,
|
hard,
|
||||||
layouts: vec![],
|
layouts: vec![],
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
//! The text layouter layouts continous pieces of text into boxes.
|
//! Layouting of continous pieces of text into boxes.
|
||||||
//!
|
//!
|
||||||
//! The layouter picks the most suitable font for each individual character.
|
//! The layouter picks the most suitable font for each individual character.
|
||||||
//! When the primary layouting axis horizontally inversed, the word is spelled
|
//! When the primary layouting axis horizontally inversed, the word is spelled
|
||||||
//! backwards. Vertical word layout is not yet supported.
|
//! backwards. Vertical word layout is not yet supported.
|
||||||
|
|
||||||
use ttf_parser::GlyphId;
|
|
||||||
use fontdock::{FaceId, FaceQuery, FontStyle};
|
use fontdock::{FaceId, FaceQuery, FontStyle};
|
||||||
|
use ttf_parser::GlyphId;
|
||||||
|
|
||||||
use crate::font::SharedFontLoader;
|
use crate::font::SharedFontLoader;
|
||||||
use crate::geom::Size;
|
use crate::geom::Size;
|
||||||
use crate::style::TextStyle;
|
use crate::style::TextStyle;
|
||||||
use super::elements::{LayoutElement, Shaped};
|
use super::elements::{LayoutElement, Shaped};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// Layouts text into a box.
|
||||||
|
pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout {
|
||||||
|
TextLayouter::new(text, ctx).layout().await
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs the text layouting.
|
/// Performs the text layouting.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TextLayouter<'a> {
|
struct TextLayouter<'a> {
|
||||||
@ -26,28 +32,24 @@ struct TextLayouter<'a> {
|
|||||||
/// The context for text layouting.
|
/// The context for text layouting.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct TextContext<'a> {
|
pub struct TextContext<'a> {
|
||||||
/// The font loader to retrieve fonts from when typesetting text
|
/// The font loader to retrieve fonts from when typesetting text with
|
||||||
/// using [`layout_text`].
|
/// `layout_text`.
|
||||||
pub loader: &'a SharedFontLoader,
|
pub loader: &'a SharedFontLoader,
|
||||||
/// The style for text: Font selection with classes, weights and variants,
|
/// The style for text: Font selection with classes, weights and variants,
|
||||||
/// font sizes, spacing and so on.
|
/// font sizes, spacing and so on.
|
||||||
pub style: &'a TextStyle,
|
pub style: &'a TextStyle,
|
||||||
/// The axes along which the word is laid out. For now, only
|
/// The direction into which the word is laid out. For now, only horizontal
|
||||||
/// primary-horizontal layouting is supported.
|
/// directions are supported.
|
||||||
pub axes: LayoutAxes,
|
pub dir: Dir,
|
||||||
/// The alignment of the finished layout.
|
/// The alignment of the _resulting_ layout. This does not effect the line
|
||||||
|
/// layouting itself, but rather how the finished layout will be positioned
|
||||||
|
/// in a parent layout.
|
||||||
pub align: LayoutAlign,
|
pub align: LayoutAlign,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layouts text into a box.
|
|
||||||
pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout {
|
|
||||||
TextLayouter::new(text, ctx).layout().await
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TextLayouter<'a> {
|
impl<'a> TextLayouter<'a> {
|
||||||
/// Create a new text layouter.
|
fn new(text: &'a str, ctx: TextContext<'a>) -> Self {
|
||||||
fn new(text: &'a str, ctx: TextContext<'a>) -> TextLayouter<'a> {
|
Self {
|
||||||
TextLayouter {
|
|
||||||
ctx,
|
ctx,
|
||||||
text,
|
text,
|
||||||
shaped: Shaped::new(FaceId::MAX, ctx.style.font_size()),
|
shaped: Shaped::new(FaceId::MAX, ctx.style.font_size()),
|
||||||
@ -57,10 +59,9 @@ impl<'a> TextLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do the layouting.
|
|
||||||
async fn layout(mut self) -> BoxLayout {
|
async fn layout(mut self) -> BoxLayout {
|
||||||
// If the primary axis is negative, we layout the characters reversed.
|
// If the primary axis is negative, we layout the characters reversed.
|
||||||
if self.ctx.axes.primary.is_positive() {
|
if self.ctx.dir.is_positive() {
|
||||||
for c in self.text.chars() {
|
for c in self.text.chars() {
|
||||||
self.layout_char(c).await;
|
self.layout_char(c).await;
|
||||||
}
|
}
|
||||||
@ -83,7 +84,6 @@ impl<'a> TextLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout an individual character.
|
|
||||||
async fn layout_char(&mut self, c: char) {
|
async fn layout_char(&mut self, c: char) {
|
||||||
let (index, glyph, char_width) = match self.select_font(c).await {
|
let (index, glyph, char_width) = match self.select_font(c).await {
|
||||||
Some(selected) => selected,
|
Some(selected) => selected,
|
||||||
@ -115,11 +115,8 @@ impl<'a> TextLayouter<'a> {
|
|||||||
self.width += char_width;
|
self.width += char_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select the best font for a character and return its index along with
|
|
||||||
/// the width of the char in the font.
|
|
||||||
async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> {
|
async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> {
|
||||||
let mut loader = self.ctx.loader.borrow_mut();
|
let mut loader = self.ctx.loader.borrow_mut();
|
||||||
|
|
||||||
let mut variant = self.ctx.style.variant;
|
let mut variant = self.ctx.style.variant;
|
||||||
|
|
||||||
if self.ctx.style.bolder {
|
if self.ctx.style.bolder {
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
//! The tree layouter layouts trees (i.e.
|
//! Layouting of syntax trees.
|
||||||
//! [syntax trees](crate::syntax::SyntaxTree) and [functions](crate::func))
|
|
||||||
//! by executing commands issued by the trees.
|
|
||||||
|
|
||||||
use crate::{Pass, Feedback, DynFuture};
|
|
||||||
use crate::style::LayoutStyle;
|
use crate::style::LayoutStyle;
|
||||||
use crate::syntax::decoration::Decoration;
|
use crate::syntax::decoration::Decoration;
|
||||||
use crate::syntax::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
|
||||||
use crate::syntax::span::{Span, Spanned};
|
use crate::syntax::span::{Span, Spanned};
|
||||||
use super::line::{LineLayouter, LineContext};
|
use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
||||||
|
use crate::{DynFuture, Feedback, Pass};
|
||||||
|
use super::line::{LineContext, LineLayouter};
|
||||||
use super::text::{layout_text, TextContext};
|
use super::text::{layout_text, TextContext};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// Layout a syntax tree into a collection of boxes.
|
||||||
|
pub async fn layout_tree(
|
||||||
|
tree: &SyntaxTree,
|
||||||
|
ctx: LayoutContext<'_>,
|
||||||
|
) -> Pass<MultiLayout> {
|
||||||
|
let mut layouter = TreeLayouter::new(ctx);
|
||||||
|
layouter.layout_tree(tree).await;
|
||||||
|
layouter.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs the tree layouting.
|
/// Performs the tree layouting.
|
||||||
#[derive(Debug)]
|
struct TreeLayouter<'a> {
|
||||||
pub struct TreeLayouter<'a> {
|
|
||||||
ctx: LayoutContext<'a>,
|
ctx: LayoutContext<'a>,
|
||||||
layouter: LineLayouter,
|
layouter: LineLayouter,
|
||||||
style: LayoutStyle,
|
style: LayoutStyle,
|
||||||
@ -21,9 +28,8 @@ pub struct TreeLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TreeLayouter<'a> {
|
impl<'a> TreeLayouter<'a> {
|
||||||
/// Create a new tree layouter.
|
fn new(ctx: LayoutContext<'a>) -> Self {
|
||||||
pub fn new(ctx: LayoutContext<'a>) -> TreeLayouter<'a> {
|
Self {
|
||||||
TreeLayouter {
|
|
||||||
layouter: LineLayouter::new(LineContext {
|
layouter: LineLayouter::new(LineContext {
|
||||||
spaces: ctx.spaces.clone(),
|
spaces: ctx.spaces.clone(),
|
||||||
axes: ctx.axes,
|
axes: ctx.axes,
|
||||||
@ -37,15 +43,17 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a syntax tree by directly processing the nodes instead of using
|
async fn layout_tree(&mut self, tree: &SyntaxTree) {
|
||||||
/// the command based architecture.
|
|
||||||
pub async fn layout_tree(&mut self, tree: &SyntaxTree) {
|
|
||||||
for node in tree {
|
for node in tree {
|
||||||
self.layout_node(node).await;
|
self.layout_node(node).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) {
|
fn finish(self) -> Pass<MultiLayout> {
|
||||||
|
Pass::new(self.layouter.finish(), self.feedback)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) {
|
||||||
let decorate = |this: &mut TreeLayouter, deco| {
|
let decorate = |this: &mut TreeLayouter, deco| {
|
||||||
this.feedback.decorations.push(Spanned::new(deco, node.span));
|
this.feedback.decorations.push(Spanned::new(deco, node.span));
|
||||||
};
|
};
|
||||||
@ -80,7 +88,10 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
SyntaxNode::Raw(lines) => {
|
SyntaxNode::Raw(lines) => {
|
||||||
// TODO: Make this more efficient.
|
// TODO: Make this more efficient.
|
||||||
let fallback = self.style.text.fallback.clone();
|
let fallback = self.style.text.fallback.clone();
|
||||||
self.style.text.fallback.list_mut().insert(0, "monospace".to_string());
|
self.style.text.fallback
|
||||||
|
.list_mut()
|
||||||
|
.insert(0, "monospace".to_string());
|
||||||
|
|
||||||
self.style.text.fallback.flatten();
|
self.style.text.fallback.flatten();
|
||||||
|
|
||||||
// Layout the first line.
|
// Layout the first line.
|
||||||
@ -104,17 +115,15 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a node into this layouting process.
|
async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) {
|
||||||
pub async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) {
|
// Execute the tree's command-generating layout function.
|
||||||
// Execute the tree's layout function which generates the commands.
|
|
||||||
let layouted = dynamic.v.layout(LayoutContext {
|
let layouted = dynamic.v.layout(LayoutContext {
|
||||||
style: &self.style,
|
style: &self.style,
|
||||||
spaces: self.layouter.remaining(),
|
spaces: self.layouter.remaining(),
|
||||||
nested: true,
|
root: true,
|
||||||
.. self.ctx
|
..self.ctx
|
||||||
}).await;
|
}).await;
|
||||||
|
|
||||||
// Add the errors generated by the tree to the error list.
|
|
||||||
self.feedback.extend_offset(layouted.feedback, dynamic.span.start);
|
self.feedback.extend_offset(layouted.feedback, dynamic.span.start);
|
||||||
|
|
||||||
for command in layouted.output {
|
for command in layouted.output {
|
||||||
@ -122,13 +131,6 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the finished list of boxes.
|
|
||||||
pub fn finish(self) -> Pass<MultiLayout> {
|
|
||||||
Pass::new(self.layouter.finish(), self.feedback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a command issued by a tree. When the command is errorful, the
|
|
||||||
/// given span is stored with the error.
|
|
||||||
fn execute_command<'r>(
|
fn execute_command<'r>(
|
||||||
&'r mut self,
|
&'r mut self,
|
||||||
command: Command<'r>,
|
command: Command<'r>,
|
||||||
@ -149,13 +151,13 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
BreakLine => self.layouter.finish_line(),
|
BreakLine => self.layouter.finish_line(),
|
||||||
BreakParagraph => self.layout_paragraph(),
|
BreakParagraph => self.layout_paragraph(),
|
||||||
BreakPage => {
|
BreakPage => {
|
||||||
if self.ctx.nested {
|
if self.ctx.root {
|
||||||
|
self.layouter.finish_space(true)
|
||||||
|
} else {
|
||||||
error!(
|
error!(
|
||||||
@self.feedback, tree_span,
|
@self.feedback, tree_span,
|
||||||
"page break cannot be issued from nested context",
|
"page break cannot only be issued from root context",
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
self.layouter.finish_space(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,12 +166,7 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
self.style.text = style;
|
self.style.text = style;
|
||||||
}
|
}
|
||||||
SetPageStyle(style) => {
|
SetPageStyle(style) => {
|
||||||
if self.ctx.nested {
|
if self.ctx.root {
|
||||||
error!(
|
|
||||||
@self.feedback, tree_span,
|
|
||||||
"page style cannot be changed from nested context",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.style.page = style;
|
self.style.page = style;
|
||||||
|
|
||||||
// The line layouter has no idea of page styles and thus we
|
// The line layouter has no idea of page styles and thus we
|
||||||
@ -184,6 +181,11 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
expansion: LayoutExpansion::new(true, true),
|
expansion: LayoutExpansion::new(true, true),
|
||||||
}
|
}
|
||||||
], true);
|
], true);
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
@self.feedback, tree_span,
|
||||||
|
"page style cannot only be changed from root context",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,17 +197,20 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}) }
|
}) }
|
||||||
|
|
||||||
/// Layout a continous piece of text and add it to the line layouter.
|
|
||||||
async fn layout_text(&mut self, text: &str) {
|
async fn layout_text(&mut self, text: &str) {
|
||||||
self.layouter.add(layout_text(text, TextContext {
|
self.layouter.add(
|
||||||
loader: &self.ctx.loader,
|
layout_text(
|
||||||
style: &self.style.text,
|
text,
|
||||||
axes: self.ctx.axes,
|
TextContext {
|
||||||
align: self.ctx.align,
|
loader: &self.ctx.loader,
|
||||||
}).await)
|
style: &self.style.text,
|
||||||
|
dir: self.ctx.axes.primary,
|
||||||
|
align: self.ctx.align,
|
||||||
|
}
|
||||||
|
).await
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the spacing for a syntactic space node.
|
|
||||||
fn layout_space(&mut self) {
|
fn layout_space(&mut self) {
|
||||||
self.layouter.add_primary_spacing(
|
self.layouter.add_primary_spacing(
|
||||||
self.style.text.word_spacing(),
|
self.style.text.word_spacing(),
|
||||||
@ -213,7 +218,6 @@ impl<'a> TreeLayouter<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the paragraph and add paragraph spacing.
|
|
||||||
fn layout_paragraph(&mut self) {
|
fn layout_paragraph(&mut self) {
|
||||||
self.layouter.add_secondary_spacing(
|
self.layouter.add_secondary_spacing(
|
||||||
self.style.text.paragraph_spacing(),
|
self.style.text.paragraph_spacing(),
|
||||||
|
@ -84,7 +84,7 @@ impl Length {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this to a length with a different unit.
|
/// Convert this to a length with a different unit.
|
||||||
pub fn with_unit(self, unit: Unit) -> Length {
|
pub fn with_unit(self, unit: Unit) -> Self {
|
||||||
Self {
|
Self {
|
||||||
val: self.val * self.unit.raw_scale() / unit.raw_scale(),
|
val: self.val * self.unit.raw_scale() / unit.raw_scale(),
|
||||||
unit,
|
unit,
|
||||||
@ -160,7 +160,7 @@ impl FromStr for Length {
|
|||||||
|
|
||||||
src[..split]
|
src[..split]
|
||||||
.parse::<f64>()
|
.parse::<f64>()
|
||||||
.map(|val| Length::new(val, unit))
|
.map(|val| Self::new(val, unit))
|
||||||
.map_err(|_| ParseLengthError)
|
.map_err(|_| ParseLengthError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
95
src/lib.rs
95
src/lib.rs
@ -2,18 +2,40 @@
|
|||||||
//!
|
//!
|
||||||
//! # Steps
|
//! # Steps
|
||||||
//! - **Parsing:** The parsing step first transforms a plain string into an
|
//! - **Parsing:** The parsing step first transforms a plain string into an
|
||||||
//! [iterator of tokens](crate::syntax::Tokens). Then, a parser constructs a
|
//! [iterator of tokens][tokens]. Then, a parser constructs a syntax tree from
|
||||||
//! syntax tree from the token stream. The structures describing the tree can
|
//! the token stream. The structures describing the tree can be found in the
|
||||||
//! be found in the [syntax](crate::syntax) module.
|
//! [syntax] module.
|
||||||
//! - **Layouting:** The next step is to transform the syntax tree into a
|
//! - **Layouting:** The next step is to transform the syntax tree into a
|
||||||
//! portable representation of the typesetted document. Types for these can be
|
//! portable representation of the typesetted document. Types for these can be
|
||||||
//! found in the [layout](crate::layout) module. A finished layout reading for
|
//! found in the [layout] module. A finished layout ready for exporting is a
|
||||||
//! exporting is a [MultiLayout](crate::layout::MultiLayout) consisting of
|
//! [`MultiLayout`] consisting of multiple boxes (or pages).
|
||||||
//! multiple boxes (or pages).
|
|
||||||
//! - **Exporting:** The finished layout can then be exported into a supported
|
//! - **Exporting:** The finished layout can then be exported into a supported
|
||||||
//! format. Submodules for these formats are located in the
|
//! format. Submodules for these formats are located in the [export] module.
|
||||||
//! [export](crate::export) module. Currently, the only supported output
|
//! Currently, the only supported output format is [_PDF_].
|
||||||
//! format is [_PDF_](crate::export::pdf).
|
//!
|
||||||
|
//! [tokens]: syntax/tokens/struct.Tokens.html
|
||||||
|
//! [syntax]: syntax/index.html
|
||||||
|
//! [layout]: layout/index.html
|
||||||
|
//! [export]: export/index.html
|
||||||
|
//! [_PDF_]: export/pdf/index.html
|
||||||
|
//! [`MultiLayout`]: layout/type.MultiLayout.html
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod diagnostic;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod func;
|
||||||
|
|
||||||
|
pub mod export;
|
||||||
|
pub mod font;
|
||||||
|
pub mod geom;
|
||||||
|
pub mod layout;
|
||||||
|
pub mod length;
|
||||||
|
pub mod library;
|
||||||
|
pub mod paper;
|
||||||
|
pub mod style;
|
||||||
|
pub mod syntax;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@ -24,26 +46,9 @@ use crate::font::SharedFontLoader;
|
|||||||
use crate::layout::MultiLayout;
|
use crate::layout::MultiLayout;
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
use crate::syntax::decoration::Decorations;
|
use crate::syntax::decoration::Decorations;
|
||||||
use crate::syntax::tree::SyntaxTree;
|
|
||||||
use crate::syntax::parsing::{parse, ParseState};
|
use crate::syntax::parsing::{parse, ParseState};
|
||||||
use crate::syntax::scope::Scope;
|
|
||||||
use crate::syntax::span::{Offset, Pos};
|
use crate::syntax::span::{Offset, Pos};
|
||||||
|
use crate::syntax::tree::SyntaxTree;
|
||||||
#[macro_use]
|
|
||||||
mod macros;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod diagnostic;
|
|
||||||
pub mod export;
|
|
||||||
pub mod font;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod func;
|
|
||||||
pub mod geom;
|
|
||||||
pub mod layout;
|
|
||||||
pub mod library;
|
|
||||||
pub mod length;
|
|
||||||
pub mod paper;
|
|
||||||
pub mod style;
|
|
||||||
pub mod syntax;
|
|
||||||
|
|
||||||
/// Transforms source code into typesetted layouts.
|
/// Transforms source code into typesetted layouts.
|
||||||
///
|
///
|
||||||
@ -59,11 +64,11 @@ pub struct Typesetter {
|
|||||||
|
|
||||||
impl Typesetter {
|
impl Typesetter {
|
||||||
/// Create a new typesetter.
|
/// Create a new typesetter.
|
||||||
pub fn new(loader: SharedFontLoader) -> Typesetter {
|
pub fn new(loader: SharedFontLoader) -> Self {
|
||||||
Typesetter {
|
Self {
|
||||||
loader,
|
loader,
|
||||||
style: LayoutStyle::default(),
|
style: LayoutStyle::default(),
|
||||||
parse_state: ParseState { scope: Scope::with_std() },
|
parse_state: ParseState { scope: crate::library::std() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,9 +105,9 @@ impl Typesetter {
|
|||||||
expansion: LayoutExpansion::new(true, true),
|
expansion: LayoutExpansion::new(true, true),
|
||||||
}],
|
}],
|
||||||
repeat: true,
|
repeat: true,
|
||||||
axes: LayoutAxes::new(LTT, TTB),
|
axes: LayoutAxes::new(LTR, TTB),
|
||||||
align: LayoutAlign::new(Start, Start),
|
align: LayoutAlign::new(Start, Start),
|
||||||
nested: false,
|
root: true,
|
||||||
},
|
},
|
||||||
).await
|
).await
|
||||||
}
|
}
|
||||||
@ -119,7 +124,7 @@ impl Typesetter {
|
|||||||
/// A dynamic future type which allows recursive invocation of async functions
|
/// A dynamic future type which allows recursive invocation of async functions
|
||||||
/// when used as the return type. This is also how the async trait functions
|
/// when used as the return type. This is also how the async trait functions
|
||||||
/// work internally.
|
/// work internally.
|
||||||
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
|
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
|
||||||
|
|
||||||
/// The result of some pass: Some output `T` and feedback data.
|
/// The result of some pass: Some output `T` and feedback data.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
@ -132,12 +137,12 @@ pub struct Pass<T> {
|
|||||||
|
|
||||||
impl<T> Pass<T> {
|
impl<T> Pass<T> {
|
||||||
/// Create a new pass from output and feedback data.
|
/// Create a new pass from output and feedback data.
|
||||||
pub fn new(output: T, feedback: Feedback) -> Pass<T> {
|
pub fn new(output: T, feedback: Feedback) -> Self {
|
||||||
Pass { output, feedback }
|
Self { output, feedback }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map the output type and keep the feedback data.
|
/// Map the output type and keep the feedback data.
|
||||||
pub fn map<F, U>(self, f: F) -> Pass<U> where F: FnOnce(T) -> U {
|
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Pass<U> {
|
||||||
Pass {
|
Pass {
|
||||||
output: f(self.output),
|
output: f(self.output),
|
||||||
feedback: self.feedback,
|
feedback: self.feedback,
|
||||||
@ -145,10 +150,10 @@ impl<T> Pass<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User feedback data accumulated during a compilation pass.
|
/// Diagnostic and semantic syntax highlighting data.
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||||
pub struct Feedback {
|
pub struct Feedback {
|
||||||
/// Diagnostics in the source code.
|
/// Diagnostics about the source code.
|
||||||
pub diagnostics: Diagnostics,
|
pub diagnostics: Diagnostics,
|
||||||
/// Decorations of the source code for semantic syntax highlighting.
|
/// Decorations of the source code for semantic syntax highlighting.
|
||||||
pub decorations: Decorations,
|
pub decorations: Decorations,
|
||||||
@ -156,28 +161,28 @@ pub struct Feedback {
|
|||||||
|
|
||||||
impl Feedback {
|
impl Feedback {
|
||||||
/// Create a new feedback instance without errors and decos.
|
/// Create a new feedback instance without errors and decos.
|
||||||
pub fn new() -> Feedback {
|
pub fn new() -> Self {
|
||||||
Feedback {
|
Self {
|
||||||
diagnostics: vec![],
|
diagnostics: vec![],
|
||||||
decorations: vec![],
|
decorations: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Merged two feedbacks into one.
|
/// Merged two feedbacks into one.
|
||||||
pub fn merge(mut a: Feedback, b: Feedback) -> Feedback {
|
pub fn merge(mut a: Self, b: Self) -> Self {
|
||||||
a.extend(b);
|
a.extend(b);
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add other feedback data to this feedback.
|
/// Add other feedback data to this feedback.
|
||||||
pub fn extend(&mut self, other: Feedback) {
|
pub fn extend(&mut self, more: Self) {
|
||||||
self.diagnostics.extend(other.diagnostics);
|
self.diagnostics.extend(more.diagnostics);
|
||||||
self.decorations.extend(other.decorations);
|
self.decorations.extend(more.decorations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add more feedback whose spans are local and need to be offset by an
|
/// Add more feedback whose spans are local and need to be offset by an
|
||||||
/// `offset` to be correct in this feedback's context.
|
/// `offset` to be correct in this feedback's context.
|
||||||
pub fn extend_offset(&mut self, more: Feedback, offset: Pos) {
|
pub fn extend_offset(&mut self, more: Self, offset: Pos) {
|
||||||
self.diagnostics.extend(more.diagnostics.offset(offset));
|
self.diagnostics.extend(more.diagnostics.offset(offset));
|
||||||
self.decorations.extend(more.decorations.offset(offset));
|
self.decorations.extend(more.decorations.offset(offset));
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
//! Font configuration.
|
|
||||||
|
|
||||||
use fontdock::{FontStyle, FontWeight, FontWidth};
|
use fontdock::{FontStyle, FontWeight, FontWidth};
|
||||||
|
|
||||||
use crate::length::ScaleLength;
|
use crate::length::ScaleLength;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -19,7 +18,6 @@ function! {
|
|||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
let size = header.args.pos.get::<ScaleLength>();
|
let size = header.args.pos.get::<ScaleLength>();
|
||||||
|
|
||||||
let style = header.args.key.get::<FontStyle>("style", f);
|
let style = header.args.key.get::<FontStyle>("style", f);
|
||||||
let weight = header.args.key.get::<FontWeight>("weight", f);
|
let weight = header.args.key.get::<FontWeight>("weight", f);
|
||||||
let width = header.args.key.get::<FontWidth>("width", f);
|
let width = header.args.key.get::<FontWidth>("width", f);
|
||||||
@ -40,7 +38,7 @@ function! {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
FontFunc {
|
Self {
|
||||||
body: parse_maybe_body(body, state, f),
|
body: parse_maybe_body(body, state, f),
|
||||||
size,
|
size,
|
||||||
style,
|
style,
|
||||||
@ -52,30 +50,39 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
styled(&self.body, ctx, Some(()), |t, _| {
|
let mut text = ctx.style.text.clone();
|
||||||
self.size.with(|s| match s {
|
|
||||||
ScaleLength::Absolute(length) => {
|
|
||||||
t.base_font_size = length.as_raw();
|
|
||||||
t.font_scale = 1.0;
|
|
||||||
}
|
|
||||||
ScaleLength::Scaled(scale) => t.font_scale = scale,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.style.with(|s| t.variant.style = s);
|
self.size.with(|s| match s {
|
||||||
self.weight.with(|w| t.variant.weight = w);
|
ScaleLength::Absolute(length) => {
|
||||||
self.width.with(|w| t.variant.width = w);
|
text.base_font_size = length.as_raw();
|
||||||
|
text.font_scale = 1.0;
|
||||||
if !self.list.is_empty() {
|
|
||||||
*t.fallback.list_mut() = self.list.iter()
|
|
||||||
.map(|s| s.to_lowercase())
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
|
ScaleLength::Scaled(scale) => text.font_scale = scale,
|
||||||
|
});
|
||||||
|
|
||||||
for (class, fallback) in &self.classes {
|
self.style.with(|s| text.variant.style = s);
|
||||||
t.fallback.set_class_list(class.clone(), fallback.clone());
|
self.weight.with(|w| text.variant.weight = w);
|
||||||
}
|
self.width.with(|w| text.variant.width = w);
|
||||||
|
|
||||||
t.fallback.flatten();
|
if !self.list.is_empty() {
|
||||||
})
|
*text.fallback.list_mut() = self.list.iter()
|
||||||
|
.map(|s| s.to_lowercase())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (class, fallback) in &self.classes {
|
||||||
|
text.fallback.set_class_list(class.clone(), fallback.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
text.fallback.flatten();
|
||||||
|
|
||||||
|
match &self.body {
|
||||||
|
Some(tree) => vec![
|
||||||
|
SetTextStyle(text),
|
||||||
|
LayoutSyntaxTree(tree),
|
||||||
|
SetTextStyle(ctx.style.text.clone()),
|
||||||
|
],
|
||||||
|
None => vec![SetTextStyle(text)],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
//! Layout building blocks.
|
|
||||||
|
|
||||||
use crate::length::ScaleLength;
|
use crate::length::ScaleLength;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -13,7 +11,7 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
BoxFunc {
|
Self {
|
||||||
body: parse_maybe_body(body, state, f).unwrap_or(SyntaxTree::new()),
|
body: parse_maybe_body(body, state, f).unwrap_or(SyntaxTree::new()),
|
||||||
width: header.args.key.get::<ScaleLength>("width", f),
|
width: header.args.key.get::<ScaleLength>("width", f),
|
||||||
height: header.args.key.get::<ScaleLength>("height", f),
|
height: header.args.key.get::<ScaleLength>("height", f),
|
||||||
@ -21,8 +19,8 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
ctx.repeat = false;
|
|
||||||
ctx.spaces.truncate(1);
|
ctx.spaces.truncate(1);
|
||||||
|
ctx.repeat = false;
|
||||||
|
|
||||||
self.width.with(|v| {
|
self.width.with(|v| {
|
||||||
let length = v.raw_scaled(ctx.base.x);
|
let length = v.raw_scaled(ctx.base.x);
|
||||||
@ -51,13 +49,13 @@ function! {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct AlignFunc {
|
pub struct AlignFunc {
|
||||||
body: Option<SyntaxTree>,
|
body: Option<SyntaxTree>,
|
||||||
aligns: Vec<Spanned<SpecAlign>>,
|
aligns: SpanVec<SpecAlign>,
|
||||||
h: Option<Spanned<SpecAlign>>,
|
h: Option<Spanned<SpecAlign>>,
|
||||||
v: Option<Spanned<SpecAlign>>,
|
v: Option<Spanned<SpecAlign>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
AlignFunc {
|
Self {
|
||||||
body: parse_maybe_body(body, state, f),
|
body: parse_maybe_body(body, state, f),
|
||||||
aligns: header.args.pos.all::<Spanned<SpecAlign>>().collect(),
|
aligns: header.args.pos.all::<Spanned<SpecAlign>>().collect(),
|
||||||
h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f),
|
h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f),
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
//! The standard library.
|
//! The standard library.
|
||||||
|
|
||||||
|
mod font;
|
||||||
|
mod layout;
|
||||||
|
mod page;
|
||||||
|
mod spacing;
|
||||||
|
|
||||||
|
pub use font::*;
|
||||||
|
pub use layout::*;
|
||||||
|
pub use page::*;
|
||||||
|
pub use spacing::*;
|
||||||
|
|
||||||
use crate::func::prelude::*;
|
use crate::func::prelude::*;
|
||||||
use crate::layout::{LayoutContext, Commands};
|
|
||||||
use crate::syntax::scope::Scope;
|
use crate::syntax::scope::Scope;
|
||||||
|
|
||||||
macro_rules! lib { ($name:ident) => { mod $name; pub use $name::*; }}
|
/// Create a scope with all standard library functions.
|
||||||
lib!(font);
|
|
||||||
lib!(layout);
|
|
||||||
lib!(page);
|
|
||||||
lib!(spacing);
|
|
||||||
|
|
||||||
/// Create a scope with all standard functions.
|
|
||||||
pub fn std() -> Scope {
|
pub fn std() -> Scope {
|
||||||
let mut std = Scope::new::<ValFunc>();
|
let mut std = Scope::new::<ValFunc>();
|
||||||
|
|
||||||
@ -28,7 +31,10 @@ pub fn std() -> Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function! {
|
function! {
|
||||||
/// `val`: Layouts the body with no special effect.
|
/// `val`: Ignores all arguments and layouts the body flatly.
|
||||||
|
///
|
||||||
|
/// This is also the fallback function, which is used when a function name
|
||||||
|
/// could not be resolved.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ValFunc {
|
pub struct ValFunc {
|
||||||
body: Option<SyntaxTree>,
|
body: Option<SyntaxTree>,
|
||||||
@ -37,7 +43,7 @@ function! {
|
|||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
header.args.pos.0.clear();
|
header.args.pos.0.clear();
|
||||||
header.args.key.0.clear();
|
header.args.key.0.clear();
|
||||||
ValFunc { body: parse_maybe_body(body, state, f), }
|
Self { body: parse_maybe_body(body, state, f), }
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
@ -47,27 +53,3 @@ function! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout an optional body with a change of the text style.
|
|
||||||
fn styled<'a, T, F>(
|
|
||||||
body: &'a Option<SyntaxTree>,
|
|
||||||
ctx: LayoutContext<'_>,
|
|
||||||
data: Option<T>,
|
|
||||||
f: F,
|
|
||||||
) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) {
|
|
||||||
if let Some(data) = data {
|
|
||||||
let mut style = ctx.style.text.clone();
|
|
||||||
f(&mut style, data);
|
|
||||||
|
|
||||||
match body {
|
|
||||||
Some(tree) => vec![
|
|
||||||
SetTextStyle(style),
|
|
||||||
LayoutSyntaxTree(tree),
|
|
||||||
SetTextStyle(ctx.style.text.clone()),
|
|
||||||
],
|
|
||||||
None => vec![SetTextStyle(style)],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
//! Page setup.
|
|
||||||
|
|
||||||
use crate::length::{Length, ScaleLength};
|
use crate::length::{Length, ScaleLength};
|
||||||
use crate::paper::{Paper, PaperClass};
|
use crate::paper::{Paper, PaperClass};
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -21,7 +19,7 @@ function! {
|
|||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
expect_no_body(body, f);
|
expect_no_body(body, f);
|
||||||
PageFunc {
|
Self {
|
||||||
paper: header.args.pos.get::<Paper>(),
|
paper: header.args.pos.get::<Paper>(),
|
||||||
width: header.args.key.get::<Length>("width", f),
|
width: header.args.key.get::<Length>("width", f),
|
||||||
height: header.args.key.get::<Length>("height", f),
|
height: header.args.key.get::<Length>("height", f),
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
//! Spacing.
|
|
||||||
|
|
||||||
use crate::length::ScaleLength;
|
|
||||||
use crate::layout::SpacingKind;
|
use crate::layout::SpacingKind;
|
||||||
|
use crate::length::ScaleLength;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
function! {
|
function! {
|
||||||
/// `parbreak`: Ends the current paragraph.
|
/// `parbreak`: Ends the current paragraph.
|
||||||
///
|
///
|
||||||
/// self has the same effect as two subsequent newlines.
|
/// This has the same effect as two subsequent newlines.
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct ParBreakFunc;
|
pub struct ParBreakFunc;
|
||||||
|
|
||||||
@ -25,7 +23,7 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function! {
|
function! {
|
||||||
/// `h` and `v`: Add spacing along an axis.
|
/// `h` and `v`: Add horizontal or vertical spacing.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct SpacingFunc {
|
pub struct SpacingFunc {
|
||||||
spacing: Option<(SpecAxis, ScaleLength)>,
|
spacing: Option<(SpecAxis, ScaleLength)>,
|
||||||
@ -35,7 +33,7 @@ function! {
|
|||||||
|
|
||||||
parse(header, body, state, f, meta) {
|
parse(header, body, state, f, meta) {
|
||||||
expect_no_body(body, f);
|
expect_no_body(body, f);
|
||||||
SpacingFunc {
|
Self {
|
||||||
spacing: header.args.pos.expect::<ScaleLength>(f)
|
spacing: header.args.pos.expect::<ScaleLength>(f)
|
||||||
.map(|s| (meta, s))
|
.map(|s| (meta, s))
|
||||||
.or_missing(header.name.span, "spacing", f),
|
.or_missing(header.name.span, "spacing", f),
|
||||||
|
@ -9,7 +9,7 @@ macro_rules! try_or {
|
|||||||
($result:expr, $or:expr $(,)?) => {
|
($result:expr, $or:expr $(,)?) => {
|
||||||
match $result {
|
match $result {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => { $or }
|
Err(_) => $or,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ macro_rules! try_opt_or {
|
|||||||
($option:expr, $or:expr $(,)?) => {
|
($option:expr, $or:expr $(,)?) => {
|
||||||
match $option {
|
match $option {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => { $or }
|
None => $or,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
13
src/paper.rs
13
src/paper.rs
@ -16,7 +16,7 @@ pub struct Paper {
|
|||||||
|
|
||||||
impl Paper {
|
impl Paper {
|
||||||
/// The paper with the given name.
|
/// The paper with the given name.
|
||||||
pub fn from_name(name: &str) -> Option<Paper> {
|
pub fn from_name(name: &str) -> Option<Self> {
|
||||||
parse_paper(name)
|
parse_paper(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,6 @@ pub enum PaperClass {
|
|||||||
impl PaperClass {
|
impl PaperClass {
|
||||||
/// The default margins for this page class.
|
/// The default margins for this page class.
|
||||||
pub fn default_margins(self) -> Value4<ScaleLength> {
|
pub fn default_margins(self) -> Value4<ScaleLength> {
|
||||||
use PaperClass::*;
|
|
||||||
let values = |l, t, r, b| Value4::new(
|
let values = |l, t, r, b| Value4::new(
|
||||||
ScaleLength::Scaled(l),
|
ScaleLength::Scaled(l),
|
||||||
ScaleLength::Scaled(t),
|
ScaleLength::Scaled(t),
|
||||||
@ -48,11 +47,11 @@ impl PaperClass {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Custom => values(0.1190, 0.0842, 0.1190, 0.0842),
|
Self::Custom => values(0.1190, 0.0842, 0.1190, 0.0842),
|
||||||
Base => values(0.1190, 0.0842, 0.1190, 0.0842),
|
Self::Base => values(0.1190, 0.0842, 0.1190, 0.0842),
|
||||||
US => values(0.1760, 0.1092, 0.1760, 0.0910),
|
Self::US => values(0.1760, 0.1092, 0.1760, 0.0910),
|
||||||
Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294),
|
Self::Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294),
|
||||||
Book => values(0.1200, 0.0852, 0.1500, 0.0965),
|
Self::Book => values(0.1200, 0.0852, 0.1500, 0.0965),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
src/style.rs
19
src/style.rs
@ -1,7 +1,10 @@
|
|||||||
//! Styles for text and pages.
|
//! Styles for text and pages.
|
||||||
|
|
||||||
use fontdock::{fallback, FallbackTree, FontVariant, FontStyle, FontWeight, FontWidth};
|
use fontdock::{
|
||||||
use crate::geom::{Size, Margins, Value4};
|
fallback, FallbackTree, FontStyle, FontVariant, FontWeight, FontWidth,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::geom::{Margins, Size, Value4};
|
||||||
use crate::length::{Length, ScaleLength};
|
use crate::length::{Length, ScaleLength};
|
||||||
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
||||||
|
|
||||||
@ -62,8 +65,8 @@ impl TextStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextStyle {
|
impl Default for TextStyle {
|
||||||
fn default() -> TextStyle {
|
fn default() -> Self {
|
||||||
TextStyle {
|
Self {
|
||||||
fallback: fallback! {
|
fallback: fallback! {
|
||||||
list: ["sans-serif"],
|
list: ["sans-serif"],
|
||||||
classes: {
|
classes: {
|
||||||
@ -105,8 +108,8 @@ pub struct PageStyle {
|
|||||||
|
|
||||||
impl PageStyle {
|
impl PageStyle {
|
||||||
/// The default page style for the given paper.
|
/// The default page style for the given paper.
|
||||||
pub fn new(paper: Paper) -> PageStyle {
|
pub fn new(paper: Paper) -> Self {
|
||||||
PageStyle {
|
Self {
|
||||||
class: paper.class,
|
class: paper.class,
|
||||||
size: paper.size(),
|
size: paper.size(),
|
||||||
margins: Value4::with_all(None),
|
margins: Value4::with_all(None),
|
||||||
@ -127,7 +130,7 @@ impl PageStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PageStyle {
|
impl Default for PageStyle {
|
||||||
fn default() -> PageStyle {
|
fn default() -> Self {
|
||||||
PageStyle::new(PAPER_A4)
|
Self::new(PAPER_A4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,32 +13,16 @@ pub type Decorations = SpanVec<Decoration>;
|
|||||||
#[cfg_attr(feature = "serialize", derive(Serialize))]
|
#[cfg_attr(feature = "serialize", derive(Serialize))]
|
||||||
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
|
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
|
||||||
pub enum Decoration {
|
pub enum Decoration {
|
||||||
/// A valid function name.
|
/// A valid, successfully resolved function name.
|
||||||
/// ```typst
|
ResolvedFunc,
|
||||||
/// [box]
|
/// An invalid, unresolved function name.
|
||||||
/// ^^^
|
UnresolvedFunc,
|
||||||
/// ```
|
/// A key part of a keyword argument.
|
||||||
ValidFuncName,
|
|
||||||
/// An invalid function name.
|
|
||||||
/// ```typst
|
|
||||||
/// [blabla]
|
|
||||||
/// ^^^^^^
|
|
||||||
/// ```
|
|
||||||
InvalidFuncName,
|
|
||||||
/// A key of a keyword argument.
|
|
||||||
/// ```typst
|
|
||||||
/// [box: width=5cm]
|
|
||||||
/// ^^^^^
|
|
||||||
/// ```
|
|
||||||
ArgumentKey,
|
ArgumentKey,
|
||||||
/// A key in an object.
|
/// A key part of a pair in an object.
|
||||||
/// ```typst
|
|
||||||
/// [box: padding={ left: 1cm, right: 2cm}]
|
|
||||||
/// ^^^^ ^^^^^
|
|
||||||
/// ```
|
|
||||||
ObjectKey,
|
ObjectKey,
|
||||||
/// An italic word.
|
/// Text in italics.
|
||||||
Italic,
|
Italic,
|
||||||
/// A bold word.
|
/// Text in bold.
|
||||||
Bold,
|
Bold,
|
||||||
}
|
}
|
||||||
|
@ -5,26 +5,26 @@ use std::ops::Deref;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::u8;
|
use std::u8;
|
||||||
|
|
||||||
use crate::Feedback;
|
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
use super::span::{Spanned, SpanVec};
|
use crate::Feedback;
|
||||||
|
use super::span::{SpanVec, Spanned};
|
||||||
use super::tokens::is_identifier;
|
use super::tokens::is_identifier;
|
||||||
use super::value::Value;
|
use super::value::Value;
|
||||||
|
|
||||||
/// An argument or return value.
|
/// An expression.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
/// An identifier: `ident`.
|
/// An identifier: `ident`.
|
||||||
Ident(Ident),
|
Ident(Ident),
|
||||||
/// A string: `"string"`.
|
/// A string: `"string"`.
|
||||||
Str(String),
|
Str(String),
|
||||||
|
/// A boolean: `true, false`.
|
||||||
|
Bool(bool),
|
||||||
/// A number: `1.2, 200%`.
|
/// A number: `1.2, 200%`.
|
||||||
Number(f64),
|
Number(f64),
|
||||||
/// A length: `2cm, 5.2in`.
|
/// A length: `2cm, 5.2in`.
|
||||||
Length(Length),
|
Length(Length),
|
||||||
/// A bool: `true, false`.
|
/// A color value with alpha channel: `#f79143ff`.
|
||||||
Bool(bool),
|
|
||||||
/// A color value, including the alpha channel: `#f79143ff`.
|
|
||||||
Color(RgbaColor),
|
Color(RgbaColor),
|
||||||
/// A tuple: `(false, 12cm, "hi")`.
|
/// A tuple: `(false, 12cm, "hi")`.
|
||||||
Tuple(Tuple),
|
Tuple(Tuple),
|
||||||
@ -32,37 +32,38 @@ pub enum Expr {
|
|||||||
NamedTuple(NamedTuple),
|
NamedTuple(NamedTuple),
|
||||||
/// An object: `{ fit: false, width: 12pt }`.
|
/// An object: `{ fit: false, width: 12pt }`.
|
||||||
Object(Object),
|
Object(Object),
|
||||||
/// An operator that negates the contained expression.
|
/// An operation that negates the contained expression.
|
||||||
Neg(Box<Spanned<Expr>>),
|
Neg(Box<Spanned<Expr>>),
|
||||||
/// An operator that adds the contained expressions.
|
/// An operation that adds the contained expressions.
|
||||||
Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
/// An operator that subtracts contained expressions.
|
/// An operation that subtracts the contained expressions.
|
||||||
Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
/// An operator that multiplies the contained expressions.
|
/// An operation that multiplies the contained expressions.
|
||||||
Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
/// An operator that divides the contained expressions.
|
/// An operation that divides the contained expressions.
|
||||||
Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
/// A natural-language name of the type of this expression, e.g. "identifier".
|
/// A natural-language name of the type of this expression, e.g.
|
||||||
|
/// "identifier".
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
use Expr::*;
|
use Expr::*;
|
||||||
match self {
|
match self {
|
||||||
Ident(_) => "identifier",
|
Ident(_) => "identifier",
|
||||||
Str(_) => "string",
|
Str(_) => "string",
|
||||||
Number(_) => "number",
|
Bool(_) => "bool",
|
||||||
Length(_) => "length",
|
Number(_) => "number",
|
||||||
Bool(_) => "bool",
|
Length(_) => "length",
|
||||||
Color(_) => "color",
|
Color(_) => "color",
|
||||||
Tuple(_) => "tuple",
|
Tuple(_) => "tuple",
|
||||||
NamedTuple(_) => "named tuple",
|
NamedTuple(_) => "named tuple",
|
||||||
Object(_) => "object",
|
Object(_) => "object",
|
||||||
Neg(_) => "negation",
|
Neg(_) => "negation",
|
||||||
Add(_, _) => "addition",
|
Add(_, _) => "addition",
|
||||||
Sub(_, _) => "subtraction",
|
Sub(_, _) => "subtraction",
|
||||||
Mul(_, _) => "multiplication",
|
Mul(_, _) => "multiplication",
|
||||||
Div(_, _) => "division",
|
Div(_, _) => "division",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,9 +74,9 @@ impl Debug for Expr {
|
|||||||
match self {
|
match self {
|
||||||
Ident(i) => i.fmt(f),
|
Ident(i) => i.fmt(f),
|
||||||
Str(s) => s.fmt(f),
|
Str(s) => s.fmt(f),
|
||||||
|
Bool(b) => b.fmt(f),
|
||||||
Number(n) => n.fmt(f),
|
Number(n) => n.fmt(f),
|
||||||
Length(s) => s.fmt(f),
|
Length(s) => s.fmt(f),
|
||||||
Bool(b) => b.fmt(f),
|
|
||||||
Color(c) => c.fmt(f),
|
Color(c) => c.fmt(f),
|
||||||
Tuple(t) => t.fmt(f),
|
Tuple(t) => t.fmt(f),
|
||||||
NamedTuple(t) => t.fmt(f),
|
NamedTuple(t) => t.fmt(f),
|
||||||
@ -89,22 +90,15 @@ impl Debug for Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unicode identifier.
|
/// An identifier as defined by unicode with a few extra permissible characters.
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```typst
|
|
||||||
/// [func: "hi", ident]
|
|
||||||
/// ^^^^ ^^^^^
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct Ident(pub String);
|
pub struct Ident(pub String);
|
||||||
|
|
||||||
impl Ident {
|
impl Ident {
|
||||||
/// Create a new identifier from a string checking that it is a valid
|
/// Create a new identifier from a string checking that it is a valid.
|
||||||
/// unicode identifier.
|
pub fn new(ident: impl AsRef<str> + Into<String>) -> Option<Self> {
|
||||||
pub fn new<S>(ident: S) -> Option<Ident> where S: AsRef<str> + Into<String> {
|
|
||||||
if is_identifier(ident.as_ref()) {
|
if is_identifier(ident.as_ref()) {
|
||||||
Some(Ident(ident.into()))
|
Some(Self(ident.into()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -139,36 +133,36 @@ pub struct RgbaColor {
|
|||||||
pub b: u8,
|
pub b: u8,
|
||||||
/// Alpha channel.
|
/// Alpha channel.
|
||||||
pub a: u8,
|
pub a: u8,
|
||||||
/// Indicates whether this is a user-provided value or a
|
/// This is true if this value was provided as a fail-over by the parser
|
||||||
/// default value provided as a fail-over by the parser.
|
/// because the user-defined value was invalid. This color may be
|
||||||
/// This color may be overwritten if this property is true.
|
/// overwritten if this property is true.
|
||||||
pub healed: bool,
|
pub healed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RgbaColor {
|
impl RgbaColor {
|
||||||
/// Constructs a new color.
|
/// Constructs a new color.
|
||||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> RgbaColor {
|
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
RgbaColor { r, g, b, a, healed: false }
|
Self { r, g, b, a, healed: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new color with the healed property set to true.
|
/// Constructs a new color with the healed property set to true.
|
||||||
pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> RgbaColor {
|
pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
RgbaColor { r, g, b, a, healed: true }
|
Self { r, g, b, a, healed: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for RgbaColor {
|
impl FromStr for RgbaColor {
|
||||||
type Err = ParseColorError;
|
type Err = ParseColorError;
|
||||||
|
|
||||||
/// Constructs a new color from a hex string like `7a03c2`.
|
/// Constructs a new color from a hex string like `7a03c2`. Do not specify a
|
||||||
/// Do not specify a leading `#`.
|
/// leading `#`.
|
||||||
fn from_str(hex_str: &str) -> Result<RgbaColor, Self::Err> {
|
fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
|
||||||
if !hex_str.is_ascii() {
|
if !hex_str.is_ascii() {
|
||||||
return Err(ParseColorError);
|
return Err(ParseColorError);
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = hex_str.len();
|
let len = hex_str.len();
|
||||||
let long = len == 6 || len == 8;
|
let long = len == 6 || len == 8;
|
||||||
let short = len == 3 || len == 4;
|
let short = len == 3 || len == 4;
|
||||||
let alpha = len == 4 || len == 8;
|
let alpha = len == 4 || len == 8;
|
||||||
|
|
||||||
@ -192,7 +186,7 @@ impl FromStr for RgbaColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RgbaColor::new(values[0], values[1], values[2], values[3]))
|
Ok(Self::new(values[0], values[1], values[2], values[3]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,14 +194,12 @@ impl Debug for RgbaColor {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f, "rgba({:02}, {:02}, {:02}, {:02})",
|
||||||
"rgba({:02}, {:02}, {:02}, {:02})",
|
|
||||||
self.r, self.g, self.b, self.a,
|
self.r, self.g, self.b, self.a,
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f, "#{:02x}{:02x}{:02x}{:02x}",
|
||||||
"#{:02x}{:02x}{:02x}{:02x}",
|
|
||||||
self.r, self.g, self.b, self.a,
|
self.r, self.g, self.b, self.a,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@ -218,7 +210,7 @@ impl Debug for RgbaColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error returned when parsing a [`RgbaColor`] from a string fails.
|
/// The error when parsing an `RgbaColor` fails.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub struct ParseColorError;
|
pub struct ParseColorError;
|
||||||
|
|
||||||
@ -226,7 +218,7 @@ impl std::error::Error for ParseColorError {}
|
|||||||
|
|
||||||
impl fmt::Display for ParseColorError {
|
impl fmt::Display for ParseColorError {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_str("invalid color")
|
f.pad("invalid color")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,8 +233,8 @@ pub struct Tuple(pub SpanVec<Expr>);
|
|||||||
|
|
||||||
impl Tuple {
|
impl Tuple {
|
||||||
/// Create an empty tuple.
|
/// Create an empty tuple.
|
||||||
pub fn new() -> Tuple {
|
pub fn new() -> Self {
|
||||||
Tuple(vec![])
|
Self(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an element.
|
/// Add an element.
|
||||||
@ -278,13 +270,13 @@ impl Tuple {
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
std::iter::from_fn(move || {
|
std::iter::from_fn(move || {
|
||||||
while i < self.0.len() {
|
while i < self.0.len() {
|
||||||
let val = V::parse(self.0[i].clone(), &mut Feedback::new());
|
let val = V::parse(self.0[i].clone(), &mut Feedback::new());
|
||||||
if val.is_some() {
|
if val.is_some() {
|
||||||
self.0.remove(i);
|
self.0.remove(i);
|
||||||
return val;
|
return val;
|
||||||
} else {
|
} else {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
@ -305,16 +297,16 @@ impl Debug for Tuple {
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct NamedTuple {
|
pub struct NamedTuple {
|
||||||
/// The name of the tuple and where it is in the user source.
|
/// The name of the tuple.
|
||||||
pub name: Spanned<Ident>,
|
pub name: Spanned<Ident>,
|
||||||
/// The elements of the tuple.
|
/// The elements of the tuple.
|
||||||
pub tuple: Spanned<Tuple>,
|
pub tuple: Spanned<Tuple>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NamedTuple {
|
impl NamedTuple {
|
||||||
/// Create a named tuple from a tuple.
|
/// Create a named tuple from a name and a tuple.
|
||||||
pub fn new(name: Spanned<Ident>, tuple: Spanned<Tuple>) -> NamedTuple {
|
pub fn new(name: Spanned<Ident>, tuple: Spanned<Tuple>) -> Self {
|
||||||
NamedTuple { name, tuple }
|
Self { name, tuple }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,24 +330,14 @@ pub struct Object(pub SpanVec<Pair>);
|
|||||||
/// A key-value pair in an object.
|
/// A key-value pair in an object.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Pair {
|
pub struct Pair {
|
||||||
/// The key part.
|
|
||||||
/// ```typst
|
|
||||||
/// key: value
|
|
||||||
/// ^^^
|
|
||||||
/// ```
|
|
||||||
pub key: Spanned<Ident>,
|
pub key: Spanned<Ident>,
|
||||||
/// The value part.
|
|
||||||
/// ```typst
|
|
||||||
/// key: value
|
|
||||||
/// ^^^^^
|
|
||||||
/// ```
|
|
||||||
pub value: Spanned<Expr>,
|
pub value: Spanned<Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Object {
|
||||||
/// Create an empty object.
|
/// Create an empty object.
|
||||||
pub fn new() -> Object {
|
pub fn new() -> Self {
|
||||||
Object(vec![])
|
Self(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a pair to object.
|
/// Add a pair to object.
|
||||||
@ -384,13 +366,13 @@ impl Object {
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
std::iter::from_fn(move || {
|
std::iter::from_fn(move || {
|
||||||
while i < self.0.len() {
|
while i < self.0.len() {
|
||||||
let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new());
|
let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new());
|
||||||
if let Some(val) = val {
|
if let Some(val) = val {
|
||||||
let pair = self.0.remove(i);
|
let pair = self.0.remove(i);
|
||||||
return Some((pair.v.key, val));
|
return Some((pair.v.key, val));
|
||||||
} else {
|
} else {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
|
@ -4,19 +4,19 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
|
pub mod decoration;
|
||||||
|
pub mod expr;
|
||||||
|
pub mod parsing;
|
||||||
|
pub mod scope;
|
||||||
|
pub mod span;
|
||||||
|
pub mod tokens;
|
||||||
|
pub mod tree;
|
||||||
|
pub mod value;
|
||||||
|
|
||||||
/// Basic types used around the syntax side.
|
/// Basic types used around the syntax side.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::expr::*;
|
pub use super::expr::*;
|
||||||
pub use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
pub use super::span::{Span, SpanVec, Spanned};
|
||||||
pub use super::span::{SpanVec, Span, Spanned};
|
pub use super::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
||||||
pub use super::value::*;
|
pub use super::value::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod decoration;
|
|
||||||
pub mod expr;
|
|
||||||
pub mod tree;
|
|
||||||
pub mod parsing;
|
|
||||||
pub mod span;
|
|
||||||
pub mod scope;
|
|
||||||
pub mod tokens;
|
|
||||||
pub mod value;
|
|
||||||
|
@ -2,36 +2,34 @@
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::{Pass, Feedback};
|
use crate::{Feedback, Pass};
|
||||||
use super::decoration::Decoration;
|
use super::decoration::Decoration;
|
||||||
use super::expr::*;
|
use super::expr::*;
|
||||||
use super::scope::Scope;
|
use super::scope::Scope;
|
||||||
use super::span::{Pos, Span, Spanned};
|
use super::span::{Pos, Span, Spanned};
|
||||||
use super::tokens::{is_newline_char, Token, Tokens, TokenMode};
|
use super::tokens::{is_newline_char, Token, TokenMode, Tokens};
|
||||||
use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
use super::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
||||||
|
|
||||||
/// A function which parses a function call into a tree.
|
/// A function which parses a function call into a dynamic node.
|
||||||
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>;
|
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>;
|
||||||
|
|
||||||
/// Parse a function call.
|
/// Parse a function call.
|
||||||
pub trait ParseCall {
|
pub trait ParseCall {
|
||||||
/// A metadata type whose value is passed into the function parser. This
|
/// Metadata whose value is passed to `parse`. This allows a single function
|
||||||
/// allows a single function to do different things depending on the value
|
/// to do different things depending on the value that needs to be given
|
||||||
/// that needs to be given when inserting the function into a
|
/// when inserting the function into a scope.
|
||||||
/// [scope](crate::syntax::Scope).
|
|
||||||
///
|
///
|
||||||
/// For example, the functions `word.spacing`, `line.spacing` and
|
/// For example, the functions `h` and `v` are built on the same type.
|
||||||
/// `par.spacing` are actually all the same function
|
|
||||||
/// [`ContentSpacingFunc`](crate::library::ContentSpacingFunc) with the
|
|
||||||
/// metadata specifiy which content should be spaced.
|
|
||||||
type Meta: Clone;
|
type Meta: Clone;
|
||||||
|
|
||||||
/// Parse the header and body into this function given a context.
|
/// Parse the function call.
|
||||||
fn parse(
|
fn parse(
|
||||||
header: FuncCall,
|
call: FuncCall,
|
||||||
state: &ParseState,
|
state: &ParseState,
|
||||||
metadata: Self::Meta,
|
metadata: Self::Meta,
|
||||||
) -> Pass<Self> where Self: Sized;
|
) -> Pass<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An invocation of a function.
|
/// An invocation of a function.
|
||||||
@ -58,8 +56,8 @@ pub struct FuncArgs {
|
|||||||
|
|
||||||
impl FuncArgs {
|
impl FuncArgs {
|
||||||
/// Create new empty function arguments.
|
/// Create new empty function arguments.
|
||||||
pub fn new() -> FuncArgs {
|
pub fn new() -> Self {
|
||||||
FuncArgs {
|
Self {
|
||||||
pos: Tuple::new(),
|
pos: Tuple::new(),
|
||||||
key: Object::new(),
|
key: Object::new(),
|
||||||
}
|
}
|
||||||
@ -77,9 +75,7 @@ impl FuncArgs {
|
|||||||
/// Either a positional or keyword argument.
|
/// Either a positional or keyword argument.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum FuncArg {
|
pub enum FuncArg {
|
||||||
/// A positional argument.
|
|
||||||
Pos(Expr),
|
Pos(Expr),
|
||||||
/// A keyword argument.
|
|
||||||
Key(Pair),
|
Key(Pair),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,26 +112,21 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
|
|||||||
Token::Function { header, body, terminated } => {
|
Token::Function { header, body, terminated } => {
|
||||||
let parsed = FuncParser::new(header, body, state).parse();
|
let parsed = FuncParser::new(header, body, state).parse();
|
||||||
feedback.extend_offset(parsed.feedback, span.start);
|
feedback.extend_offset(parsed.feedback, span.start);
|
||||||
|
|
||||||
if !terminated {
|
if !terminated {
|
||||||
error!(@feedback, Span::at(span.end), "expected closing bracket");
|
error!(@feedback, Span::at(span.end), "expected closing bracket");
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed.output
|
parsed.output
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Star => SyntaxNode::ToggleBolder,
|
Token::Star => SyntaxNode::ToggleBolder,
|
||||||
Token::Underscore => SyntaxNode::ToggleItalic,
|
Token::Underscore => SyntaxNode::ToggleItalic,
|
||||||
Token::Backslash => SyntaxNode::Linebreak,
|
Token::Backslash => SyntaxNode::Linebreak,
|
||||||
|
|
||||||
Token::Raw { raw, terminated } => {
|
Token::Raw { raw, terminated } => {
|
||||||
if !terminated {
|
if !terminated {
|
||||||
error!(@feedback, Span::at(span.end), "expected backtick");
|
error!(@feedback, Span::at(span.end), "expected backtick");
|
||||||
}
|
}
|
||||||
|
|
||||||
SyntaxNode::Raw(unescape_raw(raw))
|
SyntaxNode::Raw(unescape_raw(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Text(text) => SyntaxNode::Text(text.to_string()),
|
Token::Text(text) => SyntaxNode::Text(text.to_string()),
|
||||||
|
|
||||||
Token::LineComment(_) | Token::BlockComment(_) => continue,
|
Token::LineComment(_) | Token::BlockComment(_) => continue,
|
||||||
@ -153,17 +144,9 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
|
|||||||
|
|
||||||
struct FuncParser<'s> {
|
struct FuncParser<'s> {
|
||||||
state: &'s ParseState,
|
state: &'s ParseState,
|
||||||
/// ```typst
|
/// The tokens inside the header.
|
||||||
/// [tokens][body]
|
|
||||||
/// ^^^^^^
|
|
||||||
/// ```
|
|
||||||
tokens: Tokens<'s>,
|
tokens: Tokens<'s>,
|
||||||
peeked: Option<Option<Spanned<Token<'s>>>>,
|
peeked: Option<Option<Spanned<Token<'s>>>>,
|
||||||
/// The spanned body string if there is a body.
|
|
||||||
/// ```typst
|
|
||||||
/// [tokens][body]
|
|
||||||
/// ^^^^
|
|
||||||
/// ```
|
|
||||||
body: Option<Spanned<&'s str>>,
|
body: Option<Spanned<&'s str>>,
|
||||||
feedback: Feedback,
|
feedback: Feedback,
|
||||||
}
|
}
|
||||||
@ -173,8 +156,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
header: &'s str,
|
header: &'s str,
|
||||||
body: Option<Spanned<&'s str>>,
|
body: Option<Spanned<&'s str>>,
|
||||||
state: &'s ParseState,
|
state: &'s ParseState,
|
||||||
) -> FuncParser<'s> {
|
) -> Self {
|
||||||
FuncParser {
|
Self {
|
||||||
state,
|
state,
|
||||||
// Start at column 1 because the opening bracket is also part of
|
// Start at column 1 because the opening bracket is also part of
|
||||||
// the function, but not part of the `header` string.
|
// the function, but not part of the `header` string.
|
||||||
@ -190,7 +173,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
let name = header.name.v.as_str();
|
let name = header.name.v.as_str();
|
||||||
let (parser, deco) = match self.state.scope.get_parser(name) {
|
let (parser, deco) = match self.state.scope.get_parser(name) {
|
||||||
// The function exists in the scope.
|
// The function exists in the scope.
|
||||||
Some(parser) => (parser, Decoration::ValidFuncName),
|
Some(parser) => (parser, Decoration::ResolvedFunc),
|
||||||
|
|
||||||
// The function does not exist in the scope. The parser that is
|
// The function does not exist in the scope. The parser that is
|
||||||
// returned here is a fallback parser which exists to make sure
|
// returned here is a fallback parser which exists to make sure
|
||||||
@ -199,7 +182,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
None => {
|
None => {
|
||||||
error!(@self.feedback, header.name.span, "unknown function");
|
error!(@self.feedback, header.name.span, "unknown function");
|
||||||
let parser = self.state.scope.get_fallback_parser();
|
let parser = self.state.scope.get_fallback_parser();
|
||||||
(parser, Decoration::InvalidFuncName)
|
(parser, Decoration::UnresolvedFunc)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -261,9 +244,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
self.skip_white();
|
self.skip_white();
|
||||||
|
|
||||||
let key = ident;
|
let key = ident;
|
||||||
self.feedback.decorations.push(
|
self.feedback.decorations
|
||||||
Spanned::new(Decoration::ArgumentKey, key.span)
|
.push(Spanned::new(Decoration::ArgumentKey, key.span));
|
||||||
);
|
|
||||||
|
|
||||||
let value = try_opt_or!(self.parse_expr(), {
|
let value = try_opt_or!(self.parse_expr(), {
|
||||||
self.expected("value");
|
self.expected("value");
|
||||||
@ -399,9 +381,9 @@ impl FuncParser<'_> {
|
|||||||
self.eat_span(Expr::Str(unescape_string(string)))
|
self.eat_span(Expr::Str(unescape_string(string)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token::ExprBool(b) => self.eat_span(Expr::Bool(b)),
|
||||||
Token::ExprNumber(n) => self.eat_span(Expr::Number(n)),
|
Token::ExprNumber(n) => self.eat_span(Expr::Number(n)),
|
||||||
Token::ExprLength(s) => self.eat_span(Expr::Length(s)),
|
Token::ExprLength(s) => self.eat_span(Expr::Length(s)),
|
||||||
Token::ExprBool(b) => self.eat_span(Expr::Bool(b)),
|
|
||||||
Token::ExprHex(s) => {
|
Token::ExprHex(s) => {
|
||||||
if let Ok(color) = RgbaColor::from_str(s) {
|
if let Ok(color) = RgbaColor::from_str(s) {
|
||||||
self.eat_span(Expr::Color(color))
|
self.eat_span(Expr::Color(color))
|
||||||
@ -411,7 +393,7 @@ impl FuncParser<'_> {
|
|||||||
let healed = RgbaColor::new_healed(0, 0, 0, 255);
|
let healed = RgbaColor::new_healed(0, 0, 0, 255);
|
||||||
self.eat_span(Expr::Color(healed))
|
self.eat_span(Expr::Color(healed))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// This could be a tuple or a parenthesized expression. We parse as
|
// This could be a tuple or a parenthesized expression. We parse as
|
||||||
// a tuple in any case and coerce the tuple into a value if it is
|
// a tuple in any case and coerce the tuple into a value if it is
|
||||||
@ -500,9 +482,8 @@ impl FuncParser<'_> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.feedback.decorations.push(
|
self.feedback.decorations
|
||||||
Spanned::new(Decoration::ObjectKey, key.span)
|
.push(Spanned::new(Decoration::ObjectKey, key.span));
|
||||||
);
|
|
||||||
|
|
||||||
self.skip_white();
|
self.skip_white();
|
||||||
let value = try_opt_or!(self.parse_expr(), {
|
let value = try_opt_or!(self.parse_expr(), {
|
||||||
@ -622,7 +603,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn pos(&self) -> Pos {
|
fn pos(&self) -> Pos {
|
||||||
self.peeked.flatten()
|
self.peeked
|
||||||
|
.flatten()
|
||||||
.map(|s| s.span.start)
|
.map(|s| s.span.start)
|
||||||
.unwrap_or_else(|| self.tokens.pos())
|
.unwrap_or_else(|| self.tokens.pos())
|
||||||
}
|
}
|
||||||
@ -687,10 +669,10 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use Decoration::*;
|
use Decoration::*;
|
||||||
use Expr::{Number as Num, Length as Len, Bool};
|
use Expr::{Bool, Length as Len, Number as Num};
|
||||||
use SyntaxNode::{
|
use SyntaxNode::{
|
||||||
Space as S, ToggleItalic as Italic, ToggleBolder as Bold,
|
Space as S, Parbreak, Linebreak, ToggleItalic as Italic,
|
||||||
Parbreak, Linebreak,
|
ToggleBolder as Bold,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Test whether the given string parses into
|
/// Test whether the given string parses into
|
||||||
@ -882,7 +864,7 @@ mod tests {
|
|||||||
p!("🌎\n*/[n]" =>
|
p!("🌎\n*/[n]" =>
|
||||||
[(0:0, 0:1, T("🌎")), (0:1, 1:0, S), (1:2, 1:5, func!((0:1, 0:2, "n")))],
|
[(0:0, 0:1, T("🌎")), (0:1, 1:0, S), (1:2, 1:5, func!((0:1, 0:2, "n")))],
|
||||||
[(1:0, 1:2, "unexpected end of block comment")],
|
[(1:0, 1:2, "unexpected end of block comment")],
|
||||||
[(1:3, 1:4, ValidFuncName)],
|
[(1:3, 1:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,12 +887,12 @@ mod tests {
|
|||||||
p!("[hi]" =>
|
p!("[hi]" =>
|
||||||
[func!("hi")],
|
[func!("hi")],
|
||||||
[(0:1, 0:3, "unknown function")],
|
[(0:1, 0:3, "unknown function")],
|
||||||
[(0:1, 0:3, InvalidFuncName)],
|
[(0:1, 0:3, UnresolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// A valid name.
|
// A valid name.
|
||||||
p!("[f]" => [func!("f")], [], [(0:1, 0:2, ValidFuncName)]);
|
p!("[f]" => [func!("f")], [], [(0:1, 0:2, ResolvedFunc)]);
|
||||||
p!("[ f]" => [func!("f")], [], [(0:3, 0:4, ValidFuncName)]);
|
p!("[ f]" => [func!("f")], [], [(0:3, 0:4, ResolvedFunc)]);
|
||||||
|
|
||||||
// An invalid token for a name.
|
// An invalid token for a name.
|
||||||
p!("[12]" => [func!("")], [(0:1, 0:3, "expected function name, found number")], []);
|
p!("[12]" => [func!("")], [(0:1, 0:3, "expected function name, found number")], []);
|
||||||
@ -923,7 +905,7 @@ mod tests {
|
|||||||
// Valid.
|
// Valid.
|
||||||
p!("[val: true]" =>
|
p!("[val: true]" =>
|
||||||
[func!["val": (Bool(true))]], [],
|
[func!["val": (Bool(true))]], [],
|
||||||
[(0:1, 0:4, ValidFuncName)],
|
[(0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// No colon before arg.
|
// No colon before arg.
|
||||||
@ -936,7 +918,7 @@ mod tests {
|
|||||||
p!("[val/🌎:$]" =>
|
p!("[val/🌎:$]" =>
|
||||||
[func!("val")],
|
[func!("val")],
|
||||||
[(0:4, 0:4, "expected colon")],
|
[(0:4, 0:4, "expected colon")],
|
||||||
[(0:1, 0:4, ValidFuncName)],
|
[(0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// String in invalid header without colon still parsed as string
|
// String in invalid header without colon still parsed as string
|
||||||
@ -1159,20 +1141,20 @@ mod tests {
|
|||||||
// Correct
|
// Correct
|
||||||
p!("[val: x=true]" =>
|
p!("[val: x=true]" =>
|
||||||
[func!("val": (), { "x" => Bool(true) })], [],
|
[func!("val": (), { "x" => Bool(true) })], [],
|
||||||
[(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)],
|
[(0:6, 0:7, ArgumentKey), (0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Spacing around keyword arguments
|
// Spacing around keyword arguments
|
||||||
p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" =>
|
p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" =>
|
||||||
[S, func!("val": (), { "hi" => Str("s\n") })], [],
|
[S, func!("val": (), { "hi" => Str("s\n") })], [],
|
||||||
[(2:1, 2:3, ArgumentKey), (1:2, 1:5, ValidFuncName)],
|
[(2:1, 2:3, ArgumentKey), (1:2, 1:5, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Missing value
|
// Missing value
|
||||||
p!("[val: x=]" =>
|
p!("[val: x=]" =>
|
||||||
[func!("val")],
|
[func!("val")],
|
||||||
[(0:8, 0:8, "expected value")],
|
[(0:8, 0:8, "expected value")],
|
||||||
[(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)],
|
[(0:6, 0:7, ArgumentKey), (0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1180,7 +1162,7 @@ mod tests {
|
|||||||
fn parse_multiple_mixed_arguments() {
|
fn parse_multiple_mixed_arguments() {
|
||||||
p!("[val: 12pt, key=value]" =>
|
p!("[val: 12pt, key=value]" =>
|
||||||
[func!("val": (Len(Length::pt(12.0))), { "key" => Id("value") })], [],
|
[func!("val": (Len(Length::pt(12.0))), { "key" => Id("value") })], [],
|
||||||
[(0:12, 0:15, ArgumentKey), (0:1, 0:4, ValidFuncName)],
|
[(0:12, 0:15, ArgumentKey), (0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b") });
|
pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b") });
|
||||||
}
|
}
|
||||||
@ -1197,7 +1179,7 @@ mod tests {
|
|||||||
p!("[val: [hi]]" =>
|
p!("[val: [hi]]" =>
|
||||||
[func!("val")],
|
[func!("val")],
|
||||||
[(0:6, 0:10, "expected argument, found function")],
|
[(0:6, 0:10, "expected argument, found function")],
|
||||||
[(0:1, 0:4, ValidFuncName)],
|
[(0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1208,7 +1190,7 @@ mod tests {
|
|||||||
[func!("val": (Bool(true), Id("you")), {})],
|
[func!("val": (Bool(true), Id("you")), {})],
|
||||||
[(0:10, 0:10, "expected comma"),
|
[(0:10, 0:10, "expected comma"),
|
||||||
(0:10, 0:11, "expected argument, found equals sign")],
|
(0:10, 0:11, "expected argument, found equals sign")],
|
||||||
[(0:1, 0:4, ValidFuncName)],
|
[(0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Unexpected equals.
|
// Unexpected equals.
|
||||||
@ -1223,14 +1205,14 @@ mod tests {
|
|||||||
[func!("val": (Id("key"), Num(12.0)), {})],
|
[func!("val": (Id("key"), Num(12.0)), {})],
|
||||||
[(0:9, 0:9, "expected comma"),
|
[(0:9, 0:9, "expected comma"),
|
||||||
(0:9, 0:10, "expected argument, found colon")],
|
(0:9, 0:10, "expected argument, found colon")],
|
||||||
[(0:1, 0:4, ValidFuncName)],
|
[(0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Invalid colon after unkeyable positional argument.
|
// Invalid colon after unkeyable positional argument.
|
||||||
p!("[val: true:12]" => [func!("val": (Bool(true), Num(12.0)), {})],
|
p!("[val: true:12]" => [func!("val": (Bool(true), Num(12.0)), {})],
|
||||||
[(0:10, 0:10, "expected comma"),
|
[(0:10, 0:10, "expected comma"),
|
||||||
(0:10, 0:11, "expected argument, found colon")],
|
(0:10, 0:11, "expected argument, found colon")],
|
||||||
[(0:1, 0:4, ValidFuncName)],
|
[(0:1, 0:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1274,13 +1256,13 @@ mod tests {
|
|||||||
// Space before function
|
// Space before function
|
||||||
p!(" [val]" =>
|
p!(" [val]" =>
|
||||||
[(0:0, 0:1, S), (0:1, 0:6, func!((0:1, 0:4, "val")))], [],
|
[(0:0, 0:1, S), (0:1, 0:6, func!((0:1, 0:4, "val")))], [],
|
||||||
[(0:2, 0:5, ValidFuncName)],
|
[(0:2, 0:5, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Newline before function
|
// Newline before function
|
||||||
p!(" \n\r\n[val]" =>
|
p!(" \n\r\n[val]" =>
|
||||||
[(0:0, 2:0, Parbreak), (2:0, 2:5, func!((0:1, 0:4, "val")))], [],
|
[(0:0, 2:0, Parbreak), (2:0, 2:5, func!((0:1, 0:4, "val")))], [],
|
||||||
[(2:1, 2:4, ValidFuncName)],
|
[(2:1, 2:4, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Content before function
|
// Content before function
|
||||||
@ -1293,7 +1275,7 @@ mod tests {
|
|||||||
(0:19, 0:20, T("🌎"))
|
(0:19, 0:20, T("🌎"))
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
[(0:7, 0:10, ValidFuncName)],
|
[(0:7, 0:10, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Nested function
|
// Nested function
|
||||||
@ -1308,7 +1290,7 @@ mod tests {
|
|||||||
]))
|
]))
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
[(0:2, 0:5, ValidFuncName), (1:6, 1:9, ValidFuncName)],
|
[(0:2, 0:5, ResolvedFunc), (1:6, 1:9, ResolvedFunc)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Scopes containing function parsers.
|
//! Mapping of function names to function parsers.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
@ -15,33 +15,31 @@ pub struct Scope {
|
|||||||
impl Scope {
|
impl Scope {
|
||||||
/// Create a new empty scope with a fallback parser that is invoked when no
|
/// Create a new empty scope with a fallback parser that is invoked when no
|
||||||
/// match is found.
|
/// match is found.
|
||||||
pub fn new<F>() -> Scope
|
pub fn new<F>() -> Self
|
||||||
where F: ParseCall<Meta=()> + DynamicNode + 'static {
|
where
|
||||||
Scope {
|
F: ParseCall<Meta = ()> + DynamicNode + 'static
|
||||||
|
{
|
||||||
|
Self {
|
||||||
parsers: HashMap::new(),
|
parsers: HashMap::new(),
|
||||||
fallback: make_parser::<F>(()),
|
fallback: make_parser::<F>(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new scope with the standard functions contained.
|
/// Associate the given function name with a dynamic node type.
|
||||||
pub fn with_std() -> Scope {
|
|
||||||
crate::library::std()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Associate the given name with a type that is parseable into a function.
|
|
||||||
pub fn add<F>(&mut self, name: &str)
|
pub fn add<F>(&mut self, name: &str)
|
||||||
where F: ParseCall<Meta=()> + DynamicNode + 'static {
|
where
|
||||||
|
F: ParseCall<Meta = ()> + DynamicNode + 'static
|
||||||
|
{
|
||||||
self.add_with_meta::<F>(name, ());
|
self.add_with_meta::<F>(name, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a parseable type with additional metadata that is given to the
|
/// Add a dynamic node type with additional metadata that is passed to the
|
||||||
/// parser (other than the default of `()`).
|
/// parser.
|
||||||
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseCall>::Meta)
|
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseCall>::Meta)
|
||||||
where F: ParseCall + DynamicNode + 'static {
|
where
|
||||||
self.parsers.insert(
|
F: ParseCall + DynamicNode + 'static
|
||||||
name.to_string(),
|
{
|
||||||
make_parser::<F>(metadata),
|
self.parsers.insert(name.to_string(), make_parser::<F>(metadata));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the parser with the given name if there is one.
|
/// Return the parser with the given name if there is one.
|
||||||
@ -57,14 +55,14 @@ impl Scope {
|
|||||||
|
|
||||||
impl Debug for Scope {
|
impl Debug for Scope {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_set()
|
f.debug_set().entries(self.parsers.keys()).finish()
|
||||||
.entries(self.parsers.keys())
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_parser<F>(metadata: <F as ParseCall>::Meta) -> Box<CallParser>
|
fn make_parser<F>(metadata: <F as ParseCall>::Meta) -> Box<CallParser>
|
||||||
where F: ParseCall + DynamicNode + 'static {
|
where
|
||||||
|
F: ParseCall + DynamicNode + 'static,
|
||||||
|
{
|
||||||
Box::new(move |f, s| {
|
Box::new(move |f, s| {
|
||||||
F::parse(f, s, metadata.clone())
|
F::parse(f, s, metadata.clone())
|
||||||
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>)
|
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>)
|
||||||
|
@ -36,13 +36,13 @@ pub struct Spanned<T> {
|
|||||||
|
|
||||||
impl<T> Spanned<T> {
|
impl<T> Spanned<T> {
|
||||||
/// Create a new instance from a value and its span.
|
/// Create a new instance from a value and its span.
|
||||||
pub fn new(v: T, span: Span) -> Spanned<T> {
|
pub fn new(v: T, span: Span) -> Self {
|
||||||
Spanned { v, span }
|
Self { v, span }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new instance from a value with the zero span.
|
/// Create a new instance from a value with the zero span.
|
||||||
pub fn zero(v: T) -> Spanned<T> {
|
pub fn zero(v: T) -> Self {
|
||||||
Spanned { v, span: Span::ZERO }
|
Self { v, span: Span::ZERO }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the value.
|
/// Access the value.
|
||||||
@ -51,12 +51,12 @@ impl<T> Spanned<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Map the value using a function while keeping the span.
|
/// Map the value using a function while keeping the span.
|
||||||
pub fn map<V, F>(self, f: F) -> Spanned<V> where F: FnOnce(T) -> V {
|
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
|
||||||
Spanned { v: f(self.v), span: self.span }
|
Spanned { v: f(self.v), span: self.span }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps the span while keeping the value.
|
/// Maps the span while keeping the value.
|
||||||
pub fn map_span<F>(mut self, f: F) -> Spanned<T> where F: FnOnce(Span) -> Span {
|
pub fn map_span(mut self, f: impl FnOnce(Span) -> Span) -> Self {
|
||||||
self.span = f(self.span);
|
self.span = f(self.span);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -91,35 +91,35 @@ pub struct Span {
|
|||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
/// The zero span.
|
/// The zero span.
|
||||||
pub const ZERO: Span = Span { start: Pos::ZERO, end: Pos::ZERO };
|
pub const ZERO: Self = Self { start: Pos::ZERO, end: Pos::ZERO };
|
||||||
|
|
||||||
/// Create a new span from start and end positions.
|
/// Create a new span from start and end positions.
|
||||||
pub fn new(start: Pos, end: Pos) -> Span {
|
pub fn new(start: Pos, end: Pos) -> Self {
|
||||||
Span { start, end }
|
Self { start, end }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a span including just a single position.
|
/// Create a span including just a single position.
|
||||||
pub fn at(pos: Pos) -> Span {
|
pub fn at(pos: Pos) -> Self {
|
||||||
Span { start: pos, end: pos }
|
Self { start: pos, end: pos }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new span with the earlier start and later end position.
|
/// Create a new span with the earlier start and later end position.
|
||||||
pub fn merge(a: Span, b: Span) -> Span {
|
pub fn merge(a: Self, b: Self) -> Self {
|
||||||
Span {
|
Self {
|
||||||
start: a.start.min(b.start),
|
start: a.start.min(b.start),
|
||||||
end: a.end.max(b.end),
|
end: a.end.max(b.end),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand a span by merging it with another span.
|
/// Expand a span by merging it with another span.
|
||||||
pub fn expand(&mut self, other: Span) {
|
pub fn expand(&mut self, other: Self) {
|
||||||
*self = Span::merge(*self, other)
|
*self = Self::merge(*self, other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Offset for Span {
|
impl Offset for Span {
|
||||||
fn offset(self, by: Pos) -> Self {
|
fn offset(self, by: Pos) -> Self {
|
||||||
Span {
|
Self {
|
||||||
start: self.start.offset(by),
|
start: self.start.offset(by),
|
||||||
end: self.end.offset(by),
|
end: self.end.offset(by),
|
||||||
}
|
}
|
||||||
@ -144,31 +144,31 @@ pub struct Pos {
|
|||||||
|
|
||||||
impl Pos {
|
impl Pos {
|
||||||
/// The line 0, column 0 position.
|
/// The line 0, column 0 position.
|
||||||
pub const ZERO: Pos = Pos { line: 0, column: 0 };
|
pub const ZERO: Self = Self { line: 0, column: 0 };
|
||||||
|
|
||||||
/// Create a new position from line and column.
|
/// Create a new position from line and column.
|
||||||
pub fn new(line: usize, column: usize) -> Pos {
|
pub fn new(line: usize, column: usize) -> Self {
|
||||||
Pos { line, column }
|
Self { line, column }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Offset for Pos {
|
impl Offset for Pos {
|
||||||
fn offset(self, by: Pos) -> Self {
|
fn offset(self, by: Self) -> Self {
|
||||||
by + self
|
by + self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for Pos {
|
impl Add for Pos {
|
||||||
type Output = Pos;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(self, rhs: Pos) -> Pos {
|
fn add(self, rhs: Self) -> Self {
|
||||||
if rhs.line == 0 {
|
if rhs.line == 0 {
|
||||||
Pos {
|
Self {
|
||||||
line: self.line,
|
line: self.line,
|
||||||
column: self.column + rhs.column
|
column: self.column + rhs.column,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Pos {
|
Self {
|
||||||
line: self.line + rhs.line,
|
line: self.line + rhs.line,
|
||||||
column: rhs.column,
|
column: rhs.column,
|
||||||
}
|
}
|
||||||
@ -177,16 +177,16 @@ impl Add for Pos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Sub for Pos {
|
impl Sub for Pos {
|
||||||
type Output = Pos;
|
type Output = Self;
|
||||||
|
|
||||||
fn sub(self, rhs: Pos) -> Pos {
|
fn sub(self, rhs: Self) -> Self {
|
||||||
if self.line == rhs.line {
|
if self.line == rhs.line {
|
||||||
Pos {
|
Self {
|
||||||
line: 0,
|
line: 0,
|
||||||
column: self.column - rhs.column
|
column: self.column - rhs.column,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Pos {
|
Self {
|
||||||
line: self.line - rhs.line,
|
line: self.line - rhs.line,
|
||||||
column: self.column,
|
column: self.column,
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,16 @@ use std::fmt::Debug;
|
|||||||
|
|
||||||
use crate::func::parse_maybe_body;
|
use crate::func::parse_maybe_body;
|
||||||
use super::decoration::Decoration;
|
use super::decoration::Decoration;
|
||||||
use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair};
|
use super::expr::{Expr, Ident, NamedTuple, Object, Pair, Tuple};
|
||||||
use super::parsing::{FuncHeader, FuncArgs, FuncArg};
|
use super::parsing::{FuncArg, FuncArgs, FuncHeader};
|
||||||
use super::span::Spanned;
|
use super::span::Spanned;
|
||||||
use super::tokens::Token;
|
use super::tokens::Token;
|
||||||
use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
use super::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
||||||
|
|
||||||
/// Check whether the expected and found results are the same.
|
|
||||||
pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
|
pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
|
||||||
where T: Debug + PartialEq + SpanlessEq {
|
where
|
||||||
|
T: Debug + PartialEq + SpanlessEq,
|
||||||
|
{
|
||||||
let cmp = if cmp_spans { PartialEq::eq } else { SpanlessEq::spanless_eq };
|
let cmp = if cmp_spans { PartialEq::eq } else { SpanlessEq::spanless_eq };
|
||||||
if !cmp(&exp, &found) {
|
if !cmp(&exp, &found) {
|
||||||
println!("source: {:?}", src);
|
println!("source: {:?}", src);
|
||||||
@ -41,7 +42,7 @@ macro_rules! span_vec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! span_item {
|
macro_rules! span_item {
|
||||||
(($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)) => ({
|
(($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)) => {{
|
||||||
use $crate::syntax::span::{Pos, Span, Spanned};
|
use $crate::syntax::span::{Pos, Span, Spanned};
|
||||||
Spanned {
|
Spanned {
|
||||||
span: Span::new(
|
span: Span::new(
|
||||||
@ -50,7 +51,7 @@ macro_rules! span_item {
|
|||||||
),
|
),
|
||||||
v: $v
|
v: $v
|
||||||
}
|
}
|
||||||
});
|
}};
|
||||||
|
|
||||||
($v:expr) => {
|
($v:expr) => {
|
||||||
$crate::syntax::span::Spanned::zero($v)
|
$crate::syntax::span::Spanned::zero($v)
|
||||||
@ -70,7 +71,7 @@ function! {
|
|||||||
let cloned = header.clone();
|
let cloned = header.clone();
|
||||||
header.args.pos.0.clear();
|
header.args.pos.0.clear();
|
||||||
header.args.key.0.clear();
|
header.args.key.0.clear();
|
||||||
DebugFn {
|
Self {
|
||||||
header: cloned,
|
header: cloned,
|
||||||
body: parse_maybe_body(body, state, f),
|
body: parse_maybe_body(body, state, f),
|
||||||
}
|
}
|
||||||
@ -80,7 +81,7 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compares elements by only looking at values and ignoring spans.
|
/// Compares elements by only looking at values and ignoring spans.
|
||||||
pub trait SpanlessEq<Rhs=Self> {
|
pub trait SpanlessEq<Rhs = Self> {
|
||||||
fn spanless_eq(&self, other: &Rhs) -> bool;
|
fn spanless_eq(&self, other: &Rhs) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,20 +103,21 @@ impl SpanlessEq for SyntaxNode {
|
|||||||
impl SpanlessEq for DebugFn {
|
impl SpanlessEq for DebugFn {
|
||||||
fn spanless_eq(&self, other: &DebugFn) -> bool {
|
fn spanless_eq(&self, other: &DebugFn) -> bool {
|
||||||
self.header.spanless_eq(&other.header)
|
self.header.spanless_eq(&other.header)
|
||||||
&& self.body.spanless_eq(&other.body)
|
&& self.body.spanless_eq(&other.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpanlessEq for FuncHeader {
|
impl SpanlessEq for FuncHeader {
|
||||||
fn spanless_eq(&self, other: &Self) -> bool {
|
fn spanless_eq(&self, other: &Self) -> bool {
|
||||||
self.name.spanless_eq(&other.name) && self.args.spanless_eq(&other.args)
|
self.name.spanless_eq(&other.name)
|
||||||
|
&& self.args.spanless_eq(&other.args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpanlessEq for FuncArgs {
|
impl SpanlessEq for FuncArgs {
|
||||||
fn spanless_eq(&self, other: &Self) -> bool {
|
fn spanless_eq(&self, other: &Self) -> bool {
|
||||||
self.key.spanless_eq(&other.key)
|
self.key.spanless_eq(&other.key)
|
||||||
&& self.pos.spanless_eq(&other.pos)
|
&& self.pos.spanless_eq(&other.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +156,7 @@ impl SpanlessEq for Tuple {
|
|||||||
impl SpanlessEq for NamedTuple {
|
impl SpanlessEq for NamedTuple {
|
||||||
fn spanless_eq(&self, other: &NamedTuple) -> bool {
|
fn spanless_eq(&self, other: &NamedTuple) -> bool {
|
||||||
self.name.v == other.name.v
|
self.name.v == other.name.v
|
||||||
&& self.tuple.v.spanless_eq(&other.tuple.v)
|
&& self.tuple.v.spanless_eq(&other.tuple.v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +175,7 @@ impl SpanlessEq for Pair {
|
|||||||
impl<T: SpanlessEq> SpanlessEq for Vec<T> {
|
impl<T: SpanlessEq> SpanlessEq for Vec<T> {
|
||||||
fn spanless_eq(&self, other: &Vec<T>) -> bool {
|
fn spanless_eq(&self, other: &Vec<T>) -> bool {
|
||||||
self.len() == other.len()
|
self.len() == other.len()
|
||||||
&& self.iter().zip(other).all(|(x, y)| x.spanless_eq(&y))
|
&& self.iter().zip(other).all(|(x, y)| x.spanless_eq(&y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ use super::span::{Pos, Span, Spanned};
|
|||||||
|
|
||||||
use Token::*;
|
use Token::*;
|
||||||
use TokenMode::*;
|
use TokenMode::*;
|
||||||
|
|
||||||
/// A minimal semantic entity of source code.
|
/// A minimal semantic entity of source code.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum Token<'s> {
|
pub enum Token<'s> {
|
||||||
@ -71,14 +70,14 @@ pub enum Token<'s> {
|
|||||||
/// a String. The escaping is done later in the parser.
|
/// a String. The escaping is done later in the parser.
|
||||||
string: &'s str,
|
string: &'s str,
|
||||||
/// Whether the closing quote was present.
|
/// Whether the closing quote was present.
|
||||||
terminated: bool
|
terminated: bool,
|
||||||
},
|
},
|
||||||
|
/// A boolean in a function header: `true | false`.
|
||||||
|
ExprBool(bool),
|
||||||
/// A number in a function header: `3.14`.
|
/// A number in a function header: `3.14`.
|
||||||
ExprNumber(f64),
|
ExprNumber(f64),
|
||||||
/// A length in a function header: `12pt`.
|
/// A length in a function header: `12pt`.
|
||||||
ExprLength(Length),
|
ExprLength(Length),
|
||||||
/// A boolean in a function header: `true | false`.
|
|
||||||
ExprBool(bool),
|
|
||||||
/// A hex value in a function header: `#20d82a`.
|
/// A hex value in a function header: `#20d82a`.
|
||||||
ExprHex(&'s str),
|
ExprHex(&'s str),
|
||||||
/// A plus in a function header, signifying the addition of expressions.
|
/// A plus in a function header, signifying the addition of expressions.
|
||||||
@ -130,9 +129,9 @@ impl<'s> Token<'s> {
|
|||||||
Equals => "equals sign",
|
Equals => "equals sign",
|
||||||
ExprIdent(_) => "identifier",
|
ExprIdent(_) => "identifier",
|
||||||
ExprStr { .. } => "string",
|
ExprStr { .. } => "string",
|
||||||
|
ExprBool(_) => "bool",
|
||||||
ExprNumber(_) => "number",
|
ExprNumber(_) => "number",
|
||||||
ExprLength(_) => "length",
|
ExprLength(_) => "length",
|
||||||
ExprBool(_) => "bool",
|
|
||||||
ExprHex(_) => "hex value",
|
ExprHex(_) => "hex value",
|
||||||
Plus => "plus",
|
Plus => "plus",
|
||||||
Hyphen => "minus",
|
Hyphen => "minus",
|
||||||
@ -173,8 +172,8 @@ impl<'s> Tokens<'s> {
|
|||||||
///
|
///
|
||||||
/// The first token's span starts an the given `offset` position instead of
|
/// The first token's span starts an the given `offset` position instead of
|
||||||
/// the zero position.
|
/// the zero position.
|
||||||
pub fn new(src: &'s str, offset: Pos, mode: TokenMode) -> Tokens<'s> {
|
pub fn new(src: &'s str, offset: Pos, mode: TokenMode) -> Self {
|
||||||
Tokens {
|
Self {
|
||||||
src,
|
src,
|
||||||
mode,
|
mode,
|
||||||
iter: src.chars().peekable(),
|
iter: src.chars().peekable(),
|
||||||
@ -200,7 +199,7 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
type Item = Spanned<Token<'s>>;
|
type Item = Spanned<Token<'s>>;
|
||||||
|
|
||||||
/// Parse the next token in the source code.
|
/// Parse the next token in the source code.
|
||||||
fn next(&mut self) -> Option<Spanned<Token<'s>>> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let start = self.pos();
|
let start = self.pos();
|
||||||
let first = self.eat()?;
|
let first = self.eat()?;
|
||||||
|
|
||||||
@ -366,7 +365,7 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let end = self.index();
|
let end = self.index();
|
||||||
(&self.src[start .. end], terminated)
|
(&self.src[start..end], terminated)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_string(&mut self) -> Token<'s> {
|
fn read_string(&mut self) -> Token<'s> {
|
||||||
@ -404,7 +403,7 @@ impl<'s> Tokens<'s> {
|
|||||||
Some(c) if is_escapable(c) => {
|
Some(c) if is_escapable(c) => {
|
||||||
let index = self.index();
|
let index = self.index();
|
||||||
self.eat();
|
self.eat();
|
||||||
Text(&self.src[index .. index + c.len_utf8()])
|
Text(&self.src[index..index + c.len_utf8()])
|
||||||
}
|
}
|
||||||
Some(c) if c.is_whitespace() => Backslash,
|
Some(c) if c.is_whitespace() => Backslash,
|
||||||
Some(_) => Text("\\"),
|
Some(_) => Text("\\"),
|
||||||
@ -442,13 +441,13 @@ impl<'s> Tokens<'s> {
|
|||||||
/// Returns the string from the index where this was called offset by
|
/// Returns the string from the index where this was called offset by
|
||||||
/// `offset_start` to the end offset by `offset_end`. The end is before or
|
/// `offset_start` to the end offset by `offset_end`. The end is before or
|
||||||
/// after the match depending on `eat_match`.
|
/// after the match depending on `eat_match`.
|
||||||
fn read_string_until<F>(
|
fn read_string_until(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut f: F,
|
mut f: impl FnMut(char) -> bool,
|
||||||
eat_match: bool,
|
eat_match: bool,
|
||||||
offset_start: isize,
|
offset_start: isize,
|
||||||
offset_end: isize,
|
offset_end: isize,
|
||||||
) -> (&'s str, bool) where F: FnMut(char) -> bool {
|
) -> (&'s str, bool) {
|
||||||
let start = ((self.index() as isize) + offset_start) as usize;
|
let start = ((self.index() as isize) + offset_start) as usize;
|
||||||
let mut matched = false;
|
let mut matched = false;
|
||||||
|
|
||||||
@ -469,7 +468,7 @@ impl<'s> Tokens<'s> {
|
|||||||
end = ((end as isize) + offset_end) as usize;
|
end = ((end as isize) + offset_end) as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
(&self.src[start .. end], matched)
|
(&self.src[start..end], matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eat(&mut self) -> Option<char> {
|
fn eat(&mut self) -> Option<char> {
|
||||||
@ -493,7 +492,7 @@ impl<'s> Tokens<'s> {
|
|||||||
|
|
||||||
fn parse_percentage(text: &str) -> Option<f64> {
|
fn parse_percentage(text: &str) -> Option<f64> {
|
||||||
if text.ends_with('%') {
|
if text.ends_with('%') {
|
||||||
text[.. text.len() - 1].parse::<f64>().ok()
|
text[..text.len() - 1].parse::<f64>().ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -503,7 +502,7 @@ fn parse_percentage(text: &str) -> Option<f64> {
|
|||||||
pub fn is_newline_char(character: char) -> bool {
|
pub fn is_newline_char(character: char) -> bool {
|
||||||
match character {
|
match character {
|
||||||
// Line Feed, Vertical Tab, Form Feed, Carriage Return.
|
// Line Feed, Vertical Tab, Form Feed, Carriage Return.
|
||||||
'\x0A' ..= '\x0D' => true,
|
'\x0A'..='\x0D' => true,
|
||||||
// Next Line, Line Separator, Paragraph Separator.
|
// Next Line, Line Separator, Paragraph Separator.
|
||||||
'\u{0085}' | '\u{2028}' | '\u{2029}' => true,
|
'\u{0085}' | '\u{2028}' | '\u{2029}' => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
@ -544,15 +543,15 @@ mod tests {
|
|||||||
LeftParen as LP, RightParen as RP,
|
LeftParen as LP, RightParen as RP,
|
||||||
LeftBrace as LB, RightBrace as RB,
|
LeftBrace as LB, RightBrace as RB,
|
||||||
ExprIdent as Id,
|
ExprIdent as Id,
|
||||||
|
ExprBool as Bool,
|
||||||
ExprNumber as Num,
|
ExprNumber as Num,
|
||||||
ExprLength as Len,
|
ExprLength as Len,
|
||||||
ExprBool as Bool,
|
|
||||||
ExprHex as Hex,
|
ExprHex as Hex,
|
||||||
Text as T,
|
|
||||||
Plus,
|
Plus,
|
||||||
Hyphen as Min,
|
Hyphen as Min,
|
||||||
Star,
|
|
||||||
Slash,
|
Slash,
|
||||||
|
Star,
|
||||||
|
Text as T,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Test whether the given string tokenizes into the given list of tokens.
|
/// Test whether the given string tokenizes into the given list of tokens.
|
||||||
|
@ -6,7 +6,7 @@ use std::fmt::Debug;
|
|||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use super::span::SpanVec;
|
use super::span::SpanVec;
|
||||||
|
|
||||||
/// A list of nodes which forms a tree together with the nodes' children.
|
/// A collection of nodes which form a tree together with the nodes' children.
|
||||||
pub type SyntaxTree = SpanVec<SyntaxNode>;
|
pub type SyntaxTree = SpanVec<SyntaxNode>;
|
||||||
|
|
||||||
/// A syntax node, which encompasses a single logical entity of parsed source
|
/// A syntax node, which encompasses a single logical entity of parsed source
|
||||||
@ -27,7 +27,7 @@ pub enum SyntaxNode {
|
|||||||
ToggleItalic,
|
ToggleItalic,
|
||||||
/// Bolder was enabled / disabled.
|
/// Bolder was enabled / disabled.
|
||||||
ToggleBolder,
|
ToggleBolder,
|
||||||
/// A subtree, typically a function invocation.
|
/// A dynamic node, create through function invocations in source code.
|
||||||
Dyn(Box<dyn DynamicNode>),
|
Dyn(Box<dyn DynamicNode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,13 +65,16 @@ pub trait DynamicNode: Debug + Layout {
|
|||||||
|
|
||||||
impl dyn DynamicNode {
|
impl dyn DynamicNode {
|
||||||
/// Downcast this dynamic node to a concrete node.
|
/// Downcast this dynamic node to a concrete node.
|
||||||
pub fn downcast<N>(&self) -> Option<&N> where N: DynamicNode + 'static {
|
pub fn downcast<T>(&self) -> Option<&T>
|
||||||
self.as_any().downcast_ref::<N>()
|
where
|
||||||
|
T: DynamicNode + 'static,
|
||||||
|
{
|
||||||
|
self.as_any().downcast_ref::<T>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for dyn DynamicNode {
|
impl PartialEq for dyn DynamicNode {
|
||||||
fn eq(&self, other: &dyn DynamicNode) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.dyn_eq(other)
|
self.dyn_eq(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +85,10 @@ impl Clone for Box<dyn DynamicNode> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DynamicNode for T where T: Debug + PartialEq + Clone + Layout + 'static {
|
impl<T> DynamicNode for T
|
||||||
|
where
|
||||||
|
T: Debug + PartialEq + Clone + Layout + 'static,
|
||||||
|
{
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,20 @@
|
|||||||
|
|
||||||
use fontdock::{FontStyle, FontWeight, FontWidth};
|
use fontdock::{FontStyle, FontWeight, FontWidth};
|
||||||
|
|
||||||
use crate::Feedback;
|
|
||||||
use crate::layout::prelude::*;
|
use crate::layout::prelude::*;
|
||||||
use crate::length::{Length, ScaleLength};
|
use crate::length::{Length, ScaleLength};
|
||||||
use crate::paper::Paper;
|
use crate::paper::Paper;
|
||||||
use super::span::Spanned;
|
use crate::Feedback;
|
||||||
use super::expr::*;
|
use super::expr::*;
|
||||||
|
use super::span::Spanned;
|
||||||
|
|
||||||
/// Value types are used to extract values from functions, tuples and
|
/// Value types are used to extract values from functions, tuples and
|
||||||
/// objects. They represent the value part of an argument.
|
/// objects. They represent the value part of an argument.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
/// ```typst
|
/// ```typst
|
||||||
/// [func: value, key=value]
|
/// [func: 12pt, key="these are both values"]
|
||||||
/// ^^^^^ ^^^^^
|
/// ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Value: Sized {
|
pub trait Value: Sized {
|
||||||
/// Try to parse this value from an expression.
|
/// Try to parse this value from an expression.
|
||||||
@ -53,8 +55,8 @@ macro_rules! match_value {
|
|||||||
match_value!(Expr, "expression", e => e);
|
match_value!(Expr, "expression", e => e);
|
||||||
match_value!(Ident, "identifier", Expr::Ident(i) => i);
|
match_value!(Ident, "identifier", Expr::Ident(i) => i);
|
||||||
match_value!(String, "string", Expr::Str(s) => s);
|
match_value!(String, "string", Expr::Str(s) => s);
|
||||||
match_value!(f64, "number", Expr::Number(n) => n);
|
|
||||||
match_value!(bool, "bool", Expr::Bool(b) => b);
|
match_value!(bool, "bool", Expr::Bool(b) => b);
|
||||||
|
match_value!(f64, "number", Expr::Number(n) => n);
|
||||||
match_value!(Length, "length", Expr::Length(l) => l);
|
match_value!(Length, "length", Expr::Length(l) => l);
|
||||||
match_value!(Tuple, "tuple", Expr::Tuple(t) => t);
|
match_value!(Tuple, "tuple", Expr::Tuple(t) => t);
|
||||||
match_value!(Object, "object", Expr::Object(o) => o);
|
match_value!(Object, "object", Expr::Object(o) => o);
|
||||||
@ -63,7 +65,7 @@ match_value!(ScaleLength, "number or length",
|
|||||||
Expr::Number(scale) => ScaleLength::Scaled(scale),
|
Expr::Number(scale) => ScaleLength::Scaled(scale),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements
|
/// A value type that matches identifiers and strings and implements
|
||||||
/// `Into<String>`.
|
/// `Into<String>`.
|
||||||
pub struct StringLike(pub String);
|
pub struct StringLike(pub String);
|
||||||
|
|
||||||
@ -101,7 +103,7 @@ macro_rules! ident_value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ident_value!(Dir, "direction", |s| match s {
|
ident_value!(Dir, "direction", |s| match s {
|
||||||
"ltr" => Some(LTT),
|
"ltr" => Some(LTR),
|
||||||
"rtl" => Some(RTL),
|
"rtl" => Some(RTL),
|
||||||
"ttb" => Some(TTB),
|
"ttb" => Some(TTB),
|
||||||
"btt" => Some(BTT),
|
"btt" => Some(BTT),
|
||||||
@ -109,11 +111,11 @@ ident_value!(Dir, "direction", |s| match s {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ident_value!(SpecAlign, "alignment", |s| match s {
|
ident_value!(SpecAlign, "alignment", |s| match s {
|
||||||
"left" => Some(SpecAlign::Left),
|
"left" => Some(Self::Left),
|
||||||
"right" => Some(SpecAlign::Right),
|
"right" => Some(Self::Right),
|
||||||
"top" => Some(SpecAlign::Top),
|
"top" => Some(Self::Top),
|
||||||
"bottom" => Some(SpecAlign::Bottom),
|
"bottom" => Some(Self::Bottom),
|
||||||
"center" => Some(SpecAlign::Center),
|
"center" => Some(Self::Center),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -127,7 +129,7 @@ impl Value for FontWeight {
|
|||||||
const MIN: u16 = 100;
|
const MIN: u16 = 100;
|
||||||
const MAX: u16 = 900;
|
const MAX: u16 = 900;
|
||||||
|
|
||||||
Some(FontWeight(if weight < MIN as f64 {
|
Some(Self(if weight < MIN as f64 {
|
||||||
error!(@f, expr.span, "the minimum font weight is {}", MIN);
|
error!(@f, expr.span, "the minimum font weight is {}", MIN);
|
||||||
MIN
|
MIN
|
||||||
} else if weight > MAX as f64 {
|
} else if weight > MAX as f64 {
|
||||||
@ -163,7 +165,7 @@ impl Value for FontWidth {
|
|||||||
const MIN: u16 = 1;
|
const MIN: u16 = 1;
|
||||||
const MAX: u16 = 9;
|
const MAX: u16 = 9;
|
||||||
|
|
||||||
FontWidth::new(if width < MIN as f64 {
|
Self::new(if width < MIN as f64 {
|
||||||
error!(@f, expr.span, "the minimum font width is {}", MIN);
|
error!(@f, expr.span, "the minimum font width is {}", MIN);
|
||||||
MIN
|
MIN
|
||||||
} else if width > MAX as f64 {
|
} else if width > MAX as f64 {
|
||||||
|
@ -5,21 +5,21 @@ use std::fs::{self, File};
|
|||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use fontdock::fs::{FsIndex, FsProvider};
|
||||||
|
use fontdock::FontLoader;
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
use raqote::{DrawTarget, Source, SolidSource, PathBuilder, Vector, Transform};
|
use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector};
|
||||||
use ttf_parser::OutlineBuilder;
|
use ttf_parser::OutlineBuilder;
|
||||||
|
|
||||||
use typstc::Typesetter;
|
use typstc::export::pdf;
|
||||||
use typstc::font::{DynProvider, SharedFontLoader};
|
use typstc::font::{DynProvider, SharedFontLoader};
|
||||||
use typstc::geom::{Size, Value4};
|
use typstc::geom::{Size, Value4};
|
||||||
use typstc::layout::MultiLayout;
|
|
||||||
use typstc::layout::elements::{LayoutElement, Shaped};
|
use typstc::layout::elements::{LayoutElement, Shaped};
|
||||||
|
use typstc::layout::MultiLayout;
|
||||||
use typstc::length::Length;
|
use typstc::length::Length;
|
||||||
use typstc::style::PageStyle;
|
|
||||||
use typstc::paper::PaperClass;
|
use typstc::paper::PaperClass;
|
||||||
use typstc::export::pdf;
|
use typstc::style::PageStyle;
|
||||||
use fontdock::FontLoader;
|
use typstc::Typesetter;
|
||||||
use fontdock::fs::{FsIndex, FsProvider};
|
|
||||||
|
|
||||||
const TEST_DIR: &str = "tests";
|
const TEST_DIR: &str = "tests";
|
||||||
const OUT_DIR: &str = "tests/out";
|
const OUT_DIR: &str = "tests/out";
|
||||||
@ -38,12 +38,7 @@ fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = path
|
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
.file_stem()
|
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
if filter.matches(&name) {
|
if filter.matches(&name) {
|
||||||
let src = fs::read_to_string(&path).unwrap();
|
let src = fs::read_to_string(&path).unwrap();
|
||||||
filtered.push((name, src));
|
filtered.push((name, src));
|
||||||
@ -93,18 +88,15 @@ fn test(
|
|||||||
let typeset = block_on(typesetter.typeset(src));
|
let typeset = block_on(typesetter.typeset(src));
|
||||||
let layouts = typeset.output;
|
let layouts = typeset.output;
|
||||||
for diagnostic in typeset.feedback.diagnostics {
|
for diagnostic in typeset.feedback.diagnostics {
|
||||||
println!(" {:?} {:?}: {}",
|
println!(
|
||||||
diagnostic.v.level,
|
" {:?} {:?}: {}",
|
||||||
diagnostic.span,
|
diagnostic.v.level, diagnostic.span, diagnostic.v.message,
|
||||||
diagnostic.v.message
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the PNG file.
|
|
||||||
let png_path = format!("{}/{}.png", OUT_DIR, name);
|
let png_path = format!("{}/{}.png", OUT_DIR, name);
|
||||||
render(&layouts, &loader, 3.0).write_png(png_path).unwrap();
|
render(&layouts, &loader, 3.0).write_png(png_path).unwrap();
|
||||||
|
|
||||||
// Write the PDF file.
|
|
||||||
let pdf_path = format!("{}/{}.pdf", OUT_DIR, name);
|
let pdf_path = format!("{}/{}.pdf", OUT_DIR, name);
|
||||||
let file = BufWriter::new(File::create(pdf_path).unwrap());
|
let file = BufWriter::new(File::create(pdf_path).unwrap());
|
||||||
pdf::export(&layouts, &loader, file).unwrap();
|
pdf::export(&layouts, &loader, file).unwrap();
|
||||||
@ -116,19 +108,19 @@ struct TestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TestFilter {
|
impl TestFilter {
|
||||||
fn new(args: impl Iterator<Item=String>) -> TestFilter {
|
fn new(args: impl Iterator<Item = String>) -> Self {
|
||||||
let mut filter = Vec::new();
|
let mut filter = Vec::new();
|
||||||
let mut perfect = false;
|
let mut perfect = false;
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"--nocapture" => {},
|
"--nocapture" => {}
|
||||||
"=" => perfect = true,
|
"=" => perfect = true,
|
||||||
_ => filter.push(arg),
|
_ => filter.push(arg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TestFilter { filter, perfect }
|
Self { filter, perfect }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(&self, name: &str) -> bool {
|
fn matches(&self, name: &str) -> bool {
|
||||||
@ -136,7 +128,7 @@ impl TestFilter {
|
|||||||
self.filter.iter().any(|p| name == p)
|
self.filter.iter().any(|p| name == p)
|
||||||
} else {
|
} else {
|
||||||
self.filter.is_empty()
|
self.filter.is_empty()
|
||||||
|| self.filter.iter().any(|p| name.contains(p))
|
|| self.filter.iter().any(|p| name.contains(p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,15 +166,13 @@ fn render(
|
|||||||
|
|
||||||
for &(pos, ref element) in &layout.elements.0 {
|
for &(pos, ref element) in &layout.elements.0 {
|
||||||
match element {
|
match element {
|
||||||
LayoutElement::Text(shaped) => {
|
LayoutElement::Text(shaped) => render_shaped(
|
||||||
render_shaped(
|
&mut surface,
|
||||||
&mut surface,
|
loader,
|
||||||
loader,
|
shaped,
|
||||||
shaped,
|
scale * pos + offset,
|
||||||
scale * pos + offset,
|
scale,
|
||||||
scale,
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +205,7 @@ fn render_shaped(
|
|||||||
let t = Transform::create_scale(s as f32, -s as f32)
|
let t = Transform::create_scale(s as f32, -s as f32)
|
||||||
.post_translate(Vector::new(x as f32, y as f32));
|
.post_translate(Vector::new(x as f32, y as f32));
|
||||||
|
|
||||||
surface.fill(
|
surface.fill(
|
||||||
&path.transform(&t),
|
&path.transform(&t),
|
||||||
&Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }),
|
&Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }),
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user