# LiePress ๆถๆ้ๆ่ฎพ่ฎกๆนๆก
## HTML ๅญ้ + Lightning CSS ๆ ทๅผๅขๅผบ
---
## ็ฎๅฝ
1. [ๆถๆๆป่ง](#1-ๆถๆๆป่ง)
2. [HTML ๅญ้ๅฎไน](#2-html-ๅญ้ๅฎไน)
3. [ๆจกๅ่ฎพ่ฎก](#3-ๆจกๅ่ฎพ่ฎก)
4. [CSS ๆ ทๅผ็ณป็ป](#4-css-ๆ ทๅผ็ณป็ป)
5. [ๆฐๆฎๆตไธๅค็ๆต็จ](#5-ๆฐๆฎๆตไธๅค็ๆต็จ)
6. [ๅฎ็ฐ่งๅ](#6-ๅฎ็ฐ่งๅ)
---
## 1. ๆถๆๆป่ง
### 1.1 ๆฐๆงๆถๆๅฏนๆฏ
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๅฝๅๆถๆ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Markdown โโโถ MDAST โโโถ Styled AST โโโถ Document โโโถ PDF/SVG/PNG โ
โ (markdown) (่ชๅฎไน) (generator) (render) โ
โ โ
โ ้ฎ้ข๏ผ โ
โ - HTML ๆ ็ญพๅค็่ๅผฑ๏ผไป
ๆๅ <style>๏ผ โ
โ - CSS ่งฃๆๅจๅ่ฝๆ้๏ผๅบ็ก้ๆฉๅจ๏ผ โ
โ - ๆ ทๅผ็ณป็ปไธ HTML ๆ ๅ่ฑ่ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๆฐๆถๆ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Markdown โโโโถโ HTML โโโโถโ Styled โโโโถโ Document โโโโถโ Output โ โ
โ โ Source โ โ AST โ โ HTML โ โ Layout โ โ PDF/... โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ โ โ โ โ
โ โผ โผ โผ โผ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ markdown โ โ html โ โ css โ โ generatorโ โ
โ โ crate โ โ parser โ โ (lightningโ โ module โ โ
โ โ โ โ module โ โ css) โ โ โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ
โ ไผๅฟ๏ผ โ
โ - ๆ็กฎ็ HTML ๅญ้ๆฏๆ โ
โ - ๆต่งๅจ็บง CSS ่งฃๆ๏ผLightning CSS๏ผ โ
โ - ๆ ๅ HTML + CSS ๅค็ๆต็จ โ
โ - ๆดๅฅฝ็ๅฏ็ปดๆคๆงๅๆฉๅฑๆง โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### 1.2 ๆจกๅไพ่ตๅ
ณ็ณป
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๆจกๅไพ่ตๅพ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโ โ
โ โ lib.rs โ (ๅ
ฌๅ
ฑ API) โ
โ โโโโโโฌโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ โ
โ โผ โผ โผ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ html โโโโโโโโโโโบโ css โ โ render โ โ
โ โ module โ โ module โ โ module โ โ
โ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โ
โ โ โ โ โ
โ โ โโโโโโโโดโโโโโโโ โ โ
โ โ โ โ โ โ
โ โ โผ โผ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโ โ โ
โ โ โlightningโ โ adapter โ โ โ
โ โ โ css โ โ layer โ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโ โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโ โ
โ โgeneratorโ โ
โ โ module โ โ
โ โโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
---
## 2. HTML ๅญ้ๅฎไน
### 2.1 ๆฏๆ็ HTML ๆ ็ญพๆธ
ๅ
#### 2.1.1 ๆๆกฃ็ปๆ
| `<div>` | ๅ็บง | `style`, `class`, `id` | ้็จๅฎนๅจ |
| `<span>` | ่กๅ
| `style`, `class`, `id` | ่กๅ
ๅฎนๅจ |
| `<center>` | ๅ็บง | `style`, `class` | ๅฑ
ไธญๅฎนๅจ๏ผๅ
ผๅฎนๆงๆๆกฃ๏ผ |
#### 2.1.2 ๆ ้ข
| `<h1>` - `<h6>` | ๅ็บง | `style`, `class`, `id` | ๆ ้ข๏ผ1-6 ็บง |
#### 2.1.3 ๆฎต่ฝไธๆๆฌ
| `<p>` | ๅ็บง | `style`, `class`, `id` | ๆฎต่ฝ |
| `<br/>` | ็ฉบ่กๅ
| - | ๅผบๅถๆข่ก |
| ๆๆฌ่็น | - | - | ็บฏๆๆฌๅ
ๅฎน |
#### 2.1.4 ๆๆฌๆ ทๅผ๏ผๅ
่๏ผ
| `<strong>`, `<b>` | ่กๅ
| `style`, `class` | ๅ ็ฒ |
| `<em>`, `<i>` | ่กๅ
| `style`, `class` | ๆไฝ |
| `<u>` | ่กๅ
| `style`, `class` | ไธๅ็บฟ |
| `<del>`, `<s>` | ่กๅ
| `style`, `class` | ๅ ้ค็บฟ |
| `<mark>` | ่กๅ
| `style`, `class` | ้ซไบฎๆ ่ฎฐ |
| `<kbd>` | ่กๅ
| `style`, `class` | ้ฎ็ๆ้ฎ |
| `<sup>` | ่กๅ
| `style`, `class` | ไธๆ |
| `<sub>` | ่กๅ
| `style`, `class` | ไธๆ |
#### 2.1.5 ้พๆฅไธๅพ็
| `<a>` | ่กๅ
| `href`, `title`, `style`, `class` | ่ถ
้พๆฅ |
| `<img>` | ็ฉบ่กๅ
| `src`, `alt`, `title`, `width`, `height`, `style`, `class` | ๅพ็ |
#### 2.1.6 ๅ่กจ
| `<ul>` | ๅ็บง | `style`, `class` | ๆ ๅบๅ่กจ |
| `<ol>` | ๅ็บง | `start`, `style`, `class` | ๆๅบๅ่กจ |
| `<li>` | ๅ็บง | `style`, `class` | ๅ่กจ้กน |
#### 2.1.7 ไปฃ็
| `<pre>` + `<code>` | ๅ็บง | `class` (่ฏญ่จ) | ไปฃ็ ๅ |
| `<code>` | ่กๅ
| `style`, `class` | ่กๅ
ไปฃ็ |
#### 2.1.8 ๅ
ถไปๅ็บงๅ
็ด
| `<blockquote>` | ๅ็บง | `style`, `class`, `id` | ๅผ็จๅ |
| `<hr/>` | ็ฉบๅ็บง | `style`, `class` | ๆฐดๅนณๅ้็บฟ |
#### 2.1.9 ่กจๆ ผ
| `<table>` | ๅ็บง | `style`, `class` | ่กจๆ ผ |
| `<thead>` | ๅ็บง | `style`, `class` | ่กจๅคดๅบๅ |
| `<tbody>` | ๅ็บง | `style`, `class` | ่กจไฝๅบๅ |
| `<tr>` | ๅ็บง | `style`, `class` | ่กจๆ ผ่ก |
| `<th>` | ๅ็บง | `style`, `class`, `colspan`, `rowspan` | ่กจๅคดๅๅ
ๆ ผ |
| `<td>` | ๅ็บง | `style`, `class`, `colspan`, `rowspan` | ๆฐๆฎๅๅ
ๆ ผ |
### 2.2 ไธๆฏๆ็ HTML ๆ ็ญพ
| ่กจๅ | `<form>`, `<input>`, `<button>` ็ญ | PDF ้ไบคไบๅผๆๆกฃ |
| ๅคๅชไฝ | `<video>`, `<audio>`, `<canvas>` | ้่ฆๅคๆๆธฒๆๆฏๆ |
| ๆกๆถ | `<iframe>`, `<frame>`, `<frameset>` | ๅฎๅ
จๆงๅๅคๆๆง |
| ่ๆฌ | `<script>`, `<noscript>` | PDF ไธๆฏๆ่ๆฌ |
| ๅ
ๆฐๆฎ | `<meta>`, `<link>` (้คๆ ทๅผ) | ๆ ๆไน |
| ๅคๆๅธๅฑ | `<article>`, `<section>`, `<aside>` ็ญ่ฏญไนๆ ็ญพ | ๅฏ็จ `<div>` ๆฟไปฃ |
### 2.3 HTML AST ๅฎไน
```rust
// src/html/ast.rs
use std::collections::HashMap;
/// HTML ๆๆกฃ
#[derive(Debug, Clone)]
pub struct HtmlDocument {
pub root: HtmlElement,
/// ไป <style> ๆ ็ญพๆๅ็ๆ ทๅผ่กจ
pub style_sheets: Vec<String>,
}
/// HTML ๅ
็ด
#[derive(Debug, Clone)]
pub struct HtmlElement {
pub tag: HtmlTag,
pub attrs: HashMap<String, String>,
pub children: Vec<HtmlNode>,
}
/// HTML ่็น๏ผๅ
็ด ๆๆๆฌ๏ผ
#[derive(Debug, Clone)]
pub enum HtmlNode {
Element(HtmlElement),
Text(String),
}
/// HTML ๆ ็ญพๆไธพ
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HtmlTag {
// ๆๆกฃ็ปๆ
Div,
Span,
Center,
// ๆ ้ข
H1, H2, H3, H4, H5, H6,
// ๆฎต่ฝ
P,
Br,
// ๆๆฌๆ ทๅผ
Strong, B,
Em, I,
U,
Del, S,
Mark,
Kbd,
Sup,
Sub,
// ้พๆฅไธๅพ็
A,
Img,
// ๅ่กจ
Ul, Ol, Li,
// ไปฃ็
Pre, Code,
// ๅ
ถไปๅ็บง
Blockquote,
Hr,
// ่กจๆ ผ
Table, Thead, Tbody, Tr, Th, Td,
}
impl HtmlTag {
/// ่ทๅๆ ็ญพๅญ็ฌฆไธฒ่กจ็คบ
pub fn as_str(&self) -> &'static str {
match self {
HtmlTag::Div => "div",
HtmlTag::Span => "span",
HtmlTag::Center => "center",
HtmlTag::H1 => "h1",
HtmlTag::H2 => "h2",
HtmlTag::H3 => "h3",
HtmlTag::H4 => "h4",
HtmlTag::H5 => "h5",
HtmlTag::H6 => "h6",
HtmlTag::P => "p",
HtmlTag::Br => "br",
HtmlTag::Strong => "strong",
HtmlTag::B => "b",
HtmlTag::Em => "em",
HtmlTag::I => "i",
HtmlTag::U => "u",
HtmlTag::Del => "del",
HtmlTag::S => "s",
HtmlTag::Mark => "mark",
HtmlTag::Kbd => "kbd",
HtmlTag::Sup => "sup",
HtmlTag::Sub => "sub",
HtmlTag::A => "a",
HtmlTag::Img => "img",
HtmlTag::Ul => "ul",
HtmlTag::Ol => "ol",
HtmlTag::Li => "li",
HtmlTag::Pre => "pre",
HtmlTag::Code => "code",
HtmlTag::Blockquote => "blockquote",
HtmlTag::Hr => "hr",
HtmlTag::Table => "table",
HtmlTag::Thead => "thead",
HtmlTag::Tbody => "tbody",
HtmlTag::Tr => "tr",
HtmlTag::Th => "th",
HtmlTag::Td => "td",
}
}
/// ไปๅญ็ฌฆไธฒ่งฃๆๆ ็ญพ
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"div" => Some(HtmlTag::Div),
"span" => Some(HtmlTag::Span),
"center" => Some(HtmlTag::Center),
"h1" => Some(HtmlTag::H1),
"h2" => Some(HtmlTag::H2),
"h3" => Some(HtmlTag::H3),
"h4" => Some(HtmlTag::H4),
"h5" => Some(HtmlTag::H5),
"h6" => Some(HtmlTag::H6),
"p" => Some(HtmlTag::P),
"br" => Some(HtmlTag::Br),
"strong" => Some(HtmlTag::Strong),
"b" => Some(HtmlTag::B),
"em" => Some(HtmlTag::Em),
"i" => Some(HtmlTag::I),
"u" => Some(HtmlTag::U),
"del" => Some(HtmlTag::Del),
"s" => Some(HtmlTag::S),
"mark" => Some(HtmlTag::Mark),
"kbd" => Some(HtmlTag::Kbd),
"sup" => Some(HtmlTag::Sup),
"sub" => Some(HtmlTag::Sub),
"a" => Some(HtmlTag::A),
"img" => Some(HtmlTag::Img),
"ul" => Some(HtmlTag::Ul),
"ol" => Some(HtmlTag::Ol),
"li" => Some(HtmlTag::Li),
"pre" => Some(HtmlTag::Pre),
"code" => Some(HtmlTag::Code),
"blockquote" => Some(HtmlTag::Blockquote),
"hr" => Some(HtmlTag::Hr),
"table" => Some(HtmlTag::Table),
"thead" => Some(HtmlTag::Thead),
"tbody" => Some(HtmlTag::Tbody),
"tr" => Some(HtmlTag::Tr),
"th" => Some(HtmlTag::Th),
"td" => Some(HtmlTag::Td),
_ => None,
}
}
/// ๅคๆญๆฏๅฆไธบๅ็บงๅ
็ด
pub fn is_block(&self) -> bool {
matches!(self,
HtmlTag::Div | HtmlTag::Center |
HtmlTag::H1 | HtmlTag::H2 | HtmlTag::H3 |
HtmlTag::H4 | HtmlTag::H5 | HtmlTag::H6 |
HtmlTag::P | HtmlTag::Pre |
HtmlTag::Ul | HtmlTag::Ol | HtmlTag::Li |
HtmlTag::Blockquote | HtmlTag::Hr |
HtmlTag::Table | HtmlTag::Thead | HtmlTag::Tbody |
HtmlTag::Tr | HtmlTag::Th | HtmlTag::Td
)
}
/// ๅคๆญๆฏๅฆไธบ่กๅ
ๅ
็ด
pub fn is_inline(&self) -> bool {
!self.is_block() && !self.is_void()
}
/// ๅคๆญๆฏๅฆไธบ็ฉบๅ
็ด ๏ผๆ ๅ
ๅฎน๏ผ
pub fn is_void(&self) -> bool {
matches!(self, HtmlTag::Br | HtmlTag::Hr | HtmlTag::Img)
}
}
```
---
## 3. ๆจกๅ่ฎพ่ฎก
### 3.1 ๆจกๅ็ปๆ
```
src/
โโโ lib.rs # ๅ
ฌๅ
ฑ API
โโโ error.rs # ้่ฏฏ็ฑปๅ
โโโ html/ # HTML ๅค็ๆจกๅ๏ผๆฐๅข๏ผ
โ โโโ mod.rs # ๆจกๅๅ
ฅๅฃ
โ โโโ ast.rs # HTML AST ๅฎไน
โ โโโ parser.rs # HTML ่งฃๆๅจ
โ โโโ md_converter.rs # Markdown โ HTML ่ฝฌๆข
โ โโโ element.rs # HTML ๅ
็ด ๅทฅๅ
ท
โโโ css/ # CSS ๆ ทๅผๆจกๅ๏ผ้ๆ๏ผ
โ โโโ mod.rs # ๆจกๅๅ
ฅๅฃ
โ โโโ engine.rs # CSS ๅผๆ๏ผๅบไบ Lightning CSS๏ผ
โ โโโ adapter.rs # Lightning CSS ้้
ๅฑ
โ โโโ computed.rs # ่ฎก็ฎๅๆ ทๅผๅฎไน
โ โโโ selector.rs # ้ๆฉๅจๅน้
โ โโโ default.css # ้ป่ฎคๆ ทๅผ่กจ
โโโ layout/ # ๅธๅฑๅผๆ๏ผไป generator ้ๆ๏ผ
โ โโโ mod.rs
โ โโโ context.rs # ๅธๅฑไธไธๆ
โ โโโ flow.rs # ๆตๅผๅธๅฑ
โ โโโ text.rs # ๆๆฌๅธๅฑ
โ โโโ table.rs # ่กจๆ ผๅธๅฑ
โ โโโ pagination.rs # ๅ้กตๅค็
โโโ render/ # ๆธฒๆๆจกๅ๏ผไฟๆ๏ผ
โ โโโ mod.rs
โ โโโ pdf.rs
โ โโโ svg.rs
โ โโโ pixmap.rs
โโโ text.rs # ๆๆฌๅค็๏ผไฟๆ๏ผ
```
### 3.2 HTML ่งฃๆๅจๆจกๅ
```rust
// src/html/parser.rs
use super::ast::*;
use super::element::ElementBuilder;
/// HTML ่งฃๆๅจ
pub struct HtmlParser;
impl HtmlParser {
/// ่งฃๆ HTML ๅญ็ฌฆไธฒ
pub fn parse(html: &str) -> Result<HtmlDocument, HtmlParseError> {
let mut parser = Self;
parser.parse_document(html)
}
/// ่งฃๆๅ
่ HTML๏ผ็จไบ Markdown ไธญ็ HTML ๆ ็ญพ๏ผ
pub fn parse_inline(html: &str) -> Result<Vec<HtmlNode>, HtmlParseError> {
// ๅค็็ฎๅ็ๅ
่ๆ ็ญพๅฆ <br/>, <span>, <b> ็ญ
let mut nodes = Vec::new();
// ๅฎ็ฐ่งฃๆ้ป่พ...
Ok(nodes)
}
fn parse_document(&mut self, html: &str) -> Result<HtmlDocument, HtmlParseError> {
// ไฝฟ็จ roxmltree ๆๆๅจ่งฃๆ
// ่ฟ้็ฎๅๅฑ็คบๆ ธๅฟ้ป่พ
let root = self.parse_element(html)?;
// ๆๅ <style> ๆ ็ญพๅ
ๅฎน
let style_sheets = Self::extract_style_sheets(&root);
Ok(HtmlDocument { root, style_sheets })
}
fn parse_element(&mut self, html: &str) -> Result<HtmlElement, HtmlParseError> {
// ่งฃๆๆ ็ญพใๅฑๆงใๅญๅ
็ด
todo!()
}
/// ไปๆๆกฃไธญๆๅๆๆ <style> ๆ ็ญพๅ
ๅฎน
fn extract_style_sheets(root: &HtmlElement) -> Vec<String> {
let mut sheets = Vec::new();
Self::collect_styles(root, &mut sheets);
sheets
}
fn collect_styles(element: &HtmlElement, sheets: &mut Vec<String>) {
if element.tag == HtmlTag::Style {
// ๆๅๆ ทๅผๅ
ๅฎน
for child in &element.children {
if let HtmlNode::Text(text) = child {
sheets.push(text.clone());
}
}
}
for child in &element.children {
if let HtmlNode::Element(el) = child {
Self::collect_styles(el, sheets);
}
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum HtmlParseError {
#[error("Invalid HTML: {0}")]
InvalidHtml(String),
#[error("Unsupported tag: {0}")]
UnsupportedTag(String),
#[error("Unclosed tag: {0}")]
UnclosedTag(String),
}
```
### 3.3 Markdown ๅฐ HTML ่ฝฌๆขๅจ
```rust
// src/html/md_converter.rs
use markdown::mdast;
use super::ast::*;
use super::element::ElementBuilder;
/// Markdown ๅฐ HTML ่ฝฌๆขๅจ
pub struct MdToHtmlConverter;
impl MdToHtmlConverter {
/// ๅฐ Markdown ่ฝฌๆขไธบ HTML ๆๆกฃ
pub fn convert(mdast: &mdast::Node) -> Result<HtmlDocument, ConvertError> {
let converter = Self;
let root = converter.convert_node(mdast)?;
// ๆๅ <style> ๆ ็ญพ
let style_sheets = Self::extract_styles_from_mdast(mdast);
Ok(HtmlDocument {
root: HtmlElement {
tag: HtmlTag::Div, // ๅ
่ฃ
ๅจ div ไธญ
attrs: HashMap::new(),
children: vec![HtmlNode::Element(root)],
},
style_sheets,
})
}
/// ่ฝฌๆขๅไธช MDAST ่็น
fn convert_node(&self, node: &mdast::Node) -> Result<HtmlElement, ConvertError> {
match node {
mdast::Node::Root(root) => self.convert_root(root),
mdast::Node::Heading(heading) => self.convert_heading(heading),
mdast::Node::Paragraph(para) => self.convert_paragraph(para),
mdast::Node::Text(text) => self.convert_text(&text.value),
mdast::Node::Strong(strong) => self.convert_strong(strong),
mdast::Node::Emphasis(em) => self.convert_emphasis(em),
mdast::Node::List(list) => self.convert_list(list),
mdast::Node::ListItem(item) => self.convert_list_item(item),
mdast::Node::Code(code) => self.convert_code(code),
mdast::Node::InlineCode(code) => self.convert_inline_code(code),
mdast::Node::Link(link) => self.convert_link(link),
mdast::Node::Image(image) => self.convert_image(image),
mdast::Node::Blockquote(bq) => self.convert_blockquote(bq),
mdast::Node::ThematicBreak(_) => self.convert_hr(),
mdast::Node::Table(table) => self.convert_table(table),
mdast::Node::Delete(del) => self.convert_delete(del),
mdast::Node::Html(html) => self.convert_html(&html.value),
_ => Err(ConvertError::UnsupportedNode(format!("{:?}", node))),
}
}
fn convert_heading(&self, heading: &mdast::Heading) -> Result<HtmlElement, ConvertError> {
let tag = match heading.depth {
1 => HtmlTag::H1,
2 => HtmlTag::H2,
3 => HtmlTag::H3,
4 => HtmlTag::H4,
5 => HtmlTag::H5,
6 => HtmlTag::H6,
_ => HtmlTag::H6,
};
let children = self.convert_children(&heading.children)?;
Ok(ElementBuilder::new(tag).children(children).build())
}
fn convert_paragraph(&self, para: &mdast::Paragraph) -> Result<HtmlElement, ConvertError> {
let children = self.convert_children(¶.children)?;
Ok(ElementBuilder::new(HtmlTag::P).children(children).build())
}
fn convert_strong(&self, strong: &mdast::Strong) -> Result<HtmlElement, ConvertError> {
let children = self.convert_children(&strong.children)?;
Ok(ElementBuilder::new(HtmlTag::Strong).children(children).build())
}
fn convert_html(&self, html: &str) -> Result<HtmlElement, ConvertError> {
// ่งฃๆๅ
่ HTML ๅญ็ฌฆไธฒ
super::parser::HtmlParser::parse_inline(html)
.map_err(|e| ConvertError::HtmlParseError(e.to_string()))
.and_then(|nodes| {
// ๅฆๆๅชๆไธไธชๅ
็ด ๏ผ่ฟๅๅฎ
if nodes.len() == 1 {
if let HtmlNode::Element(el) = &nodes[0] {
return Ok(el.clone());
}
}
// ๅฆๅๅ
่ฃ
ๅจ div ไธญ
Ok(ElementBuilder::new(HtmlTag::Div).children(nodes).build())
})
}
// ... ๅ
ถไป่ฝฌๆขๆนๆณ
/// ่ฝฌๆขๅญ่็นๅ่กจ
fn convert_children(&self, children: &[mdast::Node]) -> Result<Vec<HtmlNode>, ConvertError> {
let mut result = Vec::new();
for child in children {
match self.convert_node(child)? {
el if el.tag == HtmlTag::Div && el.attrs.is_empty() => {
// ๅฑๅผ็ฉบ็ div ๅ
่ฃ
result.extend(el.children);
}
el => result.push(HtmlNode::Element(el)),
}
}
Ok(result)
}
}
/// ๅ
็ด ๆๅปบๅจ๏ผ่พ
ๅฉ๏ผ
mod element {
use super::*;
pub struct ElementBuilder {
tag: HtmlTag,
attrs: HashMap<String, String>,
children: Vec<HtmlNode>,
}
impl ElementBuilder {
pub fn new(tag: HtmlTag) -> Self {
Self {
tag,
attrs: HashMap::new(),
children: Vec::new(),
}
}
pub fn attr(mut self, key: &str, value: &str) -> Self {
self.attrs.insert(key.to_string(), value.to_string());
self
}
pub fn children(mut self, children: Vec<HtmlNode>) -> Self {
self.children = children;
self
}
pub fn build(self) -> HtmlElement {
HtmlElement {
tag: self.tag,
attrs: self.attrs,
children: self.children,
}
}
}
}
```
---
## 4. CSS ๆ ทๅผ็ณป็ป
### 4.1 ๆถๆ่ฎพ่ฎก
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CSS ๆ ทๅผ็ณป็ปๆถๆ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Lightning CSS ๅฑ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โ CSS ่งฃๆ โ โ ้ๆฉๅจๅน้
โ โ ๅฑๆงๅค็ โ โ โ
โ โ โ โ โ โ โ โ โ โ
โ โ โ - ๅฎๆด CSS โ โ - ID/็ฑป/ๆ ็ญพโ โ - ็ฑปๅๅฎๅ
จ โ โ โ
โ โ โ - ็ฎๅๅฑๅผ โ โ - ๅฑๆง้ๆฉๅจโ โ - ๅไฝ่ฝฌๆข โ โ โ
โ โ โ - ้ข่ฒๅค็ โ โ - ไผช็ฑป โ โ - ่ฎก็ฎๅผ โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ liepress ้้
ๅฑ โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โ ๆ ทๅผ่ฝฌๆข โ โ PDF ๆฉๅฑ โ โ ๅไฝๅค็ โ โ โ
โ โ โ โ โ โ โ โ โ โ
โ โ โ Lightning โ โ - ๅ้กตๆงๅถ โ โ - pxโpt โ โ โ
โ โ โ ๅฑๆง โ โ โ - ้กต็้กต่ โ โ - em/rem โ โ โ
โ โ โ liepress โ โ - ๆๅฐไผๅ โ โ - ็พๅๆฏ โ โ โ
โ โ โ ๆ ทๅผ โ โ โ โ โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ComputedStyles โ โ
โ โ ๏ผๅธๅฑๅผๆไฝฟ็จ็ๆ็ปๆ ทๅผ๏ผ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### 4.2 ไพ่ต้
็ฝฎ
```toml
# Cargo.toml
[dependencies]
# ... ็ฐๆไพ่ต ...
# CSS ๅค็
lightningcss = { version = "1.0.0-alpha.70", features = ["into_owned"] }
# HTML ่งฃๆ๏ผๅฏ้๏ผ็จไบๅค็ๅ
่ HTML๏ผ
roxmltree = "0.20"
# ้ๆฉๅจๅน้
๏ผLightning CSS ๅทฒๅ
ๅซ๏ผไฝๅฏ่ฝ้่ฆๅ็ฌไฝฟ็จ๏ผ
selectors = "0.25"
cssparser = "0.36"
```
### 4.3 CSS ๅผๆๅฎ็ฐ
```rust
// src/css/engine.rs
use lightningcss::stylesheet::{StyleSheet, ParserOptions, PrinterOptions};
use lightningcss::selector::Selector;
use lightningcss::properties::Property;
use lightningcss::values::length::Length;
use lightningcss::declaration::DeclarationBlock;
use super::computed::ComputedStyles;
use super::adapter::LightningCssAdapter;
/// CSS ๆ ทๅผๅผๆ
pub struct CssEngine {
/// ็จๆทไปฃ็ๆ ทๅผ่กจ๏ผๅ
็ฝฎ้ป่ฎค๏ผ
ua_stylesheet: StyleSheet<'static, 'static>,
/// ็จๆทๆ ทๅผ่กจ
user_stylesheet: Option<StyleSheet<'static, 'static>>,
/// ไฝ่
ๆ ทๅผ่กจ๏ผๆฅ่ช <style> ๆ ็ญพ๏ผ
author_stylesheet: Option<StyleSheet<'static, 'static>>,
/// ้้
ๅจ
adapter: LightningCssAdapter,
}
impl CssEngine {
/// ๅๅปบๆฐ็ CSS ๅผๆ
pub fn new() -> Result<Self, CssError> {
let default_css = include_str!("default.css");
let ua_stylesheet = StyleSheet::parse(default_css, ParserOptions::default())
.map_err(|e| CssError::ParseError(e.to_string()))?;
Ok(Self {
ua_stylesheet,
user_stylesheet: None,
author_stylesheet: None,
adapter: LightningCssAdapter::new(),
})
}
/// ๆทปๅ ็จๆท CSS
pub fn with_user_css(mut self, css: &str) -> Result<Self, CssError> {
self.user_stylesheet = Some(
StyleSheet::parse(css, ParserOptions::default())
.map_err(|e| CssError::ParseError(e.to_string()))?
);
Ok(self)
}
/// ๆทปๅ ไฝ่
CSS๏ผๆฅ่ช <style> ๆ ็ญพ๏ผ
pub fn with_author_css(mut self, css: &str) -> Result<Self, CssError> {
self.author_stylesheet = Some(
StyleSheet::parse(css, ParserOptions::default())
.map_err(|e| CssError::ParseError(e.to_string()))?
);
Ok(self)
}
/// ่ฎก็ฎๅ
็ด ็ๆ ทๅผ
pub fn compute_styles(
&self,
element: &HtmlElement,
parent_styles: Option<&ComputedStyles>,
) -> ComputedStyles {
// 1. ๅๅปบๆ ทๅผไธไธๆ
let mut context = StyleContext::new(parent_styles);
// 2. ๆถ้ๆๆๅน้
็่งๅ๏ผๆไผๅ
็บงๆๅบ๏ผ
let rules = self.collect_matching_rules(element);
// 3. ๅบ็จๆ ทๅผ่งๅ
for (specificity, declarations) in rules {
context.apply_declarations(declarations, specificity);
}
// 4. ๅบ็จๅ
่ๆ ทๅผ๏ผๆ้ซไผๅ
็บง๏ผ
if let Some(style_attr) = element.attrs.get("style") {
if let Ok(inline_sheet) = StyleSheet::parse(
&format!("{{ {style_attr} }}"),
ParserOptions::default()
) {
for rule in inline_sheet.rules.0 {
if let Some(declarations) = rule.declarations {
context.apply_declarations(declarations, SPECIFICITY_INLINE);
}
}
}
}
// 5. ่ฎก็ฎๆ็ปๅผ
context.into_computed_styles(&self.adapter)
}
/// ๆถ้ๅน้
็่งๅ
fn collect_matching_rules(
&self,
element: &HtmlElement,
) -> Vec<(u32, DeclarationBlock<'static, 'static>)> {
let mut rules = Vec::new();
// UA ๆ ทๅผ๏ผๆไฝไผๅ
็บง๏ผ
self.collect_from_stylesheet(&self.ua_stylesheet, element, &mut rules);
// ็จๆทๆ ทๅผ
if let Some(sheet) = &self.user_stylesheet {
self.collect_from_stylesheet(sheet, element, &mut rules);
}
// ไฝ่
ๆ ทๅผ
if let Some(sheet) = &self.author_stylesheet {
self.collect_from_stylesheet(sheet, element, &mut rules);
}
// ๆ็นๅผๆงๆๅบ
rules.sort_by_key(|(spec, _)| *spec);
rules
}
fn collect_from_stylesheet(
&self,
sheet: &StyleSheet,
element: &HtmlElement,
rules: &mut Vec<(u32, DeclarationBlock<'static, 'static>)>,
) {
for rule in &sheet.rules.0 {
if let Some(selector) = &rule.selectors {
if self.matches_selector(selector, element) {
let specificity = self.calculate_specificity(selector);
if let Some(declarations) = &rule.declarations {
rules.push((specificity, declarations.clone()));
}
}
}
}
}
/// ๆฃๆฅ้ๆฉๅจๆฏๅฆๅน้
ๅ
็ด
fn matches_selector(&self, selector: &Selector, element: &HtmlElement) -> bool {
// ็ฎๅๅฎ็ฐ๏ผๆฃๆฅๆ ็ญพๅใ็ฑปๅใID
// ๅฎ้
ๅฎ็ฐ้่ฆไฝฟ็จ Lightning CSS ็้ๆฉๅจๅน้
todo!("ๅฎ็ฐ้ๆฉๅจๅน้
")
}
/// ่ฎก็ฎ้ๆฉๅจ็นๅผๆง
fn calculate_specificity(&self, selector: &Selector) -> u32 {
// ID: 100, ็ฑป/ๅฑๆง/ไผช็ฑป: 10, ๆ ็ญพ: 1
todo!("่ฎก็ฎ็นๅผๆง")
}
}
const SPECIFICITY_INLINE: u32 = 10000;
/// ๆ ทๅผไธไธๆ๏ผๆๅปบไธญ๏ผ
struct StyleContext {
parent: Option<ComputedStyles>,
properties: HashMap<String, Property>,
}
impl StyleContext {
fn new(parent: Option<&ComputedStyles>) -> Self {
Self {
parent: parent.cloned(),
properties: HashMap::new(),
}
}
fn apply_declarations(&mut self, declarations: DeclarationBlock, specificity: u32) {
for decl in &declarations.declarations {
// ๅญๅจๅฑๆง๏ผๅ็ปญๅค็ๅฑๅ
self.properties.insert(
decl.property_id().name().to_string(),
decl.clone()
);
}
}
fn into_computed_styles(self, adapter: &LightningCssAdapter) -> ComputedStyles {
let mut computed = self.parent.unwrap_or_default();
for (_, property) in self.properties {
adapter.apply_property(&mut computed, &property);
}
computed
}
}
```
### 4.4 Lightning CSS ้้
ๅฑ
```rust
// src/css/adapter.rs
use lightningcss::properties::Property;
use lightningcss::values::length::{Length, LengthValue};
use lightningcss::values::color::CssColor;
use lightningcss::values::percentage::Percentage;
use super::computed::{ComputedStyles, CssValue, LengthUnit};
/// Lightning CSS ๅฐ liepress ็้้
ๅจ
pub struct LightningCssAdapter;
impl LightningCssAdapter {
pub fn new() -> Self {
Self
}
/// ๅฐ Lightning CSS ๅฑๆงๅบ็จๅฐ liepress ๆ ทๅผ
pub fn apply_property(&self, computed: &mut ComputedStyles, property: &Property) {
match property {
// ๅญไฝ็ธๅ
ณ
Property::FontSize(size) => {
computed.font_size = self.convert_length(size);
}
Property::FontFamily(families) => {
computed.font_family = families.0.iter()
.map(|f| f.to_string())
.collect();
}
Property::FontWeight(weight) => {
computed.font_weight = self.convert_font_weight(weight);
}
Property::FontStyle(style) => {
computed.font_style = self.convert_font_style(style);
}
Property::LineHeight(height) => {
computed.line_height = self.convert_line_height(height);
}
Property::LetterSpacing(spacing) => {
computed.letter_spacing = self.convert_length(&spacing.0);
}
// ๆๆฌ็ธๅ
ณ
Property::Color(color) => {
computed.color = self.convert_color(color);
}
Property::TextAlign(align) => {
computed.text_align = self.convert_text_align(align);
}
Property::TextIndent(indent) => {
computed.text_indent = self.convert_length(indent);
}
Property::TextDecoration(decoration) => {
computed.text_decoration = self.convert_text_decoration(decoration);
}
// ๅธๅฑ็ธๅ
ณ
Property::Display(display) => {
computed.display = self.convert_display(display);
}
Property::Width(width) => {
computed.width = Some(self.convert_length(&width.0));
}
Property::Height(height) => {
computed.height = Some(self.convert_length(&height.0));
}
// ่พน่ท
Property::Margin(margin) => {
computed.margin_top = self.convert_length(&margin.top);
computed.margin_right = self.convert_length(&margin.right);
computed.margin_bottom = self.convert_length(&margin.bottom);
computed.margin_left = self.convert_length(&margin.left);
}
Property::MarginTop(m) => computed.margin_top = self.convert_length(m),
Property::MarginRight(m) => computed.margin_right = self.convert_length(m),
Property::MarginBottom(m) => computed.margin_bottom = self.convert_length(m),
Property::MarginLeft(m) => computed.margin_left = self.convert_length(m),
// ๅกซๅ
Property::Padding(padding) => {
computed.padding_top = self.convert_length(&padding.top);
computed.padding_right = self.convert_length(&padding.right);
computed.padding_bottom = self.convert_length(&padding.bottom);
computed.padding_left = self.convert_length(&padding.left);
}
Property::PaddingTop(p) => computed.padding_top = self.convert_length(p),
Property::PaddingRight(p) => computed.padding_right = self.convert_length(p),
Property::PaddingBottom(p) => computed.padding_bottom = self.convert_length(p),
Property::PaddingLeft(p) => computed.padding_left = self.convert_length(p),
// ่พนๆก
Property::BorderTopWidth(w) => computed.border_top_width = self.convert_length(w),
Property::BorderRightWidth(w) => computed.border_right_width = self.convert_length(w),
Property::BorderBottomWidth(w) => computed.border_bottom_width = self.convert_length(w),
Property::BorderLeftWidth(w) => computed.border_left_width = self.convert_length(w),
Property::BorderTopColor(c) => computed.border_top_color = Some(self.convert_color(c)),
Property::BorderRightColor(c) => computed.border_right_color = Some(self.convert_color(c)),
Property::BorderBottomColor(c) => computed.border_bottom_color = Some(self.convert_color(c)),
Property::BorderLeftColor(c) => computed.border_left_color = Some(self.convert_color(c)),
// ่ๆฏ
Property::BackgroundColor(color) => {
computed.background_color = Some(self.convert_color(color));
}
// ๅ้กตๆงๅถ๏ผliepress ๆฉๅฑ๏ผ
Property::Custom(custom) if custom.name.as_ref() == "page-break-before" => {
computed.page_break_before = self.convert_page_break(&custom.value);
}
Property::Custom(custom) if custom.name.as_ref() == "page-break-after" => {
computed.page_break_after = self.convert_page_break(&custom.value);
}
// ๅฟฝ็ฅไธๆฏๆ็ๅฑๆง
_ => {}
}
}
/// ่ฝฌๆข้ฟๅบฆๅผ
fn convert_length(&self, length: &Length) -> CssValue {
match length {
Length::Value(val) => match val {
LengthValue::Px(v) => CssValue::Length(*v, LengthUnit::Px),
LengthValue::Pt(v) => CssValue::Length(*v, LengthUnit::Pt),
LengthValue::Em(v) => CssValue::Length(*v, LengthUnit::Em),
LengthValue::Rem(v) => CssValue::Length(*v, LengthUnit::Rem),
LengthValue::Percent(Percentage(v)) => CssValue::Percentage(*v),
LengthValue::Mm(v) => CssValue::Length(*v, LengthUnit::Mm),
LengthValue::Cm(v) => CssValue::Length(*v, LengthUnit::Cm),
LengthValue::In(v) => CssValue::Length(*v, LengthUnit::In),
_ => CssValue::Length(0.0, LengthUnit::Px),
},
_ => CssValue::Length(0.0, LengthUnit::Px),
}
}
/// ่ฝฌๆข้ข่ฒ
fn convert_color(&self, color: &CssColor) -> crate::visual::Color {
match color {
CssColor::RGB(rgb) => crate::visual::Color::new(
rgb.red,
rgb.green,
rgb.blue,
),
CssColor::RGBA(rgba) => crate::visual::Color::with_alpha(
rgba.red,
rgba.green,
rgba.blue,
(rgba.alpha * 255.0) as u8,
),
CssColor::Hex(hex) => crate::visual::Color::new(
((hex >> 16) & 0xFF) as u8,
((hex >> 8) & 0xFF) as u8,
(hex & 0xFF) as u8,
),
_ => crate::visual::Color::new(0, 0, 0),
}
}
// ... ๅ
ถไป่ฝฌๆขๆนๆณ
}
```
### 4.5 ่ฎก็ฎๅๆ ทๅผๅฎไน
```rust
// src/css/computed.rs
use crate::visual::Color;
/// CSS ๅผ็ฑปๅ
#[derive(Debug, Clone)]
pub enum CssValue {
Length(f32, LengthUnit),
Percentage(f32),
Color(Color),
String(String),
Ident(String),
Number(f32),
List(Vec<CssValue>),
None,
}
/// ้ฟๅบฆๅไฝ
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LengthUnit {
Px,
Pt,
Em,
Rem,
In,
Cm,
Mm,
Percent,
}
/// ่ฎก็ฎๅ็ๆ ทๅผ๏ผไพๅธๅฑๅผๆไฝฟ็จ๏ผ
#[derive(Debug, Clone)]
pub struct ComputedStyles {
// ๅญไฝ
pub font_family: Vec<String>,
pub font_size: CssValue,
pub font_weight: FontWeight,
pub font_style: FontStyle,
pub line_height: CssValue,
pub letter_spacing: CssValue,
pub color: Color,
// ๆๆฌ
pub text_align: TextAlign,
pub text_indent: CssValue,
pub text_decoration: TextDecoration,
pub text_transform: TextTransform,
pub white_space: WhiteSpace,
// ๅธๅฑ
pub display: Display,
pub width: Option<CssValue>,
pub height: Option<CssValue>,
// ็ๆจกๅ
pub margin_top: CssValue,
pub margin_right: CssValue,
pub margin_bottom: CssValue,
pub margin_left: CssValue,
pub padding_top: CssValue,
pub padding_right: CssValue,
pub padding_bottom: CssValue,
pub padding_left: CssValue,
// ่พนๆก
pub border_top_width: CssValue,
pub border_right_width: CssValue,
pub border_bottom_width: CssValue,
pub border_left_width: CssValue,
pub border_top_color: Option<Color>,
pub border_right_color: Option<Color>,
pub border_bottom_color: Option<Color>,
pub border_left_color: Option<Color>,
pub border_top_style: BorderStyle,
pub border_right_style: BorderStyle,
pub border_bottom_style: BorderStyle,
pub border_left_style: BorderStyle,
// ่ๆฏ
pub background_color: Option<Color>,
// ๅ้กต
pub page_break_before: PageBreak,
pub page_break_after: PageBreak,
// ่กจๆ ผ
pub border_collapse: BorderCollapse,
pub border_spacing: (CssValue, CssValue),
// ๅ่กจ
pub list_style_type: ListStyleType,
// ้พๆฅ๏ผ็นๆฎ๏ผ
pub link_url: Option<String>,
}
impl Default for ComputedStyles {
fn default() -> Self {
Self {
font_family: vec!["serif".to_string()],
font_size: CssValue::Length(10.5, LengthUnit::Pt),
font_weight: FontWeight::Normal,
font_style: FontStyle::Normal,
line_height: CssValue::Number(1.5),
letter_spacing: CssValue::Length(0.0, LengthUnit::Px),
color: Color::new(0, 0, 0),
text_align: TextAlign::Left,
text_indent: CssValue::Length(0.0, LengthUnit::Pt),
text_decoration: TextDecoration::None,
text_transform: TextTransform::None,
white_space: WhiteSpace::Normal,
display: Display::Block,
width: None,
height: None,
margin_top: CssValue::Length(0.0, LengthUnit::Pt),
margin_right: CssValue::Length(0.0, LengthUnit::Pt),
margin_bottom: CssValue::Length(12.0, LengthUnit::Pt),
margin_left: CssValue::Length(0.0, LengthUnit::Pt),
padding_top: CssValue::Length(0.0, LengthUnit::Pt),
padding_right: CssValue::Length(0.0, LengthUnit::Pt),
padding_bottom: CssValue::Length(0.0, LengthUnit::Pt),
padding_left: CssValue::Length(0.0, LengthUnit::Pt),
border_top_width: CssValue::Length(0.0, LengthUnit::Pt),
border_right_width: CssValue::Length(0.0, LengthUnit::Pt),
border_bottom_width: CssValue::Length(0.0, LengthUnit::Pt),
border_left_width: CssValue::Length(0.0, LengthUnit::Pt),
border_top_color: None,
border_right_color: None,
border_bottom_color: None,
border_left_color: None,
border_top_style: BorderStyle::None,
border_right_style: BorderStyle::None,
border_bottom_style: BorderStyle::None,
border_left_style: BorderStyle::None,
background_color: None,
page_break_before: PageBreak::Auto,
page_break_after: PageBreak::Auto,
border_collapse: BorderCollapse::Collapse,
border_spacing: (CssValue::Length(0.0, LengthUnit::Pt), CssValue::Length(0.0, LengthUnit::Pt)),
list_style_type: ListStyleType::Disc,
link_url: None,
}
}
}
impl ComputedStyles {
/// ไป็ถๆ ทๅผ็ปงๆฟ
pub fn inherit_from(&mut self, parent: &Self) {
// ๅฏ็ปงๆฟๅฑๆง
self.font_family = parent.font_family.clone();
self.font_size = parent.font_size.clone();
self.font_weight = parent.font_weight;
self.font_style = parent.font_style;
self.line_height = parent.line_height.clone();
self.letter_spacing = parent.letter_spacing.clone();
self.color = parent.color;
self.text_align = parent.text_align;
self.text_transform = parent.text_transform;
self.white_space = parent.white_space;
self.page_break_before = parent.page_break_before;
self.page_break_after = parent.page_break_after;
self.list_style_type = parent.list_style_type;
// ไธๅฏ็ปงๆฟๅฑๆงไฟๆ้ป่ฎคๅผ
}
/// ่งฃๆ้ฟๅบฆๅผไธบ pt
pub fn length_to_pt(&self, value: &CssValue, parent_size: f32) -> f32 {
match value {
CssValue::Length(v, LengthUnit::Pt) => *v,
CssValue::Length(v, LengthUnit::Px) => *v * 0.75, // ๅ่ฎพ 96 DPI
CssValue::Length(v, LengthUnit::Em) => *v * self.font_size_pt(),
CssValue::Length(v, LengthUnit::Rem) => *v * 10.5, // ๆ นๅญไฝๅคงๅฐ
CssValue::Length(v, LengthUnit::In) => *v * 72.0,
CssValue::Length(v, LengthUnit::Cm) => *v * 28.35,
CssValue::Length(v, LengthUnit::Mm) => *v * 2.835,
CssValue::Percentage(v) => *v * parent_size / 100.0,
_ => 0.0,
}
}
fn font_size_pt(&self) -> f32 {
match &self.font_size {
CssValue::Length(v, LengthUnit::Pt) => *v,
CssValue::Length(v, LengthUnit::Px) => *v * 0.75,
_ => 10.5,
}
}
}
// ๆไธพๅฎไน
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FontWeight {
Normal,
Bold,
W100, W200, W300, W400, W500, W600, W700, W800, W900,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FontStyle {
Normal,
Italic,
Oblique,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TextAlign {
Left,
Center,
Right,
Justify,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TextDecoration {
None,
Underline,
LineThrough,
Overline,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TextTransform {
None,
Uppercase,
Lowercase,
Capitalize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WhiteSpace {
Normal,
Nowrap,
Pre,
PreWrap,
PreLine,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Display {
Block,
Inline,
InlineBlock,
None,
Table,
TableRow,
TableCell,
Flex, // ๆชๆฅๆฏๆ
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderStyle {
None,
Solid,
Dashed,
Dotted,
Double,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PageBreak {
Auto,
Always,
Avoid,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderCollapse {
Collapse,
Separate,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ListStyleType {
None,
Disc,
Circle,
Square,
Decimal,
DecimalLeadingZero,
LowerRoman,
UpperRoman,
LowerAlpha,
UpperAlpha,
}
```
---
## 5. ๆฐๆฎๆตไธๅค็ๆต็จ
### 5.1 ๅฎๆดๅค็ๆต็จ
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๅฎๆดๅค็ๆต็จ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 1. ่พๅ
ฅ้ถๆฎต โ
โ โโโโโโโโโโโโโโโ โ
โ โ Markdown โ ็จๆท่พๅ
ฅ็ Markdown ๆๆฌ๏ผๅฏ่ฝๅ
ๅซ HTML ๆ ็ญพ๏ผ โ
โ โ Source โ โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ โ
โ โผ โ
โ 2. ่งฃๆ้ถๆฎต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Markdown Parser โ โ
โ โ - ไฝฟ็จ markdown crate ่งฃๆไธบ MDAST โ โ
โ โ - ไฟ็ HTML ๆ ็ญพไธบ Html ่็น โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ MDAST ๆ โ โ
โ โ Root โ โ
โ โ โโโ Heading(1, "ๆ ้ข") โ โ
โ โ โโโ Paragraph โ โ
โ โ โ โโโ Text("ๆฎต่ฝๅ
ๅฎน") โ โ
โ โ โโโ Html("<div style='...'>") โ โ
โ โ โ โโโ Paragraph โ โ
โ โ โ โโโ Text("HTML ๅ
ๅฎน") โ โ
โ โ โโโ Html("</div>") โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 3. ่ฝฌๆข้ถๆฎต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Markdown โ HTML Converter โ โ
โ โ - ้ๅ MDAST ๆ โ โ
โ โ - ๅฐ Markdown ่็น่ฝฌๆขไธบ HTML AST ่็น โ โ
โ โ - ่งฃๆๅ
่ HTML ๅญ็ฌฆไธฒ โ โ
โ โ - ๆๅ <style> ๆ ็ญพๅ
ๅฎน โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 4. HTML ๅค็้ถๆฎต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ HTML AST โ โ
โ โ Element(div) โ โ
โ โ โโโ Element(h1) โ โ
โ โ โ โโโ Text("ๆ ้ข") โ โ
โ โ โโโ Element(p) โ โ
โ โ โ โโโ Text("ๆฎต่ฝๅ
ๅฎน") โ โ
โ โ โโโ Element(div, style="...") โ โ
โ โ โโโ Element(p) โ โ
โ โ โโโ Text("HTML ๅ
ๅฎน") โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 5. ๆ ทๅผ่ฎก็ฎ้ถๆฎต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ CSS Engine โ โ
โ โ - ่งฃๆๅ
็ฝฎ้ป่ฎค CSS โ โ
โ โ - ่งฃๆ็จๆทๆไพ็ CSS โ โ
โ โ - ่งฃๆ <style> ๆ ็ญพไธญ็ CSS โ โ
โ โ - ้ๅฝ่ฎก็ฎๆฏไธชๅ
็ด ็ ComputedStyles โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 6. ๆ ทๅผๅ HTML โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Styled HTML Document โ โ
โ โ Element(div, styles={...}) โ โ
โ โ โโโ Element(h1, styles={font_size: 24pt, ...}) โ โ
โ โ โ โโโ Text("ๆ ้ข") โ โ
โ โ โโโ ... โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 7. ๅธๅฑ้ถๆฎต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Layout Engine โ โ
โ โ - ๆตๅผๅธๅฑ่ฎก็ฎ โ โ
โ โ - ๅ้กตๅค็ โ โ
โ โ - ็ๆ Document ๅฏน่ฑก โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 8. ๆธฒๆ้ถๆฎต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Document โ โ
โ โ - ้กต้ขๅ่กจ โ โ
โ โ - ่ง่งๅ
็ด ๅ่กจ โ โ
โ โ - ๅธๅฑไฟกๆฏ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ 9. ่พๅบ้ถๆฎต โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Renderer โ โ
โ โ โโโ PDF Renderer (krilla) โ โ
โ โ โโโ SVG Renderer (vello_cpu) โ โ
โ โ โโโ Pixmap Renderer (vello_cpu) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### 5.2 ๆ ธๅฟ API ่ฎพ่ฎก
```rust
// src/lib.rs
pub mod html;
pub mod css;
pub mod layout;
pub mod render;
use html::{HtmlDocument, HtmlParser, MdToHtmlConverter};
use css::CssEngine;
use layout::LayoutEngine;
use render::{PdfRenderer, SvgRenderer, PixmapRenderer};
/// ไธป่ฝฌๆขๅจ
pub struct Liepress {
css_engine: CssEngine,
layout_engine: LayoutEngine,
}
impl Liepress {
/// ๅๅปบๆฐ็่ฝฌๆขๅจๅฎไพ
pub fn new() -> Result<Self, LiepressError> {
Ok(Self {
css_engine: CssEngine::new()?,
layout_engine: LayoutEngine::new(),
})
}
/// ๆทปๅ ็จๆท CSS
pub fn with_css(mut self, css: &str) -> Result<Self, LiepressError> {
self.css_engine = self.css_engine.with_user_css(css)?;
Ok(self)
}
/// ๅฐ Markdown ่ฝฌๆขไธบ PDF
pub fn markdown_to_pdf(&self, markdown: &str) -> Result<PdfDocument, LiepressError> {
// 1. ่งฃๆ Markdown
let mdast = markdown::to_mdast(markdown, &ParseOptions::gfm())?;
// 2. ่ฝฌๆขไธบ HTML
let html_doc = MdToHtmlConverter::convert(&mdast)?;
// 3. ๆทปๅ <style> ๆ ็ญพไธญ็ CSS
for style in &html_doc.style_sheets {
self.css_engine.add_author_css(style)?;
}
// 4. ่ฎก็ฎๆ ทๅผ
let styled_doc = self.apply_styles(html_doc)?;
// 5. ๅธๅฑ
let document = self.layout_engine.layout(styled_doc)?;
// 6. ๆธฒๆ
PdfRenderer::render(&document)
}
/// ๅฐ Markdown ่ฝฌๆขไธบ SVG
pub fn markdown_to_svg(&self, markdown: &str) -> Result<SvgDocument, LiepressError> {
// ็ฑปไผผๅฎ็ฐ...
todo!()
}
/// ๅบ็จๆ ทๅผๅฐ HTML ๆๆกฃ
fn apply_styles(&self, doc: HtmlDocument) -> Result<StyledDocument, LiepressError> {
let mut styled_root = StyledElement::from(doc.root);
self.style_element_recursive(&mut styled_root, None)?;
Ok(StyledDocument::new(styled_root))
}
fn style_element_recursive(
&self,
element: &mut StyledElement,
parent_styles: Option<&ComputedStyles>,
) -> Result<(), LiepressError> {
// ่ฎก็ฎๅฝๅๅ
็ด ๆ ทๅผ
let computed = self.css_engine.compute_styles(&element.element, parent_styles);
// ๅบ็จๆ ทๅผ
element.styles = computed.clone();
// ้ๅฝๅค็ๅญๅ
็ด
for child in &mut element.children {
if let Some(el) = child.as_element_mut() {
self.style_element_recursive(el, Some(&computed))?;
}
}
Ok(())
}
}
```
---
## 6. ๅฎ็ฐ่งๅ
### 6.1 ้ถๆฎตๅๅ
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๅฎ็ฐ้ถๆฎต่งๅ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ ้ถๆฎต 1๏ผๅบ็กๆถๆ๏ผ2-3 ๅจ๏ผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ ๅๅปบ src/html/ ๆจกๅ โ โ
โ โ โ ๅฎไน HTML AST ็ปๆ๏ผast.rs๏ผ โ โ
โ โ โ ๅฎ็ฐ HTML ๆ ็ญพๆไธพๅๅทฅๅ
ทๆนๆณ โ โ
โ โ โ ๅๅปบ src/css/ ๆจกๅ็ปๆ โ โ
โ โ โ ๆทปๅ lightningcss ไพ่ต โ โ
โ โ โ ๅฎไน ComputedStyles ็ปๆ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ ้ถๆฎต 2๏ผMarkdown โ HTML ่ฝฌๆข๏ผ2 ๅจ๏ผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ ๅฎ็ฐ MdToHtmlConverter โ โ
โ โ โ ๆฏๆๆๆ Markdown ๅบ็ก่ฏญๆณ่ฝฌๆข โ โ
โ โ โ ๅฎ็ฐๅ
่ HTML ่งฃๆ โ โ
โ โ โ ๆๅ <style> ๆ ็ญพๅ
ๅฎน โ โ
โ โ โ ็ผๅ่ฝฌๆขๆต่ฏ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ ้ถๆฎต 3๏ผCSS ๆ ทๅผ็ณป็ป๏ผ3 ๅจ๏ผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ ๅฎ็ฐ CssEngine๏ผๅบไบ Lightning CSS๏ผ โ โ
โ โ โ ๅฎ็ฐ LightningCssAdapter โ โ
โ โ โ ๆฏๆๅบ็ก้ๆฉๅจ๏ผๆ ็ญพใ็ฑปใID๏ผ โ โ
โ โ โ ๆฏๆๅธธ็จ CSS ๅฑๆง โ โ
โ โ โ ๅฎ็ฐๆ ทๅผ็ปงๆฟๅๅฑๅ โ โ
โ โ โ ๅไฝ่ฝฌๆข๏ผpxโpt, em, rem, %๏ผ โ โ
โ โ โ ็ผๅๆ ทๅผๆต่ฏ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ ้ถๆฎต 4๏ผๅธๅฑๅผๆ้ๆ๏ผ2 ๅจ๏ผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ ้้
ๆฐ็ StyledDocument ็ปๆ โ โ
โ โ โ ้ๆๆๆฌๅธๅฑ๏ผๆฏๆๆฐ็ ComputedStyles๏ผ โ โ
โ โ โ ้ๆๅ็บงๅ
็ด ๅธๅฑ โ โ
โ โ โ ไฟๆ็ฐๆๅ้กต้ป่พ โ โ
โ โ โ ไฟๆ่กจๆ ผๅธๅฑ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ ้ถๆฎต 5๏ผHTML ๆ ็ญพๆฏๆ๏ผ2 ๅจ๏ผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ ๅฎ็ฐ HTML ่งฃๆๅจ๏ผๆฏๆ HTML ๅญ้๏ผ โ โ
โ โ โ ๆฏๆๆๆๅฎไน็ HTML ๆ ็ญพ โ โ
โ โ โ ๆฏๆๆ ็ญพๅฑๆง๏ผclass, id, style๏ผ โ โ
โ โ โ ๆฏๆๅตๅฅ HTML ็ปๆ โ โ
โ โ โ ็ผๅ HTML ่งฃๆๆต่ฏ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ ้ถๆฎต 6๏ผ้ๆไธไผๅ๏ผ2-3 ๅจ๏ผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ ๆดๅๆๆๆจกๅ โ โ
โ โ โ ๆง่ฝไผๅ โ โ
โ โ โ ๅฎๆด้ๆๆต่ฏ โ โ
โ โ โ ๆๆกฃๆดๆฐ โ โ
โ โ โ ่ฟ็งปๆๅ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ ้ข่ฎกๆปๅทฅๆ๏ผ11-13 ๅจ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
### 6.2 ่ฟ็งป็ญ็ฅ
#### 6.2.1 ๅๅๅ
ผๅฎนๆง
```rust
// src/lib.rs
/// ๆธฒๆ Markdown๏ผๅ
ผๅฎนๆง API๏ผ
pub fn render_markdown(input: &str, options: &RenderOptions) -> Result<Document, Error> {
// ไฝฟ็จๆฐ็ HTML ๅญ้ๆถๆ
let html_doc = MarkdownToHtmlConverter::convert(input)?;
let styled_doc = CssEngine::compute_styles(html_doc, &options.css)?;
Document::from_styled_html(styled_doc, options)
}
/// ๆธฒๆ Markdown๏ผๆฐ API๏ผๆฏๆๆดๅคๆงๅถ๏ผ
pub fn render_html(html: &str, options: &HtmlRenderOptions) -> Result<Document, Error> {
let html_doc = HtmlParser::parse(html)?;
let styled_doc = CssEngine::compute_styles(html_doc, &options.css)?;
Document::from_styled_html(styled_doc, &options.into())
}
```
#### 6.2.2 ็นๆงๅผๅ
ณ
```toml
# Cargo.toml
[features]
default = ["html-subset"]
html-subset = ["lightningcss"]
legacy-mode = [] # ไฟ็ๆงๆถๆ็จไบๅ้
```
#### 6.2.3 ๆธ่ฟๅผ่ฟ็งปๆญฅ้ชค
1. **้ถๆฎต 1**๏ผๆฐๆถๆไฝไธบ็ฌ็ซๆจกๅๅผๅ๏ผไธๅฝฑๅ็ฐๆไปฃ็
2. **้ถๆฎต 2**๏ผๆทปๅ `render_html` ๆฐ API๏ผๆง API ไฟๆไธๅ
3. **้ถๆฎต 3**๏ผๅ
้จๅฎ็ฐๅๆขๅฐๆฐๆถๆ๏ผไฟๆ API ๅ
ผๅฎน
4. **้ถๆฎต 4**๏ผๅบๅผๆง API๏ผๆไพ่ฟ็งปๆๅ
5. **้ถๆฎต 5**๏ผๅฎๅ
จ็งป้คๆงๆถๆไปฃ็
### 6.3 ้ฃ้ฉ่ฏไผฐ
| Lightning CSS ไพ่ต้ฎ้ข | ไฝ | ้ซ | ๅๅคๅค็จ CSS ่งฃๆๆนๆก |
| ๆง่ฝไธ้ | ไธญ | ไธญ | ๆฉๆๆง่ฝๅบๅๆต่ฏ๏ผๅฟ
่ฆๆถไผๅ |
| ๅ
ผๅฎนๆง้ฎ้ข | ไธญ | ้ซ | ๅฎๆด็ๅๅฝๆต่ฏๅฅไปถ |
| ๅผๅๅปถๆ | ไธญ | ไธญ | ๅ้ถๆฎตไบคไป๏ผไผๅ
ๆ ธๅฟๅ่ฝ |
### 6.4 ๆๅๆ ๅ
1. **ๅ่ฝๅฎๆดๆง**
- ๆฏๆๆๆๅฎไน็ HTML ๅญ้ๆ ็ญพ
- ๆฏๆๆๆๅฎไน็ CSS ๅฑๆง
- 100% ๅๅๅ
ผๅฎน็ฐๆ Markdown ๅ่ฝ
2. **ๆง่ฝๆๆ **
- ๆธฒๆ้ๅบฆไธไฝไบๅฝๅ็ๆฌ
- ๅ
ๅญไฝฟ็จๅข้ฟไธ่ถ
่ฟ 20%
3. **ไปฃ็ ่ดจ้**
- ๆต่ฏ่ฆ็็ > 80%
- ๆ Clippy ่ญฆๅ
- ๆๆกฃๅฎๆด
---
## ้ๅฝ
### A. ๅ่่ตๆบ
- [Lightning CSS ๆๆกฃ](https://lightningcss.dev/)
- [CommonMark ่ง่](https://spec.commonmark.org/)
- [CSS Selectors Level 4](https://www.w3.org/TR/selectors-4/)
- [CSS Box Model](https://www.w3.org/TR/CSS22/box.html)
### B. ็ธๅ
ณ crate
- `markdown` - Markdown ่งฃๆ
- `lightningcss` - CSS ่งฃๆๅๅค็
- `roxmltree` - XML/HTML ่งฃๆ๏ผๅฏ้๏ผ
- `selectors` - CSS ้ๆฉๅจๅน้
๏ผๅฆ Lightning CSS ไธๆปก่ถณ้ๆฑ๏ผ
### C. ็คบไพ Markdown + HTML
```markdown
# ๆๆกฃๆ ้ข
<div style="text-align: center; color: #666;">
## ๅฑ
ไธญๅฏๆ ้ข
่ฟๆฏๅฑ
ไธญๆพ็คบ็ๅ
ๅฎนใ
</div>
ๆฎ้ๆฎต่ฝใ<span style="color: red;">็บข่ฒๆๅญ</span> ๅ **ๅ ็ฒๆๅญ**ใ
<br/>
ๅผบๅถๆข่กๅ็ๅ
ๅฎนใ
<div class="warning-box" style="background: #fff3cd; padding: 10px; border-left: 4px solid #ffc107;">
โ ๏ธ ่ญฆๅ๏ผ่ฟๆฏไธไธช่ญฆๅๆก
</div>
```
---
*ๆๆกฃ็ๆฌ๏ผ1.0*
*ๆๅๆดๆฐ๏ผ2026-06-01*
โ โ