2014-05-07 13:06:24 +08:00
|
|
|
|
package html2image
|
|
|
|
|
|
2014-05-07 13:57:14 +08:00
|
|
|
|
/*
|
2014-05-07 13:06:24 +08:00
|
|
|
|
import (
|
2014-05-07 13:47:12 +08:00
|
|
|
|
"github.com/leanote/leanote/app/lea"
|
|
|
|
|
"github.com/leanote/leanote/app/lea/netutil"
|
2014-05-07 13:06:24 +08:00
|
|
|
|
"bufio"
|
|
|
|
|
"code.google.com/p/draw2d/draw2d"
|
|
|
|
|
// "fmt"
|
|
|
|
|
"image"
|
|
|
|
|
"image/color"
|
|
|
|
|
"image/png"
|
|
|
|
|
"image/gif"
|
|
|
|
|
"image/jpeg"
|
|
|
|
|
"os"
|
|
|
|
|
// "strings"
|
|
|
|
|
// "time"
|
|
|
|
|
"github.com/revel/revel"
|
|
|
|
|
"code.google.com/p/go.net/html"
|
|
|
|
|
"strings"
|
|
|
|
|
"strconv"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Html2Image struct {
|
|
|
|
|
image *image.RGBA
|
|
|
|
|
gc *draw2d.ImageGraphicContext
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 试探
|
|
|
|
|
gc2 *draw2d.ImageGraphicContext
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
width float64 // 图片宽度
|
|
|
|
|
height float64
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
painWidth float64 // 画布宽度
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
startX float64
|
|
|
|
|
x float64
|
|
|
|
|
y float64
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
isFirstP bool // 是否是第一个段落?
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 换行和段落的高度
|
|
|
|
|
brY float64
|
|
|
|
|
pY float64
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 字体
|
|
|
|
|
normalFontFamily draw2d.FontData
|
|
|
|
|
boldFontFamily draw2d.FontData
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// preTag 之前的标签
|
|
|
|
|
preTag *html.Node
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewHtml2Image() *Html2Image {
|
|
|
|
|
h := &Html2Image{}
|
|
|
|
|
h.width = 440
|
|
|
|
|
h.height = 10000 // 最开始设为很大, 不然加不了图片, 会影响速度
|
|
|
|
|
i, gc := h.InitGc(h.width, h.height)
|
|
|
|
|
h.gc = gc;
|
|
|
|
|
h.image = i
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 试探
|
|
|
|
|
_, h.gc2 = h.InitGc(h.width, 100)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
|
|
|
|
h.startX = 10
|
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 最初位置
|
|
|
|
|
h.x = h.startX
|
|
|
|
|
h.y = 80
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
h.isFirstP = true
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
h.normalFontFamily = draw2d.FontData{"xihei", 4, draw2d.FontStyleNormal};
|
|
|
|
|
h.boldFontFamily = draw2d.FontData{"heiti", 5, draw2d.FontStyleNormal};
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
h.SetNormalFont()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
return h
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (this *Html2Image) InitGc(w, h float64) (* image.RGBA, *draw2d.ImageGraphicContext) {
|
|
|
|
|
i := image.NewRGBA(image.Rect(0, 0, int(w), int(h)))
|
|
|
|
|
gc := draw2d.NewGraphicContext(i)
|
|
|
|
|
|
|
|
|
|
gc.SetStrokeColor(image.Black)
|
|
|
|
|
gc.SetFillColor(image.White)
|
|
|
|
|
// fill the background
|
|
|
|
|
// gc.Clear()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
draw2d.SetFontFolder(revel.BasePath + "/public/fonts/weibo")
|
|
|
|
|
draw2d.Rect(gc, 0, 0, w, h) // 设置背景
|
|
|
|
|
gc.FillStroke()
|
|
|
|
|
gc.SetFillColor(image.Black)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 这个很耗时
|
|
|
|
|
// gc.Translate(0, 0)
|
|
|
|
|
return i, gc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (this *Html2Image) SaveToPngFile(filePath string) bool {
|
|
|
|
|
// m := this.image;
|
|
|
|
|
m := this.image.SubImage(image.Rect(0, 0, int(this.width), int(this.y + 20)))
|
|
|
|
|
// 需要截断之
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
f, err := os.Create(filePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
b := bufio.NewWriter(f)
|
|
|
|
|
err = png.Encode(b, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
err = b.Flush()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 字体大小
|
|
|
|
|
|
|
|
|
|
func (this *Html2Image) SetSmallFont() {
|
|
|
|
|
this.gc.SetFontData(this.normalFontFamily)
|
|
|
|
|
this.gc2.SetFontData(this.normalFontFamily)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.gc.SetFillColor(color.NRGBA{60, 60, 60, 255})
|
|
|
|
|
this.gc.SetFontSize(12)
|
|
|
|
|
this.gc2.SetFontSize(12)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.brY = 16
|
|
|
|
|
this.pY = 30
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.painWidth = this.width - 10
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (this *Html2Image) SetNormalFont() {
|
|
|
|
|
this.gc.SetFillColor(image.Black)
|
|
|
|
|
this.gc.SetFontData(this.normalFontFamily)
|
|
|
|
|
this.gc2.SetFontData(this.normalFontFamily)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.gc.SetFontSize(14)
|
|
|
|
|
this.gc2.SetFontSize(14)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.brY = 20
|
|
|
|
|
this.pY = 30
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.painWidth = this.width - 10
|
|
|
|
|
}
|
|
|
|
|
func (this *Html2Image) SetAColor() {
|
|
|
|
|
this.gc.SetFillColor(color.NRGBA{66, 139, 202, 255})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 标题
|
|
|
|
|
func (this *Html2Image) SetTitleFont() {
|
|
|
|
|
this.gc.SetFillColor(image.Black)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.gc.SetFontData(this.boldFontFamily)
|
|
|
|
|
this.gc2.SetFontData(this.boldFontFamily)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.gc.SetFontSize(24)
|
|
|
|
|
this.gc2.SetFontSize(24)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.brY = 30
|
|
|
|
|
this.pY = 60
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.painWidth = this.width - 100
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// h1, h2...
|
|
|
|
|
// h1
|
|
|
|
|
func (this *Html2Image) SetHeadFont(h string) {
|
|
|
|
|
this.gc.SetFillColor(image.Black)
|
|
|
|
|
|
|
|
|
|
this.gc.SetFontData(this.boldFontFamily)
|
|
|
|
|
this.gc2.SetFontData(this.boldFontFamily)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.painWidth = this.width - 50
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
if h == "h1" {
|
|
|
|
|
this.gc.SetFontSize(20)
|
|
|
|
|
this.gc2.SetFontSize(20)
|
|
|
|
|
this.brY = 30
|
|
|
|
|
this.pY = 60
|
|
|
|
|
} else if h == "h2" {
|
|
|
|
|
this.gc.SetFontSize(16)
|
|
|
|
|
this.gc2.SetFontSize(16)
|
|
|
|
|
this.brY = 25
|
|
|
|
|
this.pY = 50
|
|
|
|
|
} else if h == "h3" || h == "h4" {
|
|
|
|
|
this.gc.SetFontSize(14)
|
|
|
|
|
this.gc2.SetFontSize(14)
|
|
|
|
|
this.brY = 20
|
|
|
|
|
this.pY = 40
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 新的段落
|
|
|
|
|
func (this *Html2Image) NewP() {
|
|
|
|
|
// if !this.isFirstP {
|
|
|
|
|
this.x = this.startX;
|
|
|
|
|
this.y += this.pY;
|
|
|
|
|
// } else {
|
|
|
|
|
// this.isFirstP = false
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 新一行
|
|
|
|
|
func (this *Html2Image) NewBr() {
|
|
|
|
|
this.x = this.startX;
|
|
|
|
|
this.y += this.brY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 是否超出了
|
|
|
|
|
// 1 个汉字 17.875
|
|
|
|
|
// 1 个字母最多 17.15625
|
|
|
|
|
func (this *Html2Image) IsOver(r []rune) bool {
|
|
|
|
|
// fmt.Println(string(r))
|
|
|
|
|
// 还有多宽
|
|
|
|
|
// 就算全拿汉字来说
|
|
|
|
|
// 这里是优化,速度有提升
|
2014-05-07 13:57:14 +08:00
|
|
|
|
// if this.painWidth - this.x > 17.875 * float64(len(r)) {
|
|
|
|
|
// return false
|
|
|
|
|
// }
|
2014-05-07 13:06:24 +08:00
|
|
|
|
|
|
|
|
|
// println(text)
|
|
|
|
|
width2 := this.gc2.FillStringAt(string(r), 0, 0)
|
|
|
|
|
// 以下的方法可以极大节约时间
|
|
|
|
|
// a, b, c, d := this.gc2.GetStringBounds(string(r))
|
|
|
|
|
// width2 := c - a + 2
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// fmt.Println(width2)
|
|
|
|
|
// fmt.Println(c - a)
|
|
|
|
|
|
|
|
|
|
// 小于, 那么需要->大 到第一个不合适的位置
|
|
|
|
|
if width2 + this.x <= this.painWidth {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// 是否是字母
|
|
|
|
|
func (this *Html2Image) isAlpha(word rune) bool {
|
|
|
|
|
if (word >= 'a' && word <= 'z') || (word >= 'A' && word <= 'Z') {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 是否是标点, 是标点就包含进来
|
|
|
|
|
func (this *Html2Image) includePunctuation(r []rune, end int) int {
|
|
|
|
|
if len(r) == end {
|
|
|
|
|
return end
|
|
|
|
|
}
|
|
|
|
|
c := r[end]
|
|
|
|
|
if c == ',' || c == '.' || c == '?' || c == ':' || c == ';' || c == '!' || c == ',' || c == '。' || c == '?' || c == ':' || c == ';' || c == '!' {
|
|
|
|
|
return end + 1
|
|
|
|
|
}
|
|
|
|
|
return end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 插入文本
|
|
|
|
|
// 要判断是否太长了, 太长了就截断
|
|
|
|
|
func (this *Html2Image) InsertText(text string, needTest bool, prefix string) {
|
|
|
|
|
if needTest && this.painWidth - this.x < 2 {
|
|
|
|
|
// 另起一行
|
|
|
|
|
this.NewBr()
|
|
|
|
|
this.InsertText(text, true, prefix)
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
r := []rune(text)
|
|
|
|
|
// 试探吧, 可能需要截取
|
|
|
|
|
if !needTest || !this.IsOver(r) {
|
|
|
|
|
// 不用截
|
|
|
|
|
// 可能有\n
|
|
|
|
|
width := this.gc.FillStringAt(prefix + text, this.x, this.y)
|
|
|
|
|
this.x += width + 1
|
|
|
|
|
} else {
|
|
|
|
|
// 刚开始加10个字, 之后一个一个来
|
|
|
|
|
// 一个汉字, 或一个单词加
|
|
|
|
|
wordStart := false
|
|
|
|
|
wordStartPos := 0
|
|
|
|
|
maxRI := len(r) - 1
|
|
|
|
|
for i, word := range r { // i 是0, 1, 2, 3...
|
|
|
|
|
// i是byte的位置, 一个汉字占3位
|
|
|
|
|
// 是字母
|
|
|
|
|
if this.isAlpha(word) {
|
|
|
|
|
if !wordStart {
|
|
|
|
|
wordStart = true
|
|
|
|
|
wordStartPos = i
|
|
|
|
|
}
|
|
|
|
|
} else if(word == '\n' || word == '\r') {
|
|
|
|
|
// 是否是\n
|
|
|
|
|
i = this.includePunctuation(r, i)
|
|
|
|
|
this.InsertText(string(r[0:i]), false, prefix)
|
|
|
|
|
this.NewBr()
|
|
|
|
|
// 之后的
|
|
|
|
|
if maxRI != i {
|
|
|
|
|
this.InsertText(string(r[i+1:]), true, prefix)
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
// 单词没结束不计算
|
|
|
|
|
wordStart = false
|
|
|
|
|
if i > 0 {
|
|
|
|
|
// 这里计算是否超出了, 包含自己在内
|
|
|
|
|
if this.IsOver(r[0:i+1]) {
|
|
|
|
|
// 那么...回退前一个
|
|
|
|
|
end := i
|
|
|
|
|
// 如果上一个是单词, 那么整个单词都不要, 取单词开头
|
|
|
|
|
if this.isAlpha(r[i-1]) {
|
|
|
|
|
end = wordStartPos
|
|
|
|
|
// 这一行全是这个单词, 不太现实, 但有可能, 只能截断了
|
|
|
|
|
if end == 0 {
|
|
|
|
|
end = i
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 这一段写上
|
|
|
|
|
// println("------>" + string(r[0:end]))
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 这里, 判断后面一个是否是标点符号
|
|
|
|
|
end = this.includePunctuation(r, end)
|
|
|
|
|
this.InsertText(string(r[0:end]), false, prefix)
|
|
|
|
|
this.NewBr()
|
|
|
|
|
// 之后的
|
|
|
|
|
this.InsertText(string(r[end:]), true, prefix)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
// 没超出, 不用计算, 但出要看是否是结尾了
|
|
|
|
|
// 怎么可能会出现这种情况呢?, 第一步就试了
|
2014-05-07 13:57:14 +08:00
|
|
|
|
// if i+1 == len(text) {
|
|
|
|
|
// println("不可能")
|
|
|
|
|
// }
|
2014-05-07 13:06:24 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // for
|
|
|
|
|
// !!
|
|
|
|
|
// 如果是 go get code.google.com/p/graphics-go/graphics 最后是字母, 怎么办?
|
|
|
|
|
if wordStart {
|
|
|
|
|
// 这里计算是否超出了, 包含自己在内
|
|
|
|
|
end := maxRI + 1
|
|
|
|
|
// 如果上一个是单词, 那么整个单词都不要, 取单词开头
|
|
|
|
|
if this.isAlpha(r[maxRI]) {
|
|
|
|
|
end = wordStartPos
|
|
|
|
|
// 这一行全是这个单词, 不太现实, 但有可能, 只能截断了
|
|
|
|
|
if end == 0 {
|
|
|
|
|
end = maxRI + 1
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 这一段写上
|
|
|
|
|
// println("-e----->" + string(r[0:end]))
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 这里, 判断后面一个是否是标点符号
|
|
|
|
|
end = this.includePunctuation(r, end)
|
|
|
|
|
this.InsertText(string(r[0:end]), false, prefix)
|
|
|
|
|
this.NewBr()
|
|
|
|
|
// 之后的
|
|
|
|
|
this.InsertText(string(r[end:]), true, prefix)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置页脚, url文章链接
|
|
|
|
|
func (this *Html2Image) SetBottom(username, url string) {
|
|
|
|
|
// 画一条线
|
|
|
|
|
this.NewBr()
|
|
|
|
|
this.gc.MoveTo(this.x, this.y)
|
|
|
|
|
this.gc.LineTo(this.painWidth, this.y)
|
|
|
|
|
this.gc.SetStrokeColor(color.NRGBA{200, 0, 0, 255})
|
|
|
|
|
this.gc.SetLineWidth(2)
|
|
|
|
|
this.gc.FillStroke()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.SetSmallFont()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 左侧写字
|
|
|
|
|
this.NewP()
|
|
|
|
|
this.InsertText("本文来自 " + username + " 的leanote笔记", true, " ")
|
|
|
|
|
this.NewBr()
|
|
|
|
|
this.InsertText("个人博客: ", false, " ")
|
|
|
|
|
siteUrl, _ := revel.Config.String("site.url")
|
|
|
|
|
if siteUrl == "" {
|
|
|
|
|
siteUrl = "http://leanote.com"
|
|
|
|
|
}
|
|
|
|
|
this.InsertA(siteUrl + "/blog/" + username, false)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.setLogo()
|
2014-05-07 13:57:14 +08:00
|
|
|
|
// this.painWidth = this.width - 100
|
|
|
|
|
// this.NewP()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
// this.InsertText("leanote, 不一样的笔记.", false, " ")
|
2014-05-07 13:57:14 +08:00
|
|
|
|
// this.NewBr()
|
|
|
|
|
// this.InsertText("在这里你可以管理自己的知识", false, " ")
|
|
|
|
|
// this.NewBr()
|
|
|
|
|
// this.InsertText("将知识分享给好友, 与好友一起协作知识", false, " ")
|
|
|
|
|
// this.NewBr()
|
|
|
|
|
// this.InsertText("并且还可以将笔记设为博客公开", false, " ")
|
|
|
|
|
// this.InsertText(". 赶紧加入吧! leanote.com", false, "")
|
2015-11-13 17:58:41 +08:00
|
|
|
|
//
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// Logo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (this *Html2Image) setImage(path string, x, y float64) {
|
|
|
|
|
f1, err := os.Open(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return;
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
var m1 image.Image
|
|
|
|
|
_, ext := lea.SplitFilename(path)
|
|
|
|
|
if ext == ".png" {
|
|
|
|
|
m1, err = png.Decode(f1)
|
|
|
|
|
} else if ext == ".gif" {
|
|
|
|
|
m1, err = gif.Decode(f1)
|
|
|
|
|
} else if ext == ".jpg" {
|
|
|
|
|
m1, err = jpeg.Decode(f1)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
panic(err)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.gc.Translate(x, y)
|
|
|
|
|
this.gc.DrawImage(m1)
|
|
|
|
|
this.gc.Translate(-x, -y)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 画leanote logo
|
|
|
|
|
func (this *Html2Image) setLogo() {
|
|
|
|
|
// 右上角的logo
|
|
|
|
|
path := revel.BasePath + "/public/images/leanote/logo-20-a-6.png"
|
|
|
|
|
println(path)
|
|
|
|
|
this.setImage(path, 320, 10)
|
|
|
|
|
|
|
|
|
|
// 右下角设置Logo
|
2014-05-07 13:57:14 +08:00
|
|
|
|
// path = revel.BasePath + "/public/images/leanote/logo-60-a-6.png"
|
|
|
|
|
// this.setImage(path, 320, this.y - 75)
|
2014-05-07 13:06:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 插入链接
|
|
|
|
|
func (this *Html2Image) InsertA(text string, isNormal bool) {
|
|
|
|
|
if text == "" {
|
|
|
|
|
return
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.SetAColor()
|
|
|
|
|
this.InsertText(text, true, "")
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 还原
|
|
|
|
|
if isNormal {
|
|
|
|
|
this.SetNormalFont()
|
|
|
|
|
} else {
|
|
|
|
|
this.SetSmallFont()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 文章标题
|
|
|
|
|
func (this *Html2Image) InsertTitle(title string) {
|
|
|
|
|
oldX := this.x
|
|
|
|
|
oldY := this.y - 35
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 插入之
|
|
|
|
|
this.SetTitleFont()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.InsertText(title, true, " ")
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 还原字体大小
|
|
|
|
|
this.SetNormalFont()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.NewBr()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.gc.MoveTo(oldX, oldY)
|
|
|
|
|
this.gc.LineTo(this.x, this.y - 10)
|
|
|
|
|
this.gc.SetStrokeColor(color.NRGBA{200, 0, 0, 255})
|
|
|
|
|
this.gc.SetLineWidth(5)
|
|
|
|
|
this.gc.FillStroke()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 插入h1, h2, ... h4
|
|
|
|
|
func (this *Html2Image) InsertHead(n *html.Node) {
|
|
|
|
|
this.SetHeadFont(n.Data); // h1, h2...
|
|
|
|
|
this.NewP()
|
|
|
|
|
// 把标题内容全都拿出
|
|
|
|
|
var text = ""
|
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
|
|
|
if c.Type == html.ElementNode && (c.Data == "p" || c.Data == "br"){
|
|
|
|
|
text += " "
|
|
|
|
|
} else {
|
|
|
|
|
text += strings.TrimRight(c.Data, "\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.InsertText(text, true, "")
|
|
|
|
|
this.SetNormalFont()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 插入代码
|
|
|
|
|
func (this *Html2Image) InsertCode(n *html.Node) {
|
|
|
|
|
this.NewP()
|
|
|
|
|
oldX := this.x
|
|
|
|
|
oldY := this.y - 20
|
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
|
|
|
if c.Type == html.ElementNode && (c.Data == "p" || c.Data == "br"){
|
|
|
|
|
this.NewBr()
|
|
|
|
|
} else {
|
|
|
|
|
this.InsertText(strings.TrimRight(c.Data, "\n"), true, " ")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.NewBr()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.gc.MoveTo(oldX, oldY)
|
|
|
|
|
this.gc.LineTo(this.x, this.y - 20)
|
|
|
|
|
this.gc.SetStrokeColor(color.NRGBA{0, 200, 0, 255})
|
|
|
|
|
this.gc.SetLineWidth(2)
|
|
|
|
|
this.gc.FillStroke()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 插入图片
|
2015-11-13 17:58:41 +08:00
|
|
|
|
// 这个path应该是url,
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// http://abc.com/a.gif 需要先下载
|
|
|
|
|
// 或 /upload/a.gif
|
|
|
|
|
func (this *Html2Image) InsertImage(path string, needTrans bool, width uint) {
|
|
|
|
|
if path == "" {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 是url, 那么取网络图片之
|
|
|
|
|
var ok bool
|
|
|
|
|
if strings.HasPrefix(path, "http") || strings.HasPrefix(path, "//") {
|
|
|
|
|
path, ok = netutil.WriteUrl(path, "/tmp")
|
|
|
|
|
if !ok || path == ""{
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
path = revel.BasePath + "/public/" + path
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 需要转换, logo不需要转换
|
|
|
|
|
if(needTrans) {
|
|
|
|
|
painWidth := uint(this.painWidth - 10)
|
|
|
|
|
if width > 0 && painWidth > width {
|
|
|
|
|
painWidth = width
|
|
|
|
|
}
|
|
|
|
|
ok, path = lea.TransToGif(path, painWidth, false)
|
|
|
|
|
if !ok || path == "" {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f1, err := os.Open(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return;
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
var m1 image.Image
|
|
|
|
|
_, ext := lea.SplitFilename(path)
|
|
|
|
|
if ext == ".png" {
|
|
|
|
|
m1, err = png.Decode(f1)
|
|
|
|
|
} else {
|
|
|
|
|
m1, err = gif.Decode(f1)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
panic(err)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 如果之前是p, 那么不要有<br>
|
|
|
|
|
if this.preTag.Data != "p" {
|
|
|
|
|
this.NewBr()
|
|
|
|
|
}
|
|
|
|
|
this.gc.Translate(this.x, this.y)
|
|
|
|
|
this.gc.DrawImage(m1)
|
|
|
|
|
// 还原
|
|
|
|
|
this.gc.Translate(-this.x, -this.y) // 这个有用些
|
|
|
|
|
this.y += float64(m1.Bounds().Dy()) - 20
|
|
|
|
|
this.NewP()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
os.Remove(path)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 如果图片是文章第一个的话, 之后的需要p
|
|
|
|
|
this.isFirstP = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 内容主体
|
|
|
|
|
func (this *Html2Image) InsertBody(htmlStr string) (ok bool) {
|
|
|
|
|
reader := bufio.NewReader(strings.NewReader(htmlStr))
|
|
|
|
|
doc, err := html.Parse(reader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var f func(*html.Node, *html.Node, string)
|
|
|
|
|
f = func(n *html.Node, p *html.Node, prefix string) {
|
|
|
|
|
// if p != nil {
|
|
|
|
|
// fmt.Println("Parent Data: " + p.Data)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
if n.Type == html.ElementNode {
|
|
|
|
|
this.preTag = n
|
|
|
|
|
}
|
|
|
|
|
}()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 标签
|
|
|
|
|
if n.Type == html.ElementNode {
|
|
|
|
|
if n.Data == "p" {
|
|
|
|
|
this.NewP()
|
|
|
|
|
// 遍历之后的
|
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
|
|
|
f(c, n, prefix)
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 也是一个段落, 只是要缩进
|
|
|
|
|
if n.Data == "ul" || n.Data == "ol" {
|
|
|
|
|
this.NewP()
|
|
|
|
|
// 遍历之后的
|
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
|
|
|
f(c, n, "")
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if n.Data == "li" {
|
|
|
|
|
// 遍历之后的
|
|
|
|
|
// 是否需要前缀
|
|
|
|
|
needPrefix := true // 第一个肯定要
|
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
|
|
|
if needPrefix {
|
|
|
|
|
f(c, n, " ")
|
|
|
|
|
needPrefix = false
|
|
|
|
|
} else {
|
|
|
|
|
f(c, n, "")
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
if c.Type == html.ElementNode {
|
|
|
|
|
if c.Data == "br" || c.Data == "p" {
|
|
|
|
|
needPrefix = true
|
|
|
|
|
} else {
|
|
|
|
|
needPrefix = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 输完一行后再换行
|
|
|
|
|
this.NewBr()
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 标题
|
|
|
|
|
if n.Data == "h1" || n.Data == "h2" || n.Data == "h3" || n.Data == "h4" {
|
|
|
|
|
this.InsertHead(n)
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
if n.Data == "pre" {
|
|
|
|
|
// 把之后的全拿过来
|
|
|
|
|
this.InsertCode(n)
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 图片
|
|
|
|
|
// 得到src
|
|
|
|
|
if n.Data == "img" {
|
|
|
|
|
src := ""
|
|
|
|
|
width := 0
|
|
|
|
|
for _, attr := range n.Attr {
|
|
|
|
|
if attr.Key == "src" {
|
|
|
|
|
src = attr.Val
|
|
|
|
|
} else if attr.Key == "width" {
|
|
|
|
|
width, _ = strconv.Atoi(attr.Val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if src != "" {
|
|
|
|
|
this.InsertImage(src, true, uint(width))
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 链接
|
|
|
|
|
// 如果链接里只有文本, 那么单独处理, 如果还有其它的, 不作链接处理
|
|
|
|
|
if n.Data == "a" {
|
|
|
|
|
if n.FirstChild == n.LastChild {
|
|
|
|
|
this.InsertA(n.FirstChild.Data, true)
|
|
|
|
|
return;
|
2015-11-13 17:58:41 +08:00
|
|
|
|
}
|
2014-05-07 13:06:24 +08:00
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 空行
|
2014-05-07 13:57:14 +08:00
|
|
|
|
if n.Data == "br" { // || n.Data == "div"
|
2014-05-07 13:06:24 +08:00
|
|
|
|
this.NewBr()
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 是文本, 输出之
|
|
|
|
|
if n.Type == html.TextNode {
|
|
|
|
|
data := strings.TrimSpace(n.Data);
|
|
|
|
|
// <p>xx<br/>xxx</p> 这些空白也是TextNode <p>
|
|
|
|
|
if data != "" {
|
|
|
|
|
this.InsertText(prefix + data, true, "")
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 其余的
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
|
|
|
f(c, n, prefix)
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
f(doc, nil, "")
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 主函数
|
|
|
|
|
func ToImage(uid, username, noteId, title, htmlStr, toPath string) (ok bool) {
|
|
|
|
|
h := NewHtml2Image()
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 标题
|
|
|
|
|
h.InsertTitle(title)
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 主体
|
|
|
|
|
ok = h.InsertBody(htmlStr)
|
|
|
|
|
if(!ok) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 页眉与页脚
|
|
|
|
|
h.SetBottom(username, "")
|
2015-11-13 17:58:41 +08:00
|
|
|
|
|
2014-05-07 13:06:24 +08:00
|
|
|
|
// 保存成png图片
|
|
|
|
|
ok = h.SaveToPngFile(toPath)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFillString() {
|
|
|
|
|
h := NewHtml2Image()
|
|
|
|
|
str := `一个合格的 Techspace 需要有足够专业的器材、场地和资源,你可以和你的团队在里面进行激光切割、快速贴片甚至加工木材等操作,在相对独立的空间内又能同周围的同道友人互相激发切磋。国内现有的 Techspace 没几家,不久前我去深圳特地拜访了当地的 Techspace,很喜欢那里的氛围,希望国内其他地方也能有更多这类空间供创客发挥。
|
|
|
|
|
假如你有一个比较成型的想法,想在硬件领域做点事情,核心团队也基本组好,硬件软件交互基本都有专人了`
|
|
|
|
|
// h.IsOver("W")
|
|
|
|
|
h.InsertText("go get code.google.com/p/graphics\n-go/graphics", true, "")
|
|
|
|
|
// h.InsertText("usr/bin/install: 无法创建一般文件'/usr/local/jpeg6/include/jconfig.", true)
|
|
|
|
|
// h.InsertImage("/Users/life/Desktop/share.png")
|
|
|
|
|
// h.NewP()
|
|
|
|
|
h.InsertText(str, true, "")
|
|
|
|
|
// h.InsertImage("/Users/life/Desktop/share.png")
|
|
|
|
|
h.SaveToPngFile("/Users/life/Desktop/TestPath3.png")
|
|
|
|
|
}
|
2014-05-07 13:57:14 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
func ToImage(uid, username, noteId, title, htmlStr, toPath string) (ok bool) {
|
|
|
|
|
return false
|
2015-11-13 17:58:41 +08:00
|
|
|
|
}
|