mirror of
https://github.com/typst/typst
synced 2025-05-15 01:25:28 +08:00
Write PDF outline
This commit is contained in:
parent
428c55b6ee
commit
9bdc4a7de0
@ -211,6 +211,7 @@ fn items() -> LangItems {
|
|||||||
},
|
},
|
||||||
bibliography_keys: meta::BibliographyElem::keys,
|
bibliography_keys: meta::BibliographyElem::keys,
|
||||||
heading: |level, title| meta::HeadingElem::new(title).with_level(level).pack(),
|
heading: |level, title| meta::HeadingElem::new(title).with_level(level).pack(),
|
||||||
|
heading_func: meta::HeadingElem::func(),
|
||||||
list_item: |body| layout::ListItem::new(body).pack(),
|
list_item: |body| layout::ListItem::new(body).pack(),
|
||||||
enum_item: |number, body| {
|
enum_item: |number, body| {
|
||||||
let mut elem = layout::EnumItem::new(body);
|
let mut elem = layout::EnumItem::new(body);
|
||||||
|
@ -173,14 +173,14 @@ impl Synthesize for FigureElem {
|
|||||||
// Determine the figure's kind.
|
// Determine the figure's kind.
|
||||||
let kind = match self.kind(styles) {
|
let kind = match self.kind(styles) {
|
||||||
Smart::Auto => self
|
Smart::Auto => self
|
||||||
.find_figurable(vt, styles)
|
.find_figurable(styles)
|
||||||
.map(|elem| FigureKind::Elem(elem.func()))
|
.map(|elem| FigureKind::Elem(elem.func()))
|
||||||
.unwrap_or_else(|| FigureKind::Elem(ImageElem::func())),
|
.unwrap_or_else(|| FigureKind::Elem(ImageElem::func())),
|
||||||
Smart::Custom(kind) => kind,
|
Smart::Custom(kind) => kind,
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = match &kind {
|
let content = match &kind {
|
||||||
FigureKind::Elem(func) => self.find_of_elem(vt, *func),
|
FigureKind::Elem(func) => self.find_of_elem(*func),
|
||||||
FigureKind::Name(_) => None,
|
FigureKind::Name(_) => None,
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|| self.body());
|
.unwrap_or_else(|| self.body());
|
||||||
@ -303,9 +303,9 @@ impl Refable for FigureElem {
|
|||||||
impl FigureElem {
|
impl FigureElem {
|
||||||
/// Determines the type of the figure by looking at the content, finding all
|
/// Determines the type of the figure by looking at the content, finding all
|
||||||
/// [`Figurable`] elements and sorting them by priority then returning the highest.
|
/// [`Figurable`] elements and sorting them by priority then returning the highest.
|
||||||
pub fn find_figurable(&self, vt: &Vt, styles: StyleChain) -> Option<Content> {
|
pub fn find_figurable(&self, styles: StyleChain) -> Option<Content> {
|
||||||
self.body()
|
self.body()
|
||||||
.query(vt.introspector, Selector::can::<dyn Figurable>())
|
.query(Selector::can::<dyn Figurable>())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.max_by_key(|elem| elem.with::<dyn Figurable>().unwrap().priority(styles))
|
.max_by_key(|elem| elem.with::<dyn Figurable>().unwrap().priority(styles))
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -313,9 +313,9 @@ impl FigureElem {
|
|||||||
|
|
||||||
/// Finds the element with the given function in the figure's content.
|
/// Finds the element with the given function in the figure's content.
|
||||||
/// Returns `None` if no element with the given function is found.
|
/// Returns `None` if no element with the given function is found.
|
||||||
pub fn find_of_elem(&self, vt: &Vt, func: ElemFunc) -> Option<Content> {
|
pub fn find_of_elem(&self, func: ElemFunc) -> Option<Content> {
|
||||||
self.body()
|
self.body()
|
||||||
.query(vt.introspector, Selector::Elem(func, None))
|
.query(Selector::Elem(func, None))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.cloned()
|
.cloned()
|
||||||
|
@ -23,9 +23,9 @@ pub use typst::geom::*;
|
|||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::model::{
|
pub use typst::model::{
|
||||||
element, Behave, Behaviour, Construct, Content, ElemFunc, Element, Finalize, Fold,
|
element, Behave, Behaviour, Construct, Content, ElemFunc, Element, Finalize, Fold,
|
||||||
Introspector, Label, Locatable, LocatableSelector, Location, MetaElem, Resolve,
|
Introspector, Label, Locatable, LocatableSelector, Location, MetaElem, PlainText,
|
||||||
Selector, Set, Show, StabilityProvider, StyleChain, StyleVec, Styles, Synthesize,
|
Resolve, Selector, Set, Show, StabilityProvider, StyleChain, StyleVec, Styles,
|
||||||
Unlabellable, Vt,
|
Synthesize, Unlabellable, Vt,
|
||||||
};
|
};
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use typst::syntax::{Span, Spanned};
|
pub use typst::syntax::{Span, Spanned};
|
||||||
|
@ -5,7 +5,7 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// Display: Space
|
/// Display: Space
|
||||||
/// Category: text
|
/// Category: text
|
||||||
#[element(Unlabellable, Behave)]
|
#[element(Behave, Unlabellable, PlainText)]
|
||||||
pub struct SpaceElem {}
|
pub struct SpaceElem {}
|
||||||
|
|
||||||
impl Behave for SpaceElem {
|
impl Behave for SpaceElem {
|
||||||
@ -16,6 +16,12 @@ impl Behave for SpaceElem {
|
|||||||
|
|
||||||
impl Unlabellable for SpaceElem {}
|
impl Unlabellable for SpaceElem {}
|
||||||
|
|
||||||
|
impl PlainText for SpaceElem {
|
||||||
|
fn plain_text(&self, text: &mut EcoString) {
|
||||||
|
text.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Inserts a line break.
|
/// Inserts a line break.
|
||||||
///
|
///
|
||||||
/// Advances the paragraph to the next line. A single trailing line break at the
|
/// Advances the paragraph to the next line. A single trailing line break at the
|
||||||
|
@ -40,7 +40,7 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// Display: Text
|
/// Display: Text
|
||||||
/// Category: text
|
/// Category: text
|
||||||
#[element(Construct)]
|
#[element(Construct, PlainText)]
|
||||||
pub struct TextElem {
|
pub struct TextElem {
|
||||||
/// A prioritized sequence of font families.
|
/// A prioritized sequence of font families.
|
||||||
///
|
///
|
||||||
@ -497,6 +497,12 @@ impl Construct for TextElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PlainText for TextElem {
|
||||||
|
fn plain_text(&self, text: &mut EcoString) {
|
||||||
|
text.push_str(&self.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A lowercased font family like "arial".
|
/// A lowercased font family like "arial".
|
||||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct FontFamily(EcoString);
|
pub struct FontFamily(EcoString);
|
||||||
|
@ -72,6 +72,8 @@ pub struct LangItems {
|
|||||||
) -> Vec<(EcoString, Option<EcoString>)>,
|
) -> Vec<(EcoString, Option<EcoString>)>,
|
||||||
/// A section heading: `= Introduction`.
|
/// A section heading: `= Introduction`.
|
||||||
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
|
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
|
||||||
|
/// The heading function.
|
||||||
|
pub heading_func: ElemFunc,
|
||||||
/// An item in a bullet list: `- ...`.
|
/// An item in a bullet list: `- ...`.
|
||||||
pub list_item: fn(body: Content) -> Content,
|
pub list_item: fn(body: Content) -> Content,
|
||||||
/// An item in an enumeration (numbered list): `+ ...` or `1. ...`.
|
/// An item in an enumeration (numbered list): `+ ...` or `1. ...`.
|
||||||
|
@ -13,7 +13,6 @@ use pdf_writer::types::Direction;
|
|||||||
use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
|
use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
|
||||||
use xmp_writer::{LangId, RenditionClass, XmpWriter};
|
use xmp_writer::{LangId, RenditionClass, XmpWriter};
|
||||||
|
|
||||||
use self::outline::HeadingNode;
|
|
||||||
use self::page::Page;
|
use self::page::Page;
|
||||||
use crate::doc::{Document, Lang};
|
use crate::doc::{Document, Lang};
|
||||||
use crate::font::Font;
|
use crate::font::Font;
|
||||||
@ -54,7 +53,6 @@ pub struct PdfContext<'a> {
|
|||||||
image_map: Remapper<Image>,
|
image_map: Remapper<Image>,
|
||||||
glyph_sets: HashMap<Font, HashSet<u16>>,
|
glyph_sets: HashMap<Font, HashSet<u16>>,
|
||||||
languages: HashMap<Lang, usize>,
|
languages: HashMap<Lang, usize>,
|
||||||
heading_tree: Vec<HeadingNode>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PdfContext<'a> {
|
impl<'a> PdfContext<'a> {
|
||||||
@ -76,36 +74,12 @@ impl<'a> PdfContext<'a> {
|
|||||||
image_map: Remapper::new(),
|
image_map: Remapper::new(),
|
||||||
glyph_sets: HashMap::new(),
|
glyph_sets: HashMap::new(),
|
||||||
languages: HashMap::new(),
|
languages: HashMap::new(),
|
||||||
heading_tree: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the document catalog.
|
/// Write the document catalog.
|
||||||
fn write_catalog(ctx: &mut PdfContext) {
|
fn write_catalog(ctx: &mut PdfContext) {
|
||||||
// Build the outline tree.
|
|
||||||
let outline_root_id = (!ctx.heading_tree.is_empty()).then(|| ctx.alloc.bump());
|
|
||||||
let outline_start_ref = ctx.alloc;
|
|
||||||
let len = ctx.heading_tree.len();
|
|
||||||
let mut prev_ref = None;
|
|
||||||
|
|
||||||
for (i, node) in std::mem::take(&mut ctx.heading_tree).iter().enumerate() {
|
|
||||||
prev_ref = Some(outline::write_outline_item(
|
|
||||||
ctx,
|
|
||||||
node,
|
|
||||||
outline_root_id.unwrap(),
|
|
||||||
prev_ref,
|
|
||||||
i + 1 == len,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(outline_root_id) = outline_root_id {
|
|
||||||
let mut outline_root = ctx.writer.outline(outline_root_id);
|
|
||||||
outline_root.first(outline_start_ref);
|
|
||||||
outline_root.last(Ref::new(ctx.alloc.get() - 1));
|
|
||||||
outline_root.count(ctx.heading_tree.len() as i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
let lang = ctx
|
let lang = ctx
|
||||||
.languages
|
.languages
|
||||||
.iter()
|
.iter()
|
||||||
@ -118,6 +92,9 @@ fn write_catalog(ctx: &mut PdfContext) {
|
|||||||
Direction::L2R
|
Direction::L2R
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Write the outline tree.
|
||||||
|
let outline_root_id = outline::write_outline(ctx);
|
||||||
|
|
||||||
// Write the document information.
|
// Write the document information.
|
||||||
let mut info = ctx.writer.document_info(ctx.alloc.bump());
|
let mut info = ctx.writer.document_info(ctx.alloc.bump());
|
||||||
let mut xmp = XmpWriter::new();
|
let mut xmp = XmpWriter::new();
|
||||||
|
@ -1,32 +1,76 @@
|
|||||||
use ecow::EcoString;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use pdf_writer::{Finish, Ref, TextStr};
|
use pdf_writer::{Finish, Ref, TextStr};
|
||||||
|
|
||||||
use super::{AbsExt, PdfContext, RefExt};
|
use super::{AbsExt, PdfContext, RefExt};
|
||||||
use crate::geom::{Abs, Point};
|
use crate::geom::Abs;
|
||||||
|
use crate::model::Content;
|
||||||
|
use crate::util::NonZeroExt;
|
||||||
|
|
||||||
|
/// Construct the outline for the document.
|
||||||
|
pub fn write_outline(ctx: &mut PdfContext) -> Option<Ref> {
|
||||||
|
let mut tree: Vec<HeadingNode> = vec![];
|
||||||
|
for heading in ctx.introspector.query(&item!(heading_func).select()) {
|
||||||
|
let leaf = HeadingNode::leaf(heading);
|
||||||
|
if let Some(last) = tree.last_mut() {
|
||||||
|
if last.try_insert(leaf.clone(), NonZeroUsize::ONE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.push(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let root_id = ctx.alloc.bump();
|
||||||
|
let start_ref = ctx.alloc;
|
||||||
|
let len = tree.len();
|
||||||
|
|
||||||
|
let mut prev_ref = None;
|
||||||
|
for (i, node) in tree.iter().enumerate() {
|
||||||
|
prev_ref = Some(write_outline_item(ctx, node, root_id, prev_ref, i + 1 == len));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.writer
|
||||||
|
.outline(root_id)
|
||||||
|
.first(start_ref)
|
||||||
|
.last(Ref::new(ctx.alloc.get() - 1))
|
||||||
|
.count(tree.len() as i32);
|
||||||
|
|
||||||
|
Some(root_id)
|
||||||
|
}
|
||||||
|
|
||||||
/// A heading in the outline panel.
|
/// A heading in the outline panel.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HeadingNode {
|
struct HeadingNode {
|
||||||
pub content: EcoString,
|
element: Content,
|
||||||
pub level: usize,
|
level: NonZeroUsize,
|
||||||
pub position: Point,
|
children: Vec<HeadingNode>,
|
||||||
pub page: Ref,
|
|
||||||
pub children: Vec<HeadingNode>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeadingNode {
|
impl HeadingNode {
|
||||||
pub fn len(&self) -> usize {
|
fn leaf(element: Content) -> Self {
|
||||||
|
HeadingNode {
|
||||||
|
level: element.expect_field::<NonZeroUsize>("level"),
|
||||||
|
element,
|
||||||
|
children: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
1 + self.children.iter().map(Self::len).sum::<usize>()
|
1 + self.children.iter().map(Self::len).sum::<usize>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
fn try_insert(&mut self, child: Self, level: NonZeroUsize) -> bool {
|
||||||
pub fn try_insert(&mut self, child: Self, level: usize) -> bool {
|
|
||||||
if level >= child.level {
|
if level >= child.level {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(last) = self.children.last_mut() {
|
if let Some(last) = self.children.last_mut() {
|
||||||
if last.try_insert(child.clone(), level + 1) {
|
if last.try_insert(child.clone(), level.saturating_add(1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,7 +81,7 @@ impl HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write an outline item and all its children.
|
/// Write an outline item and all its children.
|
||||||
pub fn write_outline_item(
|
fn write_outline_item(
|
||||||
ctx: &mut PdfContext,
|
ctx: &mut PdfContext,
|
||||||
node: &HeadingNode,
|
node: &HeadingNode,
|
||||||
parent_ref: Ref,
|
parent_ref: Ref,
|
||||||
@ -65,12 +109,19 @@ pub fn write_outline_item(
|
|||||||
outline.count(-(node.children.len() as i32));
|
outline.count(-(node.children.len() as i32));
|
||||||
}
|
}
|
||||||
|
|
||||||
outline.title(TextStr(&node.content));
|
outline.title(TextStr(node.element.plain_text().trim()));
|
||||||
outline.dest_direct().page(node.page).xyz(
|
|
||||||
node.position.x.to_f32(),
|
let loc = node.element.location().unwrap();
|
||||||
(node.position.y + Abs::pt(3.0)).to_f32(),
|
let pos = ctx.introspector.position(loc);
|
||||||
None,
|
let index = pos.page.get() - 1;
|
||||||
);
|
if let Some(&height) = ctx.page_heights.get(index) {
|
||||||
|
let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero());
|
||||||
|
outline.dest_direct().page(ctx.page_refs[index]).xyz(
|
||||||
|
pos.point.x.to_f32(),
|
||||||
|
height - y.to_f32(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
outline.finish();
|
outline.finish();
|
||||||
|
|
||||||
|
@ -96,16 +96,16 @@ pub fn analyze_labels(
|
|||||||
|
|
||||||
// Labels in the document.
|
// Labels in the document.
|
||||||
for elem in introspector.all() {
|
for elem in introspector.all() {
|
||||||
let Some(label) = elem.label() else { continue };
|
let Some(label) = elem.label().cloned() else { continue };
|
||||||
let details = elem
|
let details = elem
|
||||||
.field("caption")
|
.field("caption")
|
||||||
.or_else(|| elem.field("body"))
|
|
||||||
.and_then(|field| match field {
|
.and_then(|field| match field {
|
||||||
Value::Content(content) => Some(content),
|
Value::Content(content) => Some(content),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.and_then(|content| (items.text_str)(&content));
|
.unwrap_or(elem)
|
||||||
output.push((label.clone(), details));
|
.plain_text();
|
||||||
|
output.push((label, Some(details)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let split = output.len();
|
let split = output.len();
|
||||||
|
@ -3,12 +3,12 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
use comemo::{Prehashed, Tracked};
|
use comemo::Prehashed;
|
||||||
use ecow::{eco_format, EcoString, EcoVec};
|
use ecow::{eco_format, EcoString, EcoVec};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Introspector, Label,
|
element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable,
|
||||||
Locatable, Location, Recipe, Selector, Style, Styles, Synthesize,
|
Location, PlainText, Recipe, Selector, Style, Styles, Synthesize,
|
||||||
};
|
};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::doc::Meta;
|
use crate::doc::Meta;
|
||||||
@ -359,52 +359,53 @@ impl Content {
|
|||||||
|
|
||||||
/// Queries the content tree for all elements that match the given selector.
|
/// Queries the content tree for all elements that match the given selector.
|
||||||
///
|
///
|
||||||
/// # Show rules
|
|
||||||
/// Elements produced in `show` rules will not be included in the results.
|
/// Elements produced in `show` rules will not be included in the results.
|
||||||
pub fn query(
|
pub fn query(&self, selector: Selector) -> Vec<&Content> {
|
||||||
&self,
|
|
||||||
introspector: Tracked<Introspector>,
|
|
||||||
selector: Selector,
|
|
||||||
) -> Vec<&Content> {
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
self.query_into(introspector, &selector, &mut results);
|
self.traverse(&mut |element| {
|
||||||
|
if selector.matches(element) {
|
||||||
|
results.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the content tree for all elements that match the given selector
|
/// Extracts the plain text of this content.
|
||||||
/// and stores the results inside of the `results` vec.
|
pub fn plain_text(&self) -> EcoString {
|
||||||
fn query_into<'a>(
|
let mut text = EcoString::new();
|
||||||
&'a self,
|
self.traverse(&mut |element| {
|
||||||
introspector: Tracked<Introspector>,
|
if let Some(textable) = element.with::<dyn PlainText>() {
|
||||||
selector: &Selector,
|
textable.plain_text(&mut text);
|
||||||
results: &mut Vec<&'a Content>,
|
}
|
||||||
) {
|
});
|
||||||
if selector.matches(self) {
|
text
|
||||||
results.push(self);
|
}
|
||||||
}
|
|
||||||
|
/// Traverse this content.
|
||||||
|
fn traverse<'a, F>(&'a self, f: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&'a Content),
|
||||||
|
{
|
||||||
|
f(self);
|
||||||
|
|
||||||
for attr in &self.attrs {
|
for attr in &self.attrs {
|
||||||
match attr {
|
match attr {
|
||||||
Attr::Child(child) => child.query_into(introspector, selector, results),
|
Attr::Child(child) => child.traverse(f),
|
||||||
Attr::Value(value) => walk_value(introspector, value, selector, results),
|
Attr::Value(value) => walk_value(value, f),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walks a given value to find any content that matches the selector.
|
/// Walks a given value to find any content that matches the selector.
|
||||||
fn walk_value<'a>(
|
fn walk_value<'a, F>(value: &'a Value, f: &mut F)
|
||||||
introspector: Tracked<Introspector>,
|
where
|
||||||
value: &'a Value,
|
F: FnMut(&'a Content),
|
||||||
selector: &Selector,
|
{
|
||||||
results: &mut Vec<&'a Content>,
|
|
||||||
) {
|
|
||||||
match value {
|
match value {
|
||||||
Value::Content(content) => {
|
Value::Content(content) => content.traverse(f),
|
||||||
content.query_into(introspector, selector, results)
|
|
||||||
}
|
|
||||||
Value::Array(array) => {
|
Value::Array(array) => {
|
||||||
for value in array {
|
for value in array {
|
||||||
walk_value(introspector, value, selector, results);
|
walk_value(value, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -151,3 +151,9 @@ impl Debug for Label {
|
|||||||
|
|
||||||
/// Indicates that an element cannot be labelled.
|
/// Indicates that an element cannot be labelled.
|
||||||
pub trait Unlabellable {}
|
pub trait Unlabellable {}
|
||||||
|
|
||||||
|
/// Tries to extract the plain-text representation of the element.
|
||||||
|
pub trait PlainText {
|
||||||
|
/// Write this element's plain text into the given buffer.
|
||||||
|
fn plain_text(&self, text: &mut EcoString);
|
||||||
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
@ -32,5 +32,5 @@ Ok ...
|
|||||||
|
|
||||||
#set heading(numbering: "(I)")
|
#set heading(numbering: "(I)")
|
||||||
|
|
||||||
= Zusammenfassung
|
= #text(blue)[Zusammen]fassung
|
||||||
#lorem(10)
|
#lorem(10)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user