mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
CR: Whoever said orange is the new pink was seriously disturbed.
This commit is contained in:
parent
9dca4c2f78
commit
72d3f3fffa
@ -43,34 +43,42 @@ const SRGB_GRAY: Name<'static> = Name(b"srgbgray");
|
|||||||
|
|
||||||
/// An exporter for a whole PDF document.
|
/// An exporter for a whole PDF document.
|
||||||
struct PdfExporter<'a> {
|
struct PdfExporter<'a> {
|
||||||
|
writer: PdfWriter,
|
||||||
fonts: &'a FontStore,
|
fonts: &'a FontStore,
|
||||||
images: &'a ImageStore,
|
images: &'a ImageStore,
|
||||||
writer: PdfWriter,
|
|
||||||
alloc: Ref,
|
|
||||||
pages: Vec<Page>,
|
pages: Vec<Page>,
|
||||||
face_map: Remapper<FaceId>,
|
page_heights: Vec<f32>,
|
||||||
|
alloc: Ref,
|
||||||
|
page_tree_ref: Ref,
|
||||||
face_refs: Vec<Ref>,
|
face_refs: Vec<Ref>,
|
||||||
glyph_sets: HashMap<FaceId, HashSet<u16>>,
|
|
||||||
image_map: Remapper<ImageId>,
|
|
||||||
image_refs: Vec<Ref>,
|
image_refs: Vec<Ref>,
|
||||||
page_refs: Vec<Ref>,
|
page_refs: Vec<Ref>,
|
||||||
|
face_map: Remapper<FaceId>,
|
||||||
|
image_map: Remapper<ImageId>,
|
||||||
|
glyph_sets: HashMap<FaceId, HashSet<u16>>,
|
||||||
|
languages: HashMap<Lang, usize>,
|
||||||
heading_tree: Vec<HeadingNode>,
|
heading_tree: Vec<HeadingNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PdfExporter<'a> {
|
impl<'a> PdfExporter<'a> {
|
||||||
fn new(ctx: &'a Context) -> Self {
|
fn new(ctx: &'a Context) -> Self {
|
||||||
|
let mut alloc = Ref::new(1);
|
||||||
|
let page_tree_ref = alloc.bump();
|
||||||
Self {
|
Self {
|
||||||
|
writer: PdfWriter::new(),
|
||||||
fonts: &ctx.fonts,
|
fonts: &ctx.fonts,
|
||||||
images: &ctx.images,
|
images: &ctx.images,
|
||||||
writer: PdfWriter::new(),
|
|
||||||
alloc: Ref::new(1),
|
|
||||||
pages: vec![],
|
pages: vec![],
|
||||||
face_map: Remapper::new(),
|
page_heights: vec![],
|
||||||
face_refs: vec![],
|
alloc,
|
||||||
glyph_sets: HashMap::new(),
|
page_tree_ref,
|
||||||
image_map: Remapper::new(),
|
|
||||||
image_refs: vec![],
|
|
||||||
page_refs: vec![],
|
page_refs: vec![],
|
||||||
|
face_refs: vec![],
|
||||||
|
image_refs: vec![],
|
||||||
|
face_map: Remapper::new(),
|
||||||
|
image_map: Remapper::new(),
|
||||||
|
glyph_sets: HashMap::new(),
|
||||||
|
languages: HashMap::new(),
|
||||||
heading_tree: vec![],
|
heading_tree: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,7 +87,15 @@ impl<'a> PdfExporter<'a> {
|
|||||||
self.build_pages(frames);
|
self.build_pages(frames);
|
||||||
self.write_fonts();
|
self.write_fonts();
|
||||||
self.write_images();
|
self.write_images();
|
||||||
self.write_structure();
|
|
||||||
|
// The root page tree.
|
||||||
|
for page in std::mem::take(&mut self.pages).into_iter() {
|
||||||
|
self.write_page(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_page_tree();
|
||||||
|
self.write_catalog();
|
||||||
|
|
||||||
self.writer.finish()
|
self.writer.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +104,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
let page_id = self.alloc.bump();
|
let page_id = self.alloc.bump();
|
||||||
self.page_refs.push(page_id);
|
self.page_refs.push(page_id);
|
||||||
let page = PageExporter::new(self, page_id).export(frame);
|
let page = PageExporter::new(self, page_id).export(frame);
|
||||||
|
self.page_heights.push(page.size.y.to_f32());
|
||||||
self.pages.push(page);
|
self.pages.push(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,45 +324,11 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_structure(&mut self) {
|
fn write_page(&mut self, page: Page) {
|
||||||
// The root page tree.
|
|
||||||
let page_tree_ref = self.alloc.bump();
|
|
||||||
|
|
||||||
// The page objects (non-root nodes in the page tree).
|
|
||||||
let mut page_heights =
|
|
||||||
self.pages.iter().map(|page| page.size.y.to_f32()).collect();
|
|
||||||
|
|
||||||
let mut languages = HashMap::new();
|
|
||||||
|
|
||||||
for (page, page_id) in std::mem::take(&mut self.pages)
|
|
||||||
.into_iter()
|
|
||||||
.zip(self.page_refs.clone().iter())
|
|
||||||
{
|
|
||||||
self.write_page(
|
|
||||||
page,
|
|
||||||
*page_id,
|
|
||||||
page_tree_ref,
|
|
||||||
&mut languages,
|
|
||||||
&mut page_heights,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.write_page_tree(page_tree_ref);
|
|
||||||
self.write_catalog(page_tree_ref, &languages);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_page(
|
|
||||||
&mut self,
|
|
||||||
page: Page,
|
|
||||||
page_id: Ref,
|
|
||||||
page_tree_ref: Ref,
|
|
||||||
languages: &mut HashMap<Lang, usize>,
|
|
||||||
page_heights: &mut Vec<f32>,
|
|
||||||
) {
|
|
||||||
let content_id = self.alloc.bump();
|
let content_id = self.alloc.bump();
|
||||||
|
|
||||||
let mut page_writer = self.writer.page(page_id);
|
let mut page_writer = self.writer.page(page.id);
|
||||||
page_writer.parent(page_tree_ref);
|
page_writer.parent(self.page_tree_ref);
|
||||||
|
|
||||||
let w = page.size.x.to_f32();
|
let w = page.size.x.to_f32();
|
||||||
let h = page.size.y.to_f32();
|
let h = page.size.y.to_f32();
|
||||||
@ -364,7 +347,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
Destination::Internal(loc) => {
|
Destination::Internal(loc) => {
|
||||||
let index = loc.page - 1;
|
let index = loc.page - 1;
|
||||||
let height = page_heights[index];
|
let height = self.page_heights[index];
|
||||||
link.action()
|
link.action()
|
||||||
.action_type(ActionType::GoTo)
|
.action_type(ActionType::GoTo)
|
||||||
.destination_direct()
|
.destination_direct()
|
||||||
@ -377,20 +360,13 @@ impl<'a> PdfExporter<'a> {
|
|||||||
annotations.finish();
|
annotations.finish();
|
||||||
page_writer.finish();
|
page_writer.finish();
|
||||||
|
|
||||||
for (lang, count) in page.languages {
|
|
||||||
languages
|
|
||||||
.entry(lang)
|
|
||||||
.and_modify(|x| *x += count)
|
|
||||||
.or_insert_with(|| count);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.writer
|
self.writer
|
||||||
.stream(content_id, &deflate(&page.content.finish()))
|
.stream(content_id, &deflate(&page.content.finish()))
|
||||||
.filter(Filter::FlateDecode);
|
.filter(Filter::FlateDecode);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_page_tree(&mut self, page_tree_ref: Ref) {
|
fn write_page_tree(&mut self) {
|
||||||
let mut pages = self.writer.pages(page_tree_ref);
|
let mut pages = self.writer.pages(self.page_tree_ref);
|
||||||
pages
|
pages
|
||||||
.count(self.page_refs.len() as i32)
|
.count(self.page_refs.len() as i32)
|
||||||
.kids(self.page_refs.iter().copied());
|
.kids(self.page_refs.iter().copied());
|
||||||
@ -420,34 +396,36 @@ impl<'a> PdfExporter<'a> {
|
|||||||
pages.finish();
|
pages.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_catalog(&mut self, page_tree_ref: Ref, languages: &HashMap<Lang, usize>) {
|
fn write_catalog(&mut self) {
|
||||||
// Build the outline tree.
|
// Build the outline tree.
|
||||||
let outline_root_id = self.alloc.bump();
|
let outline_root_id = (!self.heading_tree.is_empty()).then(|| self.alloc.bump());
|
||||||
|
|
||||||
let outline_start_ref = self.alloc;
|
let outline_start_ref = self.alloc;
|
||||||
|
let len = self.heading_tree.len();
|
||||||
|
let mut prev_ref = None;
|
||||||
|
|
||||||
for (i, node) in std::mem::take(&mut self.heading_tree).iter().enumerate() {
|
for (i, node) in std::mem::take(&mut self.heading_tree).iter().enumerate() {
|
||||||
self.write_outline_item(
|
prev_ref = Some(self.write_outline_item(
|
||||||
node,
|
node,
|
||||||
i == 0,
|
outline_root_id.unwrap(),
|
||||||
i + 1 == self.heading_tree.len(),
|
prev_ref,
|
||||||
outline_root_id,
|
i + 1 == len,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.heading_tree.is_empty() {
|
if let Some(outline_root_id) = outline_root_id {
|
||||||
let mut outline_root = self.writer.outline(outline_root_id);
|
let mut outline_root = self.writer.outline(outline_root_id);
|
||||||
outline_root.first(outline_start_ref);
|
outline_root.first(outline_start_ref);
|
||||||
outline_root.last(Ref::new(self.alloc.get() - 1));
|
outline_root.last(Ref::new(self.alloc.get() - 1));
|
||||||
outline_root.count(self.heading_tree.len() as i32);
|
outline_root.count(self.heading_tree.len() as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
let lang = languages
|
let lang = self
|
||||||
.into_iter()
|
.languages
|
||||||
.max_by(|(_, v1), (_, v2)| v1.cmp(v2))
|
.iter()
|
||||||
.map(|(k, _)| k);
|
.max_by_key(|(&lang, &count)| (count, lang))
|
||||||
|
.map(|(&k, _)| k);
|
||||||
|
|
||||||
let dir = if lang.copied().map(Lang::dir) == Some(Dir::RTL) {
|
let dir = if lang.map(Lang::dir) == Some(Dir::RTL) {
|
||||||
Direction::R2L
|
Direction::R2L
|
||||||
} else {
|
} else {
|
||||||
Direction::L2R
|
Direction::L2R
|
||||||
@ -456,10 +434,10 @@ impl<'a> PdfExporter<'a> {
|
|||||||
// Write the document information, catalog and wrap it up!
|
// Write the document information, catalog and wrap it up!
|
||||||
self.writer.document_info(self.alloc.bump()).creator(TextStr("Typst"));
|
self.writer.document_info(self.alloc.bump()).creator(TextStr("Typst"));
|
||||||
let mut catalog = self.writer.catalog(self.alloc.bump());
|
let mut catalog = self.writer.catalog(self.alloc.bump());
|
||||||
catalog.pages(page_tree_ref);
|
catalog.pages(self.page_tree_ref);
|
||||||
catalog.viewer_preferences().direction(dir);
|
catalog.viewer_preferences().direction(dir);
|
||||||
|
|
||||||
if !self.heading_tree.is_empty() {
|
if let Some(outline_root_id) = outline_root_id {
|
||||||
catalog.outlines(outline_root_id);
|
catalog.outlines(outline_root_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,29 +451,28 @@ impl<'a> PdfExporter<'a> {
|
|||||||
fn write_outline_item(
|
fn write_outline_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
node: &HeadingNode,
|
node: &HeadingNode,
|
||||||
is_first: bool,
|
|
||||||
is_last: bool,
|
|
||||||
parent_ref: Ref,
|
parent_ref: Ref,
|
||||||
) {
|
prev_ref: Option<Ref>,
|
||||||
|
is_last: bool,
|
||||||
|
) -> Ref {
|
||||||
let id = self.alloc.bump();
|
let id = self.alloc.bump();
|
||||||
let next = Ref::new(id.get() + node.len() as i32);
|
let next_ref = Ref::new(id.get() + node.len() as i32);
|
||||||
|
|
||||||
let mut outline = self.writer.outline_item(id);
|
let mut outline = self.writer.outline_item(id);
|
||||||
outline.parent(parent_ref);
|
outline.parent(parent_ref);
|
||||||
|
|
||||||
if !is_last {
|
if !is_last {
|
||||||
outline.next(next);
|
outline.next(next_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_first {
|
if let Some(prev_rev) = prev_ref {
|
||||||
outline.prev(Ref::new(id.get() - 1));
|
outline.prev(prev_rev);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !node.children.is_empty() {
|
if !node.children.is_empty() {
|
||||||
let current_child = Ref::new(id.get() + 1);
|
let current_child = Ref::new(id.get() + 1);
|
||||||
outline.first(current_child);
|
outline.first(current_child);
|
||||||
outline.last(Ref::new(next.get() - 1));
|
outline.last(Ref::new(next_ref.get() - 1));
|
||||||
|
|
||||||
outline.count(-1 * node.children.len() as i32);
|
outline.count(-1 * node.children.len() as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,36 +485,37 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
outline.finish();
|
outline.finish();
|
||||||
|
|
||||||
if !node.children.is_empty() {
|
let mut prev_ref = None;
|
||||||
for (i, child) in node.children.iter().enumerate() {
|
for (i, child) in node.children.iter().enumerate() {
|
||||||
self.write_outline_item(child, i == 0, i + 1 == node.children.len(), id);
|
prev_ref = Some(self.write_outline_item(
|
||||||
}
|
child,
|
||||||
|
id,
|
||||||
|
prev_ref,
|
||||||
|
i + 1 == node.children.len(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An exporter for the contents of a single PDF page.
|
/// An exporter for the contents of a single PDF page.
|
||||||
struct PageExporter<'a> {
|
struct PageExporter<'a, 'b> {
|
||||||
fonts: &'a FontStore,
|
exporter: &'a mut PdfExporter<'b>,
|
||||||
font_map: &'a mut Remapper<FaceId>,
|
|
||||||
image_map: &'a mut Remapper<ImageId>,
|
|
||||||
glyphs: &'a mut HashMap<FaceId, HashSet<u16>>,
|
|
||||||
heading_tree: &'a mut Vec<HeadingNode>,
|
|
||||||
page_ref: Ref,
|
page_ref: Ref,
|
||||||
languages: HashMap<Lang, usize>,
|
|
||||||
bottom: f32,
|
|
||||||
content: Content,
|
content: Content,
|
||||||
links: Vec<(Destination, Rect)>,
|
|
||||||
state: State,
|
state: State,
|
||||||
saves: Vec<State>,
|
saves: Vec<State>,
|
||||||
|
bottom: f32,
|
||||||
|
links: Vec<(Destination, Rect)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data for an exported page.
|
/// Data for an exported page.
|
||||||
struct Page {
|
struct Page {
|
||||||
|
id: Ref,
|
||||||
size: Size,
|
size: Size,
|
||||||
content: Content,
|
content: Content,
|
||||||
links: Vec<(Destination, Rect)>,
|
links: Vec<(Destination, Rect)>,
|
||||||
languages: HashMap<Lang, usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simulated graphics state used to deduplicate graphics state changes and
|
/// A simulated graphics state used to deduplicate graphics state changes and
|
||||||
@ -573,7 +551,7 @@ impl HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
1 + self.children.iter().map(|c| c.len()).sum::<usize>()
|
1 + self.children.iter().map(Self::len).sum::<usize>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, other: Heading, level: usize) -> bool {
|
fn insert(&mut self, other: Heading, level: usize) -> bool {
|
||||||
@ -581,32 +559,27 @@ impl HeadingNode {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.children.is_empty() && level + 1 > other.level {
|
if let Some(child) = self.children.last_mut() {
|
||||||
return self.children.last_mut().unwrap().insert(other, level + 1);
|
if child.insert(other.clone(), level + 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.children
|
self.children.push(Self::leaf(other));
|
||||||
.push(HeadingNode { heading: other, children: Vec::new() });
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PageExporter<'a> {
|
impl<'a, 'b> PageExporter<'a, 'b> {
|
||||||
fn new(exporter: &'a mut PdfExporter, page_ref: Ref) -> Self {
|
fn new(exporter: &'a mut PdfExporter<'b>, page_ref: Ref) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fonts: exporter.fonts,
|
exporter,
|
||||||
font_map: &mut exporter.face_map,
|
|
||||||
image_map: &mut exporter.image_map,
|
|
||||||
glyphs: &mut exporter.glyph_sets,
|
|
||||||
heading_tree: &mut exporter.heading_tree,
|
|
||||||
page_ref,
|
page_ref,
|
||||||
languages: HashMap::new(),
|
|
||||||
bottom: 0.0,
|
|
||||||
content: Content::new(),
|
content: Content::new(),
|
||||||
links: vec![],
|
|
||||||
state: State::default(),
|
state: State::default(),
|
||||||
saves: vec![],
|
saves: vec![],
|
||||||
|
bottom: 0.0,
|
||||||
|
links: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,8 +598,8 @@ impl<'a> PageExporter<'a> {
|
|||||||
Page {
|
Page {
|
||||||
size: frame.size,
|
size: frame.size,
|
||||||
content: self.content,
|
content: self.content,
|
||||||
|
id: self.page_ref,
|
||||||
links: self.links,
|
links: self.links,
|
||||||
languages: self.languages,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,17 +607,17 @@ impl<'a> PageExporter<'a> {
|
|||||||
if let Some(Role::Heading(level)) = frame.role() {
|
if let Some(Role::Heading(level)) = frame.role() {
|
||||||
let heading = Heading {
|
let heading = Heading {
|
||||||
position: Point::new(self.state.transform.tx, self.state.transform.ty),
|
position: Point::new(self.state.transform.tx, self.state.transform.ty),
|
||||||
content: frame.inner_text(),
|
content: frame.text(),
|
||||||
page: self.page_ref,
|
page: self.page_ref,
|
||||||
level,
|
level,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(last) = self.heading_tree.last_mut() {
|
if let Some(last) = self.exporter.heading_tree.last_mut() {
|
||||||
if !last.insert(heading.clone(), 1) {
|
if !last.insert(heading.clone(), 1) {
|
||||||
self.heading_tree.push(HeadingNode::leaf(heading))
|
self.exporter.heading_tree.push(HeadingNode::leaf(heading))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.heading_tree.push(HeadingNode::leaf(heading))
|
self.exporter.heading_tree.push(HeadingNode::leaf(heading))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,13 +657,14 @@ impl<'a> PageExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_text(&mut self, x: f32, y: f32, text: &Text) {
|
fn write_text(&mut self, x: f32, y: f32, text: &Text) {
|
||||||
*self.languages.entry(text.lang).or_insert(0) += text.glyphs.len();
|
*self.exporter.languages.entry(text.lang).or_insert(0) += text.glyphs.len();
|
||||||
self.glyphs
|
self.exporter
|
||||||
|
.glyph_sets
|
||||||
.entry(text.face_id)
|
.entry(text.face_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
.extend(text.glyphs.iter().map(|g| g.id));
|
.extend(text.glyphs.iter().map(|g| g.id));
|
||||||
|
|
||||||
let face = self.fonts.get(text.face_id);
|
let face = self.exporter.fonts.get(text.face_id);
|
||||||
|
|
||||||
self.set_fill(text.fill);
|
self.set_fill(text.fill);
|
||||||
self.set_font(text.face_id, text.size);
|
self.set_font(text.face_id, text.size);
|
||||||
@ -804,8 +778,8 @@ impl<'a> PageExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_image(&mut self, x: f32, y: f32, id: ImageId, size: Size) {
|
fn write_image(&mut self, x: f32, y: f32, id: ImageId, size: Size) {
|
||||||
self.image_map.insert(id);
|
self.exporter.image_map.insert(id);
|
||||||
let name = format_eco!("Im{}", self.image_map.map(id));
|
let name = format_eco!("Im{}", self.exporter.image_map.map(id));
|
||||||
let w = size.x.to_f32();
|
let w = size.x.to_f32();
|
||||||
let h = size.y.to_f32();
|
let h = size.y.to_f32();
|
||||||
self.content.save_state();
|
self.content.save_state();
|
||||||
@ -868,8 +842,8 @@ impl<'a> PageExporter<'a> {
|
|||||||
|
|
||||||
fn set_font(&mut self, face_id: FaceId, size: Length) {
|
fn set_font(&mut self, face_id: FaceId, size: Length) {
|
||||||
if self.state.font != Some((face_id, size)) {
|
if self.state.font != Some((face_id, size)) {
|
||||||
self.font_map.insert(face_id);
|
self.exporter.face_map.insert(face_id);
|
||||||
let name = format_eco!("F{}", self.font_map.map(face_id));
|
let name = format_eco!("F{}", self.exporter.face_map.map(face_id));
|
||||||
self.content.set_font(Name(name.as_bytes()), size.to_f32());
|
self.content.set_font(Name(name.as_bytes()), size.to_f32());
|
||||||
self.state.font = Some((face_id, size));
|
self.state.font = Some((face_id, size));
|
||||||
}
|
}
|
||||||
|
41
src/frame.rs
41
src/frame.rs
@ -71,11 +71,8 @@ impl Frame {
|
|||||||
/// group based on the number of elements in the frame.
|
/// group based on the number of elements in the frame.
|
||||||
pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
||||||
if (self.elements.is_empty() || frame.as_ref().is_light())
|
if (self.elements.is_empty() || frame.as_ref().is_light())
|
||||||
&& (frame.as_ref().role().is_none() || self.role.is_none())
|
&& frame.as_ref().role().is_none()
|
||||||
{
|
{
|
||||||
if self.role.is_none() {
|
|
||||||
self.role = frame.as_ref().role()
|
|
||||||
}
|
|
||||||
frame.inline(self, self.layer(), pos);
|
frame.inline(self, self.layer(), pos);
|
||||||
} else {
|
} else {
|
||||||
self.elements.push((pos, Element::Group(Group::new(frame.share()))));
|
self.elements.push((pos, Element::Group(Group::new(frame.share()))));
|
||||||
@ -98,11 +95,8 @@ impl Frame {
|
|||||||
/// Add a frame at a position in the background.
|
/// Add a frame at a position in the background.
|
||||||
pub fn prepend_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
pub fn prepend_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
||||||
if (self.elements.is_empty() || frame.as_ref().is_light())
|
if (self.elements.is_empty() || frame.as_ref().is_light())
|
||||||
&& (frame.as_ref().role().is_none() || self.role.is_none())
|
&& frame.as_ref().role().is_none()
|
||||||
{
|
{
|
||||||
if self.role.is_none() {
|
|
||||||
self.role = frame.as_ref().role()
|
|
||||||
}
|
|
||||||
frame.inline(self, 0, pos);
|
frame.inline(self, 0, pos);
|
||||||
} else {
|
} else {
|
||||||
self.elements
|
self.elements
|
||||||
@ -149,10 +143,8 @@ impl Frame {
|
|||||||
|
|
||||||
/// Apply the given role to the frame if it doesn't already have one.
|
/// Apply the given role to the frame if it doesn't already have one.
|
||||||
pub fn apply_role(&mut self, role: Role) {
|
pub fn apply_role(&mut self, role: Role) {
|
||||||
match self.role {
|
if self.role.map_or(true, Role::is_weak) {
|
||||||
None => self.role = Some(role),
|
self.role = Some(role);
|
||||||
Some(old) if old.is_weak() => self.role = Some(role),
|
|
||||||
Some(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,24 +171,29 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Recover the text inside of the frame and its children.
|
/// Recover the text inside of the frame and its children.
|
||||||
pub fn inner_text(&self) -> EcoString {
|
pub fn text(&self) -> EcoString {
|
||||||
let mut res = EcoString::new();
|
let mut text = EcoString::new();
|
||||||
for (_, element) in &self.elements {
|
for (_, element) in &self.elements {
|
||||||
match element {
|
match element {
|
||||||
Element::Text(text) => res.push_str(
|
Element::Text(content) => {
|
||||||
&text.glyphs.iter().map(|glyph| glyph.c).collect::<EcoString>(),
|
for glyph in &content.glyphs {
|
||||||
),
|
text.push(glyph.c);
|
||||||
Element::Group(group) => res.push_str(&group.frame.inner_text()),
|
}
|
||||||
|
}
|
||||||
|
Element::Group(group) => text.push_str(&group.frame.text()),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res
|
text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Frame {
|
impl Debug for Frame {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
self.role.fmt(f)?;
|
if let Some(role) = self.role {
|
||||||
|
write!(f, "{role:?} ")?;
|
||||||
|
}
|
||||||
|
|
||||||
f.debug_list()
|
f.debug_list()
|
||||||
.entries(self.elements.iter().map(|(_, element)| element))
|
.entries(self.elements.iter().map(|(_, element)| element))
|
||||||
.finish()
|
.finish()
|
||||||
@ -422,7 +419,7 @@ pub enum Role {
|
|||||||
/// A generic inline subdivision.
|
/// A generic inline subdivision.
|
||||||
GenericInline,
|
GenericInline,
|
||||||
/// A list. The boolean indicates whether it is ordered.
|
/// A list. The boolean indicates whether it is ordered.
|
||||||
List(bool),
|
List { ordered: bool },
|
||||||
/// A list item. Must have a list parent.
|
/// A list item. Must have a list parent.
|
||||||
ListItem,
|
ListItem,
|
||||||
/// The label of a list item.
|
/// The label of a list item.
|
||||||
@ -445,6 +442,8 @@ pub enum Role {
|
|||||||
Footer,
|
Footer,
|
||||||
/// A page background.
|
/// A page background.
|
||||||
Background,
|
Background,
|
||||||
|
/// A page foreground.
|
||||||
|
Foreground,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Role {
|
impl Role {
|
||||||
|
@ -94,9 +94,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
frames = child.layout(ctx, &pod, styles)?;
|
frames = child.layout(ctx, &pod, styles)?;
|
||||||
|
|
||||||
for frame in frames.iter_mut() {
|
for frame in frames.iter_mut() {
|
||||||
if frame.role().map_or(true, Role::is_weak) {
|
Arc::make_mut(frame).apply_role(Role::GenericBlock);
|
||||||
Arc::make_mut(frame).apply_role(Role::GenericBlock);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relayout with full expansion into square region to make sure
|
// Relayout with full expansion into square region to make sure
|
||||||
|
@ -65,41 +65,6 @@ pub enum TrackSizing {
|
|||||||
Fractional(Fraction),
|
Fractional(Fraction),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines what kind of semantics a grid should represent.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
enum GridSemantics {
|
|
||||||
/// The grid is transparent to the semantic tree.
|
|
||||||
None,
|
|
||||||
/// The grid is a list, its rows are list items. The bool indicates whether
|
|
||||||
/// the list is ordered.
|
|
||||||
List,
|
|
||||||
/// The grid is a table.
|
|
||||||
Table,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GridSemantics {
|
|
||||||
/// The role of a row in a grid with these semantics.
|
|
||||||
fn row(self) -> Option<Role> {
|
|
||||||
match self {
|
|
||||||
Self::None => None,
|
|
||||||
Self::List => Some(Role::ListItem),
|
|
||||||
Self::Table => Some(Role::TableRow),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the semantic role of a grid row given the previous semantics and
|
|
||||||
/// the cell's role.
|
|
||||||
fn determine(other: Option<Self>, role: Option<Role>) -> Self {
|
|
||||||
match (other, role) {
|
|
||||||
(None, Some(Role::ListItem | Role::ListLabel)) => Self::List,
|
|
||||||
(Some(Self::List), Some(Role::ListItem | Role::ListLabel)) => Self::List,
|
|
||||||
(None, Some(Role::TableCell)) => Self::Table,
|
|
||||||
(Some(Self::Table), Some(Role::TableCell)) => Self::Table,
|
|
||||||
_ => Self::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Vec<TrackSizing>,
|
Vec<TrackSizing>,
|
||||||
Expected: "integer, auto, relative length, fraction, or array of the latter three)",
|
Expected: "integer, auto, relative length, fraction, or array of the latter three)",
|
||||||
@ -487,7 +452,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let mut output = Frame::new(Size::new(self.used.x, height));
|
let mut output = Frame::new(Size::new(self.used.x, height));
|
||||||
|
|
||||||
let mut pos = Point::zero();
|
let mut pos = Point::zero();
|
||||||
let mut semantic = None;
|
|
||||||
|
|
||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
if let Some(node) = self.cell(x, y) {
|
||||||
@ -501,7 +465,13 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
let pod = Regions::one(size, base, Spec::splat(true));
|
let pod = Regions::one(size, base, Spec::splat(true));
|
||||||
let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0);
|
let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0);
|
||||||
semantic = Some(GridSemantics::determine(semantic, frame.role()));
|
match frame.role() {
|
||||||
|
Some(Role::ListLabel | Role::ListItemBody) => {
|
||||||
|
output.apply_role(Role::ListItem)
|
||||||
|
}
|
||||||
|
Some(Role::TableCell) => output.apply_role(Role::TableRow),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
@ -509,10 +479,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pos.x += rcol;
|
pos.x += rcol;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(role) = semantic.and_then(GridSemantics::row) {
|
|
||||||
output.apply_role(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +501,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
// Layout the row.
|
// Layout the row.
|
||||||
let mut pos = Point::zero();
|
let mut pos = Point::zero();
|
||||||
let mut semantic = None;
|
|
||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
if let Some(node) = self.cell(x, y) {
|
||||||
pod.first.x = rcol;
|
pod.first.x = rcol;
|
||||||
@ -549,7 +514,13 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Push the layouted frames into the individual output frames.
|
// Push the layouted frames into the individual output frames.
|
||||||
let frames = node.layout(self.ctx, &pod, self.styles)?;
|
let frames = node.layout(self.ctx, &pod, self.styles)?;
|
||||||
for (output, frame) in outputs.iter_mut().zip(frames) {
|
for (output, frame) in outputs.iter_mut().zip(frames) {
|
||||||
semantic = Some(GridSemantics::determine(semantic, frame.role()));
|
match frame.role() {
|
||||||
|
Some(Role::ListLabel | Role::ListItemBody) => {
|
||||||
|
output.apply_role(Role::ListItem)
|
||||||
|
}
|
||||||
|
Some(Role::TableCell) => output.apply_role(Role::TableRow),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -557,12 +528,6 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pos.x += rcol;
|
pos.x += rcol;
|
||||||
}
|
}
|
||||||
|
|
||||||
for output in outputs.iter_mut() {
|
|
||||||
if let Some(role) = semantic.and_then(GridSemantics::row) {
|
|
||||||
output.apply_role(role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(outputs)
|
Ok(outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,30 +110,28 @@ impl PageNode {
|
|||||||
let pad = padding.resolve(styles).relative_to(size);
|
let pad = padding.resolve(styles).relative_to(size);
|
||||||
let pw = size.x - pad.left - pad.right;
|
let pw = size.x - pad.left - pad.right;
|
||||||
let py = size.y - pad.bottom;
|
let py = size.y - pad.bottom;
|
||||||
for (marginal, pos, area, role) in [
|
for (role, marginal, pos, area) in [
|
||||||
(
|
(
|
||||||
|
Role::Header,
|
||||||
header,
|
header,
|
||||||
Point::with_x(pad.left),
|
Point::with_x(pad.left),
|
||||||
Size::new(pw, pad.top),
|
Size::new(pw, pad.top),
|
||||||
Role::Header,
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
Role::Footer,
|
||||||
footer,
|
footer,
|
||||||
Point::new(pad.left, py),
|
Point::new(pad.left, py),
|
||||||
Size::new(pw, pad.bottom),
|
Size::new(pw, pad.bottom),
|
||||||
Role::Footer,
|
|
||||||
),
|
),
|
||||||
(foreground, Point::zero(), size, Role::Background),
|
(Role::Foreground, foreground, Point::zero(), size),
|
||||||
(background, Point::zero(), size, Role::Background),
|
(Role::Background, background, Point::zero(), size),
|
||||||
] {
|
] {
|
||||||
if let Some(content) = marginal.resolve(ctx, page)? {
|
if let Some(content) = marginal.resolve(ctx, page)? {
|
||||||
let pod = Regions::one(area, area, Spec::splat(true));
|
let pod = Regions::one(area, area, Spec::splat(true));
|
||||||
let role_map = StyleMap::with_role(role);
|
|
||||||
let styles = role_map.chain(&styles);
|
|
||||||
let mut sub = content.layout(ctx, &pod, styles)?.remove(0);
|
let mut sub = content.layout(ctx, &pod, styles)?.remove(0);
|
||||||
Arc::make_mut(&mut sub).apply_role(role);
|
Arc::make_mut(&mut sub).apply_role(role);
|
||||||
|
|
||||||
if std::ptr::eq(marginal, background) {
|
if role == Role::Background {
|
||||||
Arc::make_mut(frame).prepend_frame(pos, sub);
|
Arc::make_mut(frame).prepend_frame(pos, sub);
|
||||||
} else {
|
} else {
|
||||||
Arc::make_mut(frame).push_frame(pos, sub);
|
Arc::make_mut(frame).push_frame(pos, sub);
|
||||||
|
@ -92,8 +92,7 @@ impl Show for HeadingNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut map = StyleMap::with_role(Role::Heading(self.level.get()));
|
let mut map = StyleMap::new();
|
||||||
|
|
||||||
map.set(TextNode::SIZE, resolve!(Self::SIZE));
|
map.set(TextNode::SIZE, resolve!(Self::SIZE));
|
||||||
|
|
||||||
if let Smart::Custom(family) = resolve!(Self::FAMILY) {
|
if let Smart::Custom(family) = resolve!(Self::FAMILY) {
|
||||||
@ -116,7 +115,7 @@ impl Show for HeadingNode {
|
|||||||
realized = realized.underlined();
|
realized = realized.underlined();
|
||||||
}
|
}
|
||||||
|
|
||||||
realized = realized.styled_with_map(map);
|
realized = realized.styled_with_map(map).role(Role::Heading(self.level.get()));
|
||||||
realized = realized.spaced(
|
realized = realized.spaced(
|
||||||
resolve!(Self::ABOVE).resolve(styles),
|
resolve!(Self::ABOVE).resolve(styles),
|
||||||
resolve!(Self::BELOW).resolve(styles),
|
resolve!(Self::BELOW).resolve(styles),
|
||||||
|
@ -161,7 +161,9 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(realized.role(Role::List(L == ORDERED)).spaced(above, below))
|
Ok(realized
|
||||||
|
.role(Role::List { ordered: L == ORDERED })
|
||||||
|
.spaced(above, below))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,7 +557,6 @@ fn prepare<'a>(
|
|||||||
|
|
||||||
if !shift.is_zero() || frame.role().map_or(true, Role::is_weak) {
|
if !shift.is_zero() || frame.role().map_or(true, Role::is_weak) {
|
||||||
let frame = Arc::make_mut(&mut frame);
|
let frame = Arc::make_mut(&mut frame);
|
||||||
|
|
||||||
frame.translate(Point::with_y(shift));
|
frame.translate(Point::with_y(shift));
|
||||||
frame.apply_role(Role::GenericInline);
|
frame.apply_role(Role::GenericInline);
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ impl Content {
|
|||||||
|
|
||||||
/// Assign a role to this content by adding a style map.
|
/// Assign a role to this content by adding a style map.
|
||||||
pub fn role(self, role: Role) -> Self {
|
pub fn role(self, role: Role) -> Self {
|
||||||
self.styled_with_map(StyleMap::with_role(role))
|
self.styled_with_entry(StyleEntry::Role(role))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reenable the show rule identified by the selector.
|
/// Reenable the show rule identified by the selector.
|
||||||
|
@ -37,13 +37,6 @@ impl StyleMap {
|
|||||||
styles
|
styles
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a style map from a single role.
|
|
||||||
pub fn with_role(role: Role) -> Self {
|
|
||||||
let mut styles = Self::new();
|
|
||||||
styles.push(StyleEntry::Role(role));
|
|
||||||
styles
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set an inner value for a style property.
|
/// Set an inner value for a style property.
|
||||||
///
|
///
|
||||||
/// If the property needs folding and the value is already contained in the
|
/// If the property needs folding and the value is already contained in the
|
||||||
|
Loading…
x
Reference in New Issue
Block a user