Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions native/folio_nif/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::str::FromStr;

use ecow::{eco_format, EcoString};
use typst::engine::Engine;
use typst::foundations::{Bytes, Content, NativeElement, OneOrMultiple, Smart};
use typst::foundations::{Bytes, Content, NativeElement, OneOrMultiple, Smart, Unlabellable};
use std::sync::Arc;
use typst::layout::{
Abs, AlignElem, Alignment, Axes, BlockBody, BlockElem, Celled, ColbreakElem,
Expand Down Expand Up @@ -232,6 +232,7 @@ pub fn build_content(engine: &mut Engine, nodes: &[ExContent]) -> Content {
(ExContent::Parbreak(_), _) | (_, ExContent::Parbreak(_)) => false,
(ExContent::Pagebreak(_), _) | (_, ExContent::Pagebreak(_)) => false,
(ExContent::Colbreak(_), _) | (_, ExContent::Colbreak(_)) => false,
(_, ExContent::Label(_)) => false,
// Between two paragraph-like blocks
(p, n) if is_paragraph_like(p) && is_paragraph_like(n) => true,
// After a paragraph-like block and before another block
Expand All @@ -244,11 +245,23 @@ pub fn build_content(engine: &mut Engine, nodes: &[ExContent]) -> Content {
seq.push(ParbreakElem::shared().clone());
}
}
seq.push(convert_node(engine, node));
push_or_attach_label(engine, &mut seq, node);
}
Content::sequence(seq)
}

fn push_or_attach_label(engine: &mut Engine, seq: &mut Vec<Content>, node: &ExContent) {
if let ExContent::Label(label) = node {
if let Some(lbl) = typst::foundations::Label::new(PicoStr::intern(&label.name)) {
if let Some(elem) = seq.iter_mut().rev().find(|n| !n.can::<dyn Unlabellable>()) {
*elem = std::mem::take(elem).labelled(lbl);
return;
}
}
}
seq.push(convert_node(engine, node));
}

fn is_block(node: &ExContent) -> bool {
match node {
ExContent::Heading(_) | ExContent::Paragraph(_) | ExContent::List(_)
Expand All @@ -275,7 +288,11 @@ fn is_paragraph_like(node: &ExContent) -> bool {
}

fn cc(engine: &mut Engine, nodes: &[ExContent]) -> Content {
Content::sequence(nodes.iter().map(|n| convert_node(engine, n)).collect::<Vec<_>>())
let mut seq: Vec<Content> = Vec::with_capacity(nodes.len());
for n in nodes {
push_or_attach_label(engine, &mut seq, n);
}
Content::sequence(seq)
}

fn convert_node(engine: &mut Engine, node: &ExContent) -> Content {
Expand Down
65 changes: 45 additions & 20 deletions native/folio_nif/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, LazyLock, Mutex};

use typst::comemo::{Track, TrackedMut};
use typst::comemo::{Constraint, Track, TrackedMut};
use typst::diag::{FileError, FileResult};
use typst::engine::{Engine, Route, Sink, Traced};
use typst::foundations::{
Bytes, Content, Context, Datetime, Derived, Duration, NativeElement, Smart, StyleChain, Styles,
Target, TargetElem,
Bytes, Content, Context, Datetime, Derived, Duration, NativeElement, Output, Smart,
StyleChain, Styles, Target, TargetElem,
};
use typst::introspection::EmptyIntrospector;
use typst::introspection::{EmptyIntrospector, Introspector, MAX_ITERS};
use typst::layout::{Abs, Margin, Sides};
use typst::layout::PageElem;
use typst::loading::{DataSource, LoadSource, Loaded};
Expand Down Expand Up @@ -147,31 +147,56 @@ impl FolioWorld {
}

fn layout(&self, content: &[ExContent]) -> Result<typst_layout::PagedDocument, String> {
let mut sink = Sink::new();
let introspector = EmptyIntrospector;
let traced = Traced::default();

let mut engine = Engine {
routines: &typst::ROUTINES,
world: Track::track(self),
introspector: typst::utils::Protected::new(introspector.track()),
traced: traced.track(),
sink: sink.track_mut(),
route: Route::root(),
let empty = EmptyIntrospector;

let mut build_sink = Sink::new();
let (body, user_styles) = {
let mut engine = Engine {
routines: &typst::ROUTINES,
world: Track::track(self),
introspector: typst::utils::Protected::new(empty.track()),
traced: traced.track(),
sink: build_sink.track_mut(),
route: Route::root(),
};
let body = build_content(&mut engine, content);
let mut styles = typst::foundations::Styles::new();
apply_styles(&mut styles, &self.styles, &mut engine);
(body, styles)
};

let body = build_content(&mut engine, content);

let lib = &GLOBAL.library;
let base = StyleChain::new(&lib.styles);
let mut user_styles = typst::foundations::Styles::new();
apply_styles(&mut user_styles, &self.styles, &mut engine);
let target_style: Styles = TargetElem::target.set(Target::Paged).wrap().into();
let chained = base.chain(&target_style);
let styles = chained.chain(&user_styles);

layout_document(&mut engine, &body, styles)
.map_err(|e| format!("Layout error: {:?}", e))
let mut prev: Option<typst_layout::PagedDocument> = None;
for _ in 0..MAX_ITERS {
let constraint = Constraint::new();
let introspector: &dyn Introspector = match &prev {
Some(doc) => Output::introspector(doc),
None => &empty,
};
let mut iter_sink = Sink::new();
let mut engine = Engine {
routines: &typst::ROUTINES,
world: Track::track(self),
introspector: typst::utils::Protected::new(introspector.track_with(&constraint)),
traced: traced.track(),
sink: iter_sink.track_mut(),
route: Route::root(),
};
let doc = layout_document(&mut engine, &body, styles)
.map_err(|e| format!("Layout error: {:?}", e))?;
drop(engine);
if constraint.validate(Output::introspector(&doc)) {
return Ok(doc);
}
prev = Some(doc);
}
prev.ok_or_else(|| "Layout error: introspection did not converge".to_string())
}

pub fn eval_math(engine: &mut Engine, math_str: &str, block: bool) -> Content {
Expand Down
14 changes: 14 additions & 0 deletions test/folio_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ defmodule FolioTest do
assert is_binary(svg)
assert String.starts_with?(svg, "<svg")
end

test "page_numbering advances the counter across pages" do
import Folio.DSL

assert {:ok, [page1, page2 | _] = pages} =
Folio.to_svg(
[text("body"), pagebreak(), text("body")],
styles: [Folio.Styles.page_numbering("1")]
)

assert length(pages) >= 2
# Body content should be the same, but page numbering should differ
assert page1 != page2
end
end

describe "to_png/2" do
Expand Down