fix: block math align center (#1762)
Some checks failed
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / build-binary (push) Has been cancelled
tinymist::ci / build-vsc-assets (push) Has been cancelled
tinymist::ci / build-vscode (push) Has been cancelled
tinymist::ci / build-vscode-others (push) Has been cancelled
tinymist::ci / publish-vscode (push) Has been cancelled
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-2019) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Has been cancelled

* fix: block math align center

* fix: block math align center (#1764)

* fix: process block children

* test: update snapshot

---------

Co-authored-by: Hong Jiarong <me@jrhim.com>
This commit is contained in:
Myriad-Dreamin 2025-05-18 03:08:35 +08:00 committed by GitHub
parent 953f997281
commit a33ab6a79b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 136 additions and 43 deletions

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/render_equation.typ
---
{
"contents": "```typc\nlet lam(\n A: type,\n B: type,\n) = dictionary;\n```\n\n---\n\nLambda constructor.\n\nTyping Rule:\n\n<img alt=\"typst-block\" src=\"data:image-hash/svg+xml;base64,redacted\" />\n\n# Positional Parameters\n\n## A\n\n```typc\ntype: type\n```\n\nThe type of the argument.\n \n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the condition of the proposition.<!-- typlite:end:list-item 1 -->\n\n## B (positional)\n\n```typc\ntype: type\n```\n\nThe type of the body.\n \n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the conclusion of the proposition.<!-- typlite:end:list-item 1 -->",
"contents": "```typc\nlet lam(\n A: type,\n B: type,\n) = dictionary;\n```\n\n---\n\nLambda constructor.\n\nTyping Rule:\n\n<p align=\"center\"><img alt=\"typst-block\" src=\"data:image-hash/svg+xml;base64,redacted\" /></p>\n\n# Positional Parameters\n\n## A\n\n```typc\ntype: type\n```\n\nThe type of the argument.\n \n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the condition of the proposition.<!-- typlite:end:list-item 1 -->\n\n## B (positional)\n\n```typc\ntype: type\n```\n\nThe type of the body.\n \n - <!-- typlite:begin:list-item 1 -->It can be also regarded as the conclusion of the proposition.<!-- typlite:end:list-item 1 -->",
"range": "12:20:12:23"
}

View file

@ -103,6 +103,43 @@ impl HighlightNode {
}
}
/// Node for centered content
#[derive(Debug, PartialEq, Clone)]
#[custom_node]
pub struct CenterNode {
/// The content to be centered
pub node: Node,
}
impl CenterNode {
pub fn new(children: Vec<Node>) -> Self {
CenterNode {
node: Node::HtmlElement(cmark_writer::ast::HtmlElement {
tag: "p".to_string(),
attributes: vec![cmark_writer::ast::HtmlAttribute {
name: "align".to_string(),
value: "center".to_string(),
}],
children,
self_closing: false,
}),
}
}
fn write_custom(&self, writer: &mut dyn CustomNodeWriter) -> WriteResult<()> {
let mut temp_writer = cmark_writer::writer::CommonMarkWriter::new();
temp_writer.write(&self.node)?;
let content = temp_writer.into_string();
writer.write_str(&content)?;
writer.write_str("\n")?;
Ok(())
}
fn is_block_custom(&self) -> bool {
true
}
}
/// Common writer interface for different formats
pub trait FormatWriter {
/// Write AST document to output format

View file

@ -1,6 +1,6 @@
---
source: crates/typlite/src/tests.rs
expression: "conv(world, false)"
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/figure_caption.typ
---
<!DOCTYPE html>
@ -13,6 +13,7 @@ input_file: crates/typlite/src/fixtures/integration/figure_caption.typ
</html>
=====
![Content](./fig.svg)
<p align="center">![Content](./fig.svg)
Caption
</p>

View file

@ -1,6 +1,6 @@
---
source: crates/typlite/src/tests.rs
expression: "conv(world, false)"
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/figure_image.typ
---
<!DOCTYPE html>
@ -13,4 +13,6 @@ input_file: crates/typlite/src/fixtures/integration/figure_image.typ
</html>
=====
![](./fig.svg)
<p align="center">![](./fig.svg)
</p>

View file

@ -1,6 +1,6 @@
---
source: crates/typlite/src/tests.rs
expression: "conv(world, false)"
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/figure_image_alt.typ
---
<!DOCTYPE html>
@ -13,4 +13,6 @@ input_file: crates/typlite/src/fixtures/integration/figure_image_alt.typ
</html>
=====
![Content](./fig.svg)
<p align="center">![Content](./fig.svg)
</p>

View file

@ -1,6 +1,6 @@
---
source: crates/typlite/src/tests.rs
expression: "conv(world, false)"
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/math_block.typ
---
<!DOCTYPE html>
@ -13,4 +13,4 @@ input_file: crates/typlite/src/fixtures/integration/math_block.typ
</html>
=====
<img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" />
<p align="center"><img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" /></p>

View file

@ -1,6 +1,6 @@
---
source: crates/typlite/src/tests.rs
expression: "conv(world, false)"
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/math_block2.typ
---
<!DOCTYPE html>
@ -13,4 +13,4 @@ input_file: crates/typlite/src/fixtures/integration/math_block2.typ
</html>
=====
<img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" />
<p align="center"><img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" /></p>

View file

@ -1,6 +1,6 @@
---
source: crates/typlite/src/tests.rs
expression: "conv(world, false)"
expression: "conv(world, ConvKind::Md { for_docs: false })"
input_file: crates/typlite/src/fixtures/integration/outline.typ
---
<!DOCTYPE html>
@ -63,7 +63,7 @@ namespace MyApplication
Math inline:<img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" />and block:
<img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" />
<p align="center"><img alt="typst-block" src="data:image-hash/svg+xml;base64,redacted" /></p>
- First item
- Second item

View file

@ -15,11 +15,14 @@ input_file: crates/typlite/src/fixtures/integration/figure_caption.typ
=====
\begin{document}
\begin{center}
\begin{figure}[htbp]
\centering
\includegraphics[width=0.8\textwidth]{fig.svg}
\caption{Caption}
\end{figure}
\end{center}
\end{document}

View file

@ -15,10 +15,13 @@ input_file: crates/typlite/src/fixtures/integration/figure_image.typ
=====
\begin{document}
\begin{center}
\begin{figure}[htbp]
\centering
\includegraphics[width=0.8\textwidth]{fig.svg}
\end{figure}
\end{center}
\end{document}

View file

@ -15,10 +15,13 @@ input_file: crates/typlite/src/fixtures/integration/figure_image_alt.typ
=====
\begin{document}
\begin{center}
\begin{figure}[htbp]
\centering
\includegraphics[width=0.8\textwidth]{fig.svg}
\end{figure}
\end{center}
\end{document}

View file

@ -15,7 +15,8 @@ input_file: crates/typlite/src/fixtures/integration/math_block.typ
=====
\begin{document}
\begin{center}
\end{center}
\end{document}

View file

@ -15,7 +15,8 @@ input_file: crates/typlite/src/fixtures/integration/math_block2.typ
=====
\begin{document}
\begin{center}
\end{center}
\end{document}

View file

@ -65,7 +65,8 @@ namespace MyApplication
Math inline:and block:
\begin{center}
\end{center}
\begin{itemize}
\item First item

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash
input_file: crates/typlite/src/fixtures/integration/figure_caption.typ
---
siphash128_13:17d544d88231b74b1119c35f627026b
siphash128_13:57521e02b1a93b78c212f39755cf70f

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash
input_file: crates/typlite/src/fixtures/integration/figure_image.typ
---
siphash128_13:89ee713812f00bde9ac174f72c81760
siphash128_13:57521e02b1a93b78c212f39755cf70f

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash
input_file: crates/typlite/src/fixtures/integration/figure_image_alt.typ
---
siphash128_13:89ee713812f00bde9ac174f72c81760
siphash128_13:57521e02b1a93b78c212f39755cf70f

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash
input_file: crates/typlite/src/fixtures/integration/math_block.typ
---
siphash128_13:ca4f0e6c5b2afee90d9736cb2d3bd6ba
siphash128_13:57521e02b1a93b78c212f39755cf70f

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash
input_file: crates/typlite/src/fixtures/integration/math_block2.typ
---
siphash128_13:1c9f3489f7742ef572998ff2b4fd5abd
siphash128_13:57521e02b1a93b78c212f39755cf70f

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash
input_file: crates/typlite/src/fixtures/integration/math_inline.typ
---
siphash128_13:2ac3d241b41c4ee23a122b73e43c8063
siphash128_13:d822231ced812e25cf1951464239a2c6

View file

@ -3,4 +3,4 @@ source: crates/typlite/src/tests.rs
expression: hash
input_file: crates/typlite/src/fixtures/integration/outline.typ
---
siphash128_13:549cf83e9b77d8ae061c95ceb4f93ef6
siphash128_13:3f03c4d1977d61d03c9da495a480baaa

View file

@ -167,12 +167,17 @@
)
show math.equation.where(block: true): it => if-not-paged(
it,
html.elem(
"m1eqblock",
if sys.inputs.at("x-remove-html", default: none) != "true" { html.frame(block(inset: 0.5em, it)) } else {
process-math-eq(it.body).flatten().join()
},
),
if sys.inputs.at("x-remove-html", default: none) != "true" {
html.elem(
"m1eqblock",
html.frame(block(inset: 0.5em, it)),
)
} else {
html.elem(
"m1eqinline",
process-math-eq(it.body).flatten().join(),
)
},
)
// show linebreak: it => if-not-paged(it, md-linebreak)

View file

@ -4,7 +4,7 @@ use cmark_writer::ast::{CustomNode, HtmlAttribute, HtmlElement as CmarkHtmlEleme
use typst::html::{tag, HtmlElement, HtmlNode};
use crate::attributes::{HeadingAttr, RawAttr, TypliteAttrsParser};
use crate::common::ListState;
use crate::common::{CenterNode, ListState};
use crate::tags::md_tag;
use crate::Result;
use crate::TypliteFeat;
@ -134,10 +134,12 @@ impl HtmlToAstParser {
md_tag::math_equation_inline | md_tag::math_equation_block => {
if element.tag == md_tag::math_equation_block {
self.flush_inline_buffer();
}
self.convert_children(element)?;
if element.tag == md_tag::math_equation_block {
self.flush_inline_buffer();
self.convert_children(element)?;
let content = std::mem::take(&mut self.inline_buffer);
self.blocks
.push(Node::Custom(Box::new(CenterNode::new(content))));
} else {
self.convert_children(element)?;
}
Ok(())
}

View file

@ -4,7 +4,7 @@ use cmark_writer::ast::Node;
use typst::html::HtmlElement;
use crate::attributes::{FigureAttr, ImageAttr, LinkAttr, TypliteAttrsParser};
use crate::common::{FigureNode, HighlightNode};
use crate::common::{CenterNode, FigureNode, HighlightNode};
use crate::Result;
use super::core::HtmlToAstParser;
@ -88,10 +88,12 @@ impl InlineParser {
parser.convert_children_into(&mut body_content, element)?;
let body = Box::new(Node::Paragraph(body_content));
// Create figure node using generic definition
parser
.blocks
.push(Node::Custom(Box::new(FigureNode { body, caption })));
// Create figure node with centering
let figure_node = Box::new(FigureNode { body, caption });
let centered_node = CenterNode::new(vec![Node::Custom(figure_node)]);
// Add the centered figure to blocks
parser.blocks.push(Node::Custom(Box::new(centered_node)));
Ok(())
}

View file

@ -7,14 +7,15 @@ use ecow::EcoString;
use std::fs;
use std::io::Cursor;
use crate::common::{FigureNode, FormatWriter};
use crate::common::{CenterNode, FigureNode, FormatWriter};
use crate::Result;
use super::image_processor::DocxImageProcessor;
use super::numbering::DocxNumbering;
use super::styles::DocxStyles;
/// DOCX writer that generates DOCX directly from AST (without intermediate representation)
/// DOCX writer that generates DOCX directly from AST (without intermediate
/// representation)
pub struct DocxWriter {
styles: DocxStyles,
numbering: DocxNumbering,
@ -416,8 +417,30 @@ impl DocxWriter {
}
Node::Custom(custom_node) => {
if let Some(figure_node) = custom_node.as_any().downcast_ref::<FigureNode>() {
// Process figure node with special handling
docx = self.process_figure(docx, figure_node)?;
} else if let Some(center_node) = custom_node.as_any().downcast_ref::<CenterNode>()
{
// Handle regular node but with center alignment
match &center_node.node {
Node::Paragraph(content) => {
docx = self.process_paragraph(docx, content, None)?;
// Get the last paragraph and center it
if let Some(DocumentChild::Paragraph(para)) =
docx.document.children.last_mut()
{
para.property = para.property.clone().align(AlignmentType::Center);
}
}
other => {
docx = self.process_node(docx, other)?;
// Get the last element and center it if it's a paragraph
if let Some(DocumentChild::Paragraph(para)) =
docx.document.children.last_mut()
{
para.property = para.property.clone().align(AlignmentType::Center);
}
}
}
} else if let Some(external_frame) = custom_node
.as_any()
.downcast_ref::<crate::common::ExternalFrameNode>(

View file

@ -6,7 +6,9 @@ use cmark_writer::ast::Node;
use ecow::EcoString;
use tinymist_std::path::unix_slash;
use crate::common::{ExternalFrameNode, FigureNode, FormatWriter, HighlightNode, ListState};
use crate::common::{
CenterNode, ExternalFrameNode, FigureNode, FormatWriter, HighlightNode, ListState,
};
use crate::Result;
/// LaTeX writer implementation
@ -290,6 +292,11 @@ impl LaTeXWriter {
}
output.push_str("\\end{figure}\n\n");
} else if let Some(center_node) = custom_node.as_any().downcast_ref::<CenterNode>()
{
output.push_str("\\begin{center}\n");
self.write_node(&center_node.node, output)?;
output.push_str("\\end{center}\n\n");
} else if let Some(highlight_node) =
custom_node.as_any().downcast_ref::<HighlightNode>()
{