mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Fix space handling for multiline 🔨
This commit is contained in:
parent
0c87c0c5a5
commit
89205368c2
35
src/doc.rs
35
src/doc.rs
@ -15,10 +15,19 @@ pub struct Document {
|
|||||||
/// Default styles for a document.
|
/// Default styles for a document.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
/// The width and height of the paper.
|
/// The width of the paper.
|
||||||
pub paper_size: [Size; 2],
|
pub width: Size,
|
||||||
/// The [left, top, right, bottom] margins of the paper.
|
/// The height of the paper.
|
||||||
pub margins: [Size; 4],
|
pub height: Size,
|
||||||
|
|
||||||
|
/// The left margin of the paper.
|
||||||
|
pub margin_left: Size,
|
||||||
|
/// The top margin of the paper.
|
||||||
|
pub margin_top: Size,
|
||||||
|
/// The right margin of the paper.
|
||||||
|
pub margin_right: Size,
|
||||||
|
/// The bottom margin of the paper.
|
||||||
|
pub margin_bottom: Size,
|
||||||
|
|
||||||
/// A fallback list of font families to use.
|
/// A fallback list of font families to use.
|
||||||
pub font_families: Vec<String>,
|
pub font_families: Vec<String>,
|
||||||
@ -31,9 +40,15 @@ pub struct Style {
|
|||||||
impl Default for Style {
|
impl Default for Style {
|
||||||
fn default() -> Style {
|
fn default() -> Style {
|
||||||
Style {
|
Style {
|
||||||
// A4 paper with 1.5 cm margins in all directions
|
// A4 paper
|
||||||
paper_size: [Size::from_mm(210.0), Size::from_mm(297.0)],
|
width: Size::from_mm(210.0),
|
||||||
margins: [Size::from_cm(2.5); 4],
|
height: Size::from_mm(297.0),
|
||||||
|
|
||||||
|
// A bit more on top and bottom
|
||||||
|
margin_left: Size::from_cm(2.5),
|
||||||
|
margin_top: Size::from_cm(3.0),
|
||||||
|
margin_right: Size::from_cm(2.5),
|
||||||
|
margin_bottom: Size::from_cm(3.0),
|
||||||
|
|
||||||
// Default font family
|
// Default font family
|
||||||
font_families: (&[
|
font_families: (&[
|
||||||
@ -48,8 +63,10 @@ impl Default for Style {
|
|||||||
/// A page with text contents in a document.
|
/// A page with text contents in a document.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
/// The width and height of the page.
|
/// The width of the page.
|
||||||
pub size: [Size; 2],
|
pub width: Size,
|
||||||
|
/// The height of the page.
|
||||||
|
pub height: Size,
|
||||||
/// Text content on the page.
|
/// Text content on the page.
|
||||||
pub text: Vec<Text>,
|
pub text: Vec<Text>,
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ pub struct Engine<'s> {
|
|||||||
|
|
||||||
impl<'s> Engine<'s> {
|
impl<'s> Engine<'s> {
|
||||||
/// Create a new generator from a syntax tree.
|
/// Create a new generator from a syntax tree.
|
||||||
pub fn new(tree: &'s SyntaxTree<'s>) -> Engine<'s> {
|
pub fn new(tree: &'s SyntaxTree<'s>, style: Style) -> Engine<'s> {
|
||||||
Engine {
|
Engine {
|
||||||
style: Style::default(),
|
style,
|
||||||
tree,
|
tree,
|
||||||
fonts: Vec::new(),
|
fonts: Vec::new(),
|
||||||
active_font: 0,
|
active_font: 0,
|
||||||
@ -48,9 +48,9 @@ impl<'s> Engine<'s> {
|
|||||||
|
|
||||||
// Move cursor to top-left position
|
// Move cursor to top-left position
|
||||||
self.text_commands.push(TextCommand::Move(
|
self.text_commands.push(TextCommand::Move(
|
||||||
self.style.margins[0],
|
self.style.margin_left,
|
||||||
self.style.paper_size[1] - self.style.margins[1])
|
self.style.height - self.style.margin_top
|
||||||
);
|
));
|
||||||
|
|
||||||
// Set the current font
|
// Set the current font
|
||||||
self.text_commands.push(TextCommand::SetFont(0, self.style.font_size));
|
self.text_commands.push(TextCommand::SetFont(0, self.style.font_size));
|
||||||
@ -70,7 +70,8 @@ impl<'s> Engine<'s> {
|
|||||||
|
|
||||||
// Create a page from the contents.
|
// Create a page from the contents.
|
||||||
let page = Page {
|
let page = Page {
|
||||||
size: self.style.paper_size,
|
width: self.style.width,
|
||||||
|
height: self.style.height,
|
||||||
text: vec![Text {
|
text: vec![Text {
|
||||||
commands: self.text_commands,
|
commands: self.text_commands,
|
||||||
}],
|
}],
|
||||||
@ -83,15 +84,10 @@ impl<'s> Engine<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_word(&mut self, word: &str) {
|
fn write_word(&mut self, word: &str) {
|
||||||
let max_width = self.style.paper_size[0] - 2 * self.style.margins[0];
|
|
||||||
|
|
||||||
let font = &self.fonts[self.active_font];
|
let font = &self.fonts[self.active_font];
|
||||||
let width = word.chars()
|
|
||||||
.map(|c| font.widths[font.map(c) as usize] * self.style.font_size)
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
|
let width = self.width(word);
|
||||||
if self.current_width + width > max_width {
|
if self.would_overflow(width) {
|
||||||
let vertical_move = - self.style.font_size
|
let vertical_move = - self.style.font_size
|
||||||
* self.style.line_spacing
|
* self.style.line_spacing
|
||||||
* font.metrics.ascender;
|
* font.metrics.ascender;
|
||||||
@ -107,10 +103,29 @@ impl<'s> Engine<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_space(&mut self) {
|
fn write_space(&mut self) {
|
||||||
if !self.current_line.is_empty() {
|
let space_width = self.width(" ");
|
||||||
self.write_word(" ");
|
|
||||||
|
if !self.would_overflow(space_width) && !self.current_line.is_empty() {
|
||||||
|
self.text_commands.push(TextCommand::Text(" ".to_owned()));
|
||||||
|
self.current_line.push_str(" ");
|
||||||
|
self.current_width += space_width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn width(&self, word: &str) -> Size {
|
||||||
|
let font = &self.fonts[self.active_font];
|
||||||
|
word.chars()
|
||||||
|
.map(|c| font.widths[font.map(c) as usize] * self.style.font_size)
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn would_overflow(&self, width: Size) -> bool {
|
||||||
|
let max_width = self.style.width
|
||||||
|
- self.style.margin_left
|
||||||
|
- self.style.margin_right;
|
||||||
|
|
||||||
|
self.current_width + width > max_width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type used for parsing.
|
/// Result type used for parsing.
|
||||||
|
20
src/lib.rs
20
src/lib.rs
@ -37,20 +37,32 @@ use std::error;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use crate::syntax::SyntaxTree;
|
use crate::syntax::SyntaxTree;
|
||||||
use crate::doc::Document;
|
use crate::doc::{Document, Style};
|
||||||
|
|
||||||
|
|
||||||
/// Emits various compiled intermediates from source code.
|
/// Emits various compiled intermediates from source code.
|
||||||
pub struct Compiler<'s> {
|
pub struct Compiler<'s> {
|
||||||
/// The source code of the document.
|
/// The source code of the document.
|
||||||
source: &'s str,
|
source: &'s str,
|
||||||
|
/// Style for typesetting.
|
||||||
|
style: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Compiler<'s> {
|
impl<'s> Compiler<'s> {
|
||||||
/// Create a new compiler from a document.
|
/// Create a new compiler from a document.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(source: &'s str) -> Compiler<'s> {
|
pub fn new(source: &'s str) -> Compiler<'s> {
|
||||||
Compiler { source }
|
Compiler {
|
||||||
|
source,
|
||||||
|
style: Style::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the default style for typesetting.
|
||||||
|
#[inline]
|
||||||
|
pub fn style(&mut self, style: Style) -> &mut Self {
|
||||||
|
self.style = style;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator over the tokens of the document.
|
/// Return an iterator over the tokens of the document.
|
||||||
@ -69,7 +81,7 @@ impl<'s> Compiler<'s> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn typeset(&self) -> Result<Document, Error> {
|
pub fn typeset(&self) -> Result<Document, Error> {
|
||||||
let tree = self.parse()?;
|
let tree = self.parse()?;
|
||||||
Engine::new(&tree).typeset().map_err(Into::into)
|
Engine::new(&tree, self.style.clone()).typeset().map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the document as a _PDF_, returning how many bytes were written.
|
/// Write the document as a _PDF_, returning how many bytes were written.
|
||||||
@ -159,7 +171,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn long() {
|
fn long_styled() {
|
||||||
test("wikipedia", r#"
|
test("wikipedia", r#"
|
||||||
Typesetting is the composition of text by means of arranging physical types or the
|
Typesetting is the composition of text by means of arranging physical types or the
|
||||||
digital equivalents. Stored letters and other symbols (called sorts in mechanical
|
digital equivalents. Stored letters and other symbols (called sorts in mechanical
|
||||||
|
@ -122,11 +122,11 @@ impl<'a, W: Write> PdfCreator<'a, W> {
|
|||||||
// The page objects
|
// The page objects
|
||||||
let mut id = self.offsets.pages.0;
|
let mut id = self.offsets.pages.0;
|
||||||
for page in &self.doc.pages {
|
for page in &self.doc.pages {
|
||||||
let width = page.size[0].to_points();
|
|
||||||
let height = page.size[1].to_points();
|
|
||||||
|
|
||||||
self.writer.write_obj(id, Page::new(self.offsets.page_tree)
|
self.writer.write_obj(id, Page::new(self.offsets.page_tree)
|
||||||
.media_box(Rect::new(0.0, 0.0, width, height))
|
.media_box(Rect::new(
|
||||||
|
0.0, 0.0,
|
||||||
|
page.width.to_points(), page.height.to_points())
|
||||||
|
)
|
||||||
.contents(self.offsets.contents.0 ..= self.offsets.contents.1)
|
.contents(self.offsets.contents.0 ..= self.offsets.contents.1)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user