Awesome
Layout Engine for HTML(css support)
A Layout Engine for HTML with limited CSS support, implemented in pure Go. This project was written with the help of a large language model. This layout engine allows you to set the page width and then automatically computes the height and width of children elements, taking into account the margin, padding, and border to achieve accurate layouts. Additionally, it provides a split page method that helps you convert HTML to PDF with line break support.
Installation
To install and set up the project, follow these steps:
- Clone the repository:
git clone https://github.com/html2any/layout.git
- Navigate to the project directory:
cd layout/example
- Install the required dependencies:
go build && ./example pdf complicated.html
Usage
Follow these instructions to use the project:
- Load Fonts for compute and render
- Define a text line break method
- Init render for output
- Load html that need to convert
- Set Width and Layout each element,You will get then result height.
- RenderTo a file
Demo Result
Write HTML File
<div style="font-size:12px;margin:5px 50px; height:700px; top:70px;background-color:#f5f4d8">
<div id="htitle">
<img src="assets/imgs/google.png" style="width:92px;height:30px;margin:10px 0" />
<div>Delivery Note (Supplier)</div>
<img src="assets/imgs/google.png" style="width:55px;height:50px;margin:5px 0;" />
</div>
<div class="dline"></div>
<div class="title">Delivery Instructions:</div>
<div class="line">1. Please carefully check.</div>
<div class="line">2. Both parties should complete the signature confirmation.</div>
<div class="line">3. If any exceptions occur, please contact "xx@yy.com".</div>
<div class="title">Project Information:</div>
<table id="info">
<tr><td>Project Name: Mobile Sand Project</td></tr>
<tr><td>PR: PR23061100002411</td><td>PO: PO230721000044</td></tr>
<tr><td>Recipient: Edible Oil Natural Gas Co., Ltd.</td></tr>
</table>
<div class="title">Delivery List:</div>
<table id="plist">
<tr style="background-color: #f2f2f2;"><td>Number</td><td>Product ID</td><td>Product Name</td><td>Quantity</td><td>Model</td></tr>
<tr><td>001</td><td>S2305009830</td><td>Card Issuer With Bluetooth</<td><td>1</td><td>M-SNM10-C</td></tr>
</table>
</div>
Write Css.go. See example/css.go
var cssMap map[string]string = map[string]string{
"tr": `flex-direction: row;`,
"#htitle": `margin:0px;font-size:26px;text-align:center;display:flex;flex-direction:row;`,
"#htitle>div": `flex:1;line-height:44px;font-weight: bold;color:#ff0000;`,
".dline": `border-top:0.5px solid #f00;border-bottom:0.5px solid #f00;padding-top:1px;margin:5px 0px`,
...
}
Convert Demo
func main() {
format := os.Args[1]
// loadFonts and CacheAll
fontcache := loadFonts()
sfnt := fonts.NewSFNT(fontcache)
if _, err := sfnt.CacheAll(); err != nil {
panic(err)
}
// init render
render := initRender(format, fontcache)
// load html
root := parseHTML2Block()
if format == "img" {
_, err := layout.CacuHeight(root, layout.A4_Width, "NotoSansSC", NewLayout(sfnt))
if err != nil {
panic(err)
}
render.(*ImageRender).NewCanvas(root.Width, root.Height+root.Top)
layout.RenderTo(root, render, 0, 0)
out := render.GetData()
os.WriteFile("out.png", out, 0644)
} else if format == "pdf" {
if _, pages, err := layout.CacuPages(root, layout.A4_Width, "NotoSansSC", NewLayout(sfnt)); err != nil {
panic(err)
} else {
pdf_render := render.(*PdfRender)
for _, page := range pages {
pdf_render.pdf.AddPage()
layout.RenderTo(page, pdf_render, 0, 0)
}
out := render.GetData()
os.WriteFile("out.pdf", out, 0644)
}
}
}
Css support list
top
flex
flex-direction
overflow
margin|margin-top|margin-right|margin-bottom|margin-left
padding|padding-top|padding-right|padding-bottom|padding-left
text-align|vertical-align
background-color
font-family|font-style|font-size|font-color
height
line-height
width
border|border-top|border-right|border-bottom|border-left
border-[top|right|bottom|left]-color
border-[top|right|bottom|left]-style
Tag support
- Any tag name
- Attributes: class,src,style,id
More Usage
- TextSplit is an interface.
- Render is an interface.
- You can define your own impliment.
Performance
- Html parser is faster than
json.Unmarshal
parser - Recursive calculation is almost the same as stack. I chose to use recursion, which is better understood.
- The calculation of text lines will take some time.
- Rendering to pdf or image is fast enough
- The most expensive is encoding to pdf or png
Contributing
Contributions to the project are welcome! If you would like to contribute, please follow these guidelines:
- Fork the repository.
- Create a new branch:
git checkout -b feature/your-feature
. - Make your changes and commit them:
git commit -m 'Add some feature'
. - Push to the branch:
git push origin feature/your-feature
. - Open a pull request.
Please ensure that your code adheres to the project's coding style and includes appropriate tests.
License
This project is licensed under the MIT License. See the LICENSE file for more information.