mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
feat(强化兼容): 改进字体渲染
This commit is contained in:
+390
-107
@@ -21,6 +21,7 @@ import (
|
||||
"image/color"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -34,13 +35,15 @@ import (
|
||||
|
||||
// Renderer 渲染器实现
|
||||
type Renderer struct {
|
||||
Reader *Reader
|
||||
DPI float64
|
||||
fontFamily *canvas.FontFamily
|
||||
DrawParams map[string]*DrawParam
|
||||
FontMap map[string]*canvas.FontFamily
|
||||
fontDirs []string
|
||||
fontFS []fs.FS
|
||||
Reader *Reader
|
||||
DPI float64
|
||||
fontFamily *canvas.FontFamily
|
||||
DrawParams map[string]*DrawParam
|
||||
CompositeGraphicUnits map[string]*CompositeGraphicUnit
|
||||
FontMap map[string]*canvas.FontFamily
|
||||
FontGIDMap map[string]map[uint16]rune
|
||||
fontDirs []string
|
||||
fontFS []fs.FS
|
||||
}
|
||||
|
||||
// RendererOption 渲染器配置选项
|
||||
@@ -103,7 +106,7 @@ func (r *Renderer) RenderPageToContext(ctx *canvas.Context, page *PageContent) e
|
||||
}
|
||||
if page.Content.Layer != nil {
|
||||
for _, layer := range page.Content.Layer {
|
||||
r.renderLayer(ctx, layer, pageH, nil, nil, 0)
|
||||
r.renderLayer(ctx, layer, pageH, nil, nil, 0, nil)
|
||||
}
|
||||
}
|
||||
if len(page.Template) > 0 && r.Reader.doc != nil {
|
||||
@@ -237,36 +240,81 @@ func (r *Renderer) renderTemplate(ctx *canvas.Context, templateID string, pageH
|
||||
}
|
||||
if tplContent.Content.Layer != nil {
|
||||
for _, layer := range tplContent.Content.Layer {
|
||||
r.renderLayer(ctx, layer, pageH, nil, nil, 0)
|
||||
r.renderLayer(ctx, layer, pageH, nil, nil, 0, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// renderLayer 渲染图层
|
||||
// 入参: ctx 画布上下文, layer 图层对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽
|
||||
func (r *Renderer) renderLayer(ctx *canvas.Context, layer Layer, pageH float64, defaultFill color.Color, defaultStroke color.Color, defaultLW float64) {
|
||||
// 入参: ctx 画布上下文, layer 图层对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM
|
||||
func (r *Renderer) renderLayer(ctx *canvas.Context, layer Layer, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) {
|
||||
if layer.DrawParam != "" {
|
||||
if dp := r.getDrawParam(layer.DrawParam, nil); dp != nil {
|
||||
if dp.LineWidth > 0 {
|
||||
defaultLW = dp.LineWidth
|
||||
}
|
||||
if dp.FillColor != nil {
|
||||
defaultFill = parseColor(dp.FillColor.Value)
|
||||
defaultFill = parseColorWithAlpha(dp.FillColor.Value, dp.FillColor.Alpha)
|
||||
}
|
||||
if dp.StrokeColor != nil {
|
||||
defaultStroke = parseColor(dp.StrokeColor.Value)
|
||||
defaultStroke = parseColorWithAlpha(dp.StrokeColor.Value, dp.StrokeColor.Alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, imgObj := range layer.ImageObject {
|
||||
r.renderImage(ctx, imgObj, pageH)
|
||||
for _, textObj := range layer.TextObject {
|
||||
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, parentCTM)
|
||||
}
|
||||
for _, pathObj := range layer.PathObject {
|
||||
r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW)
|
||||
r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, parentCTM)
|
||||
}
|
||||
for _, textObj := range layer.TextObject {
|
||||
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke)
|
||||
for _, imgObj := range layer.ImageObject {
|
||||
r.renderImage(ctx, imgObj, pageH, parentCTM)
|
||||
}
|
||||
for _, cgu := range layer.CompositeGraphicUnit {
|
||||
r.renderCompositeGraphicUnit(ctx, cgu, pageH, defaultFill, defaultStroke, defaultLW, parentCTM)
|
||||
}
|
||||
}
|
||||
|
||||
// renderCompositeGraphicUnit 渲染复合图元
|
||||
// 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM
|
||||
func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu CompositeGraphicUnit, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) {
|
||||
ctx.Push()
|
||||
currentCTM := NewMatrix(cgu.CTM)
|
||||
if parentCTM != nil {
|
||||
currentCTM = parentCTM.Multiply(currentCTM)
|
||||
}
|
||||
r.applyClips(ctx, cgu.Clips, pageH, ¤tCTM)
|
||||
if cgu.ResourceID != "" {
|
||||
if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok {
|
||||
r.renderCompositeGraphicUnit(ctx, *ref, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM)
|
||||
}
|
||||
}
|
||||
if cgu.DrawParam != "" {
|
||||
if dp := r.getDrawParam(cgu.DrawParam, nil); dp != nil {
|
||||
if dp.LineWidth > 0 {
|
||||
defaultLW = dp.LineWidth
|
||||
}
|
||||
if dp.FillColor != nil {
|
||||
defaultFill = parseColorWithAlpha(dp.FillColor.Value, dp.FillColor.Alpha)
|
||||
}
|
||||
if dp.StrokeColor != nil {
|
||||
defaultStroke = parseColorWithAlpha(dp.StrokeColor.Value, dp.StrokeColor.Alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, imgObj := range cgu.ImageObject {
|
||||
r.renderImage(ctx, imgObj, pageH, ¤tCTM)
|
||||
}
|
||||
for _, pathObj := range cgu.PathObject {
|
||||
r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM)
|
||||
}
|
||||
for _, textObj := range cgu.TextObject {
|
||||
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, ¤tCTM)
|
||||
}
|
||||
for _, subCgu := range cgu.CompositeGraphicUnit {
|
||||
r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM)
|
||||
}
|
||||
ctx.Pop()
|
||||
}
|
||||
|
||||
// getDrawParam 获取绘制参数逻辑
|
||||
@@ -290,12 +338,37 @@ func (r *Renderer) getDrawParam(id string, visited map[string]bool) *DrawParam {
|
||||
if dp.LineWidth > 0 {
|
||||
merged.LineWidth = dp.LineWidth
|
||||
}
|
||||
if dp.Join != "" {
|
||||
merged.Join = dp.Join
|
||||
}
|
||||
if dp.Cap != "" {
|
||||
merged.Cap = dp.Cap
|
||||
}
|
||||
if dp.DashPattern != "" {
|
||||
merged.DashPattern = dp.DashPattern
|
||||
merged.DashOffset = dp.DashOffset
|
||||
}
|
||||
if dp.MiterLimit > 0 {
|
||||
merged.MiterLimit = dp.MiterLimit
|
||||
}
|
||||
if dp.FillColor != nil {
|
||||
merged.FillColor = dp.FillColor
|
||||
}
|
||||
if dp.StrokeColor != nil {
|
||||
merged.StrokeColor = dp.StrokeColor
|
||||
}
|
||||
if dp.Font != "" {
|
||||
merged.Font = dp.Font
|
||||
}
|
||||
if dp.Size > 0 {
|
||||
merged.Size = dp.Size
|
||||
}
|
||||
if dp.Weight > 0 {
|
||||
merged.Weight = dp.Weight
|
||||
}
|
||||
if dp.Italic {
|
||||
merged.Italic = dp.Italic
|
||||
}
|
||||
return &merged
|
||||
}
|
||||
return dp
|
||||
@@ -318,8 +391,8 @@ func (r *Renderer) initCommon() {
|
||||
}
|
||||
|
||||
// renderImage 渲染图片
|
||||
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度
|
||||
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64) {
|
||||
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM
|
||||
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix) {
|
||||
resPath, ok := r.Reader.ResMap[obj.ResourceID]
|
||||
if !ok {
|
||||
return
|
||||
@@ -342,9 +415,13 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
|
||||
if obj.CTM == "" {
|
||||
ctm = Matrix{a: box.W, d: box.H}
|
||||
}
|
||||
if parentCTM != nil {
|
||||
ctm = parentCTM.Multiply(ctm)
|
||||
}
|
||||
tx, ty := ctm.Transform(0, 1)
|
||||
canvasX, canvasY := tx+box.X, pageH-(ty+box.Y)
|
||||
ctx.Push()
|
||||
r.applyClips(ctx, obj.Clips, pageH, &ctm)
|
||||
ctx.Translate(canvasX, canvasY)
|
||||
ctx.Scale(ctm.a/imgW, ctm.d/imgH)
|
||||
ctx.DrawImage(0, 0, img, canvas.DPMM(1.0))
|
||||
@@ -352,34 +429,47 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
|
||||
}
|
||||
|
||||
// renderPath 渲染路径
|
||||
// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽
|
||||
func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64) {
|
||||
// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM
|
||||
func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) {
|
||||
ctx.Push()
|
||||
bx, by := 0.0, 0.0
|
||||
if obj.Boundary != "" {
|
||||
if box, err := ParseBox(obj.Boundary); err == nil {
|
||||
bx, by = box.X, box.Y
|
||||
}
|
||||
}
|
||||
ctm := NewMatrix(obj.CTM)
|
||||
fillColor, strokeColor := defaultFill, defaultStroke
|
||||
if strokeColor == nil {
|
||||
strokeColor = canvas.Black
|
||||
if parentCTM != nil {
|
||||
ctm = parentCTM.Multiply(ctm)
|
||||
}
|
||||
r.applyClips(ctx, obj.Clips, pageH, &ctm)
|
||||
fillColor, strokeColor := defaultFill, defaultStroke
|
||||
lineWidth := defaultLW
|
||||
if lineWidth == 0 {
|
||||
lineWidth = 0.353
|
||||
}
|
||||
lineCap := canvas.ButtCap
|
||||
lineJoin := canvas.MiterJoin
|
||||
dashOffset := 0.0
|
||||
var dashPattern []float64
|
||||
if obj.DrawParam != "" {
|
||||
if dp := r.getDrawParam(obj.DrawParam, nil); dp != nil {
|
||||
if dp.LineWidth > 0 {
|
||||
lineWidth = dp.LineWidth
|
||||
}
|
||||
if dp.FillColor != nil {
|
||||
fillColor = parseColor(dp.FillColor.Value)
|
||||
fillColor = parseColorWithAlpha(dp.FillColor.Value, dp.FillColor.Alpha)
|
||||
}
|
||||
if dp.StrokeColor != nil {
|
||||
strokeColor = parseColor(dp.StrokeColor.Value)
|
||||
strokeColor = parseColorWithAlpha(dp.StrokeColor.Value, dp.StrokeColor.Alpha)
|
||||
}
|
||||
if dp.Cap == "Round" {
|
||||
lineCap = canvas.RoundCap
|
||||
} else if dp.Cap == "Square" {
|
||||
lineCap = canvas.SquareCap
|
||||
}
|
||||
if dp.Join == "Round" {
|
||||
lineJoin = canvas.RoundJoin
|
||||
} else if dp.Join == "Bevel" {
|
||||
lineJoin = canvas.BevelJoin
|
||||
}
|
||||
if dp.DashPattern != "" {
|
||||
dashPattern = parseFloats(dp.DashPattern)
|
||||
dashOffset = dp.DashOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,64 +477,65 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
||||
lineWidth = obj.LineWidth
|
||||
}
|
||||
if obj.FillColor != nil {
|
||||
fillColor = parseColor(obj.FillColor.Value)
|
||||
fillColor = parseColorWithAlpha(obj.FillColor.Value, obj.FillColor.Alpha)
|
||||
}
|
||||
if obj.StrokeColor != nil {
|
||||
strokeColor = parseColor(obj.StrokeColor.Value)
|
||||
strokeColor = parseColorWithAlpha(obj.StrokeColor.Value, obj.StrokeColor.Alpha)
|
||||
}
|
||||
p := &canvas.Path{}
|
||||
tokens := strings.Fields(obj.AbbreviatedData)
|
||||
for i := 0; i < len(tokens); {
|
||||
cmd := tokens[i]
|
||||
i++
|
||||
switch cmd {
|
||||
case "M", "S":
|
||||
if i+1 < len(tokens) {
|
||||
x, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
y, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
tx, ty := ctm.Transform(x, y)
|
||||
p.MoveTo(tx+bx, pageH-(ty+by))
|
||||
i += 2
|
||||
}
|
||||
case "L":
|
||||
if i+1 < len(tokens) {
|
||||
x, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
y, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
tx, ty := ctm.Transform(x, y)
|
||||
p.LineTo(tx+bx, pageH-(ty+by))
|
||||
i += 2
|
||||
}
|
||||
case "B":
|
||||
if i+5 < len(tokens) {
|
||||
x1, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
y1, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
x2, _ := strconv.ParseFloat(tokens[i+2], 64)
|
||||
y2, _ := strconv.ParseFloat(tokens[i+3], 64)
|
||||
x3, _ := strconv.ParseFloat(tokens[i+4], 64)
|
||||
y3, _ := strconv.ParseFloat(tokens[i+5], 64)
|
||||
tx1, ty1 := ctm.Transform(x1, y1)
|
||||
tx2, ty2 := ctm.Transform(x2, y2)
|
||||
tx3, ty3 := ctm.Transform(x3, y3)
|
||||
p.CubeTo(tx1+bx, pageH-(ty1+by), tx2+bx, pageH-(ty2+by), tx3+bx, pageH-(ty3+by))
|
||||
i += 6
|
||||
}
|
||||
case "C":
|
||||
p.Close()
|
||||
if obj.Cap != "" {
|
||||
if obj.Cap == "Round" {
|
||||
lineCap = canvas.RoundCap
|
||||
} else if obj.Cap == "Square" {
|
||||
lineCap = canvas.SquareCap
|
||||
} else {
|
||||
lineCap = canvas.ButtCap
|
||||
}
|
||||
}
|
||||
if fillColor != nil {
|
||||
if obj.Join != "" {
|
||||
if obj.Join == "Round" {
|
||||
lineJoin = canvas.RoundJoin
|
||||
} else if obj.Join == "Bevel" {
|
||||
lineJoin = canvas.BevelJoin
|
||||
} else {
|
||||
lineJoin = canvas.MiterJoin
|
||||
}
|
||||
}
|
||||
if obj.DashPattern != "" {
|
||||
dashPattern = parseFloats(obj.DashPattern)
|
||||
dashOffset = obj.DashOffset
|
||||
}
|
||||
p := r.buildPath(obj, pageH, ctm)
|
||||
shouldFill := true
|
||||
if obj.Fill != nil {
|
||||
shouldFill = *obj.Fill
|
||||
}
|
||||
if shouldFill && fillColor != nil {
|
||||
ctx.SetFillColor(fillColor)
|
||||
ctx.DrawPath(0, 0, p)
|
||||
}
|
||||
ctx.SetStrokeColor(strokeColor)
|
||||
ctx.SetStrokeWidth(lineWidth)
|
||||
ctx.DrawPath(0, 0, p)
|
||||
shouldStroke := true
|
||||
if obj.Stroke != nil {
|
||||
shouldStroke = *obj.Stroke
|
||||
}
|
||||
if shouldStroke {
|
||||
if strokeColor == nil {
|
||||
strokeColor = color.Transparent
|
||||
}
|
||||
ctx.SetStrokeColor(strokeColor)
|
||||
ctx.SetStrokeWidth(lineWidth)
|
||||
ctx.SetStrokeCapper(lineCap)
|
||||
ctx.SetStrokeJoiner(lineJoin)
|
||||
if len(dashPattern) > 0 {
|
||||
ctx.SetDashes(dashOffset, dashPattern...)
|
||||
}
|
||||
ctx.DrawPath(0, 0, p)
|
||||
}
|
||||
ctx.Pop()
|
||||
}
|
||||
|
||||
// renderText 渲染文本
|
||||
// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色
|
||||
func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color) {
|
||||
// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM
|
||||
func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix) {
|
||||
ctx.Push()
|
||||
bx, by := 0.0, 0.0
|
||||
if obj.Boundary != "" {
|
||||
@@ -453,34 +544,58 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
||||
}
|
||||
}
|
||||
ctm := NewMatrix(obj.CTM)
|
||||
if parentCTM != nil {
|
||||
ctm = parentCTM.Multiply(ctm)
|
||||
}
|
||||
r.applyClips(ctx, obj.Clips, pageH, &ctm)
|
||||
var dp *DrawParam
|
||||
if obj.DrawParam != "" {
|
||||
dp = r.getDrawParam(obj.DrawParam, nil)
|
||||
}
|
||||
sizeMM := obj.Size
|
||||
if sizeMM == 0 && dp != nil && dp.Size > 0 {
|
||||
sizeMM = dp.Size
|
||||
}
|
||||
if sizeMM == 0 {
|
||||
sizeMM = 3.5
|
||||
}
|
||||
if obj.VScale != 0 {
|
||||
sizeMM *= obj.VScale
|
||||
}
|
||||
if scale := ctm.YScale(); scale > 0 {
|
||||
sizeMM *= scale
|
||||
}
|
||||
sizePt := sizeMM * 2.83465
|
||||
textColor := defaultFill
|
||||
if textColor == nil {
|
||||
textColor = canvas.Black
|
||||
fillColor := defaultFill
|
||||
if fillColor == nil {
|
||||
fillColor = canvas.Black
|
||||
}
|
||||
if obj.DrawParam != "" {
|
||||
if dp := r.getDrawParam(obj.DrawParam, nil); dp != nil && dp.FillColor != nil {
|
||||
textColor = parseColor(dp.FillColor.Value)
|
||||
}
|
||||
if dp != nil && dp.FillColor != nil {
|
||||
fillColor = parseColorWithAlpha(dp.FillColor.Value, dp.FillColor.Alpha)
|
||||
}
|
||||
if obj.FillColor != nil {
|
||||
textColor = parseColor(obj.FillColor.Value)
|
||||
fillColor = parseColorWithAlpha(obj.FillColor.Value, obj.FillColor.Alpha)
|
||||
}
|
||||
fontStyle := canvas.FontRegular
|
||||
if obj.Weight >= 700 {
|
||||
weight := obj.Weight
|
||||
if weight == 0 && dp != nil && dp.Weight > 0 {
|
||||
weight = dp.Weight
|
||||
}
|
||||
if weight >= 700 {
|
||||
fontStyle |= canvas.FontBold
|
||||
}
|
||||
if obj.Italic {
|
||||
italic := obj.Italic
|
||||
if !italic && dp != nil && dp.Italic {
|
||||
italic = true
|
||||
}
|
||||
if italic {
|
||||
fontStyle |= canvas.FontItalic
|
||||
}
|
||||
if of, ok := r.Reader.fontCache[obj.Font]; ok {
|
||||
fontID := obj.Font
|
||||
if fontID == "" && dp != nil && dp.Font != "" {
|
||||
fontID = dp.Font
|
||||
}
|
||||
if of, ok := r.Reader.fontCache[fontID]; ok {
|
||||
if of.Bold {
|
||||
fontStyle |= canvas.FontBold
|
||||
}
|
||||
@@ -488,20 +603,41 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
||||
fontStyle |= canvas.FontItalic
|
||||
}
|
||||
}
|
||||
ff := r.loadFont(obj.Font)
|
||||
face := ff.Face(sizePt, textColor, fontStyle, canvas.FontNormal)
|
||||
ff := r.loadFont(fontID)
|
||||
if ff == nil {
|
||||
return
|
||||
}
|
||||
face := ff.Face(sizePt, fillColor, fontStyle, canvas.FontNormal)
|
||||
for _, tc := range obj.TextCode {
|
||||
runes := []rune(tc.Value)
|
||||
var runes []rune
|
||||
if tc.Index != "" {
|
||||
runes = r.parseIndexRunes(tc.Index, fontID)
|
||||
} else {
|
||||
runes = []rune(tc.Value)
|
||||
}
|
||||
dxs, dys := parseFloats(tc.DeltaX), parseFloats(tc.DeltaY)
|
||||
cx, cy := tc.X, tc.Y
|
||||
xs, ys := parseFloats(tc.X), parseFloats(tc.Y)
|
||||
cx, cy := 0.0, 0.0
|
||||
if len(xs) > 0 {
|
||||
cx = xs[0]
|
||||
}
|
||||
if len(ys) > 0 {
|
||||
cy = ys[0]
|
||||
}
|
||||
for i, run := range runes {
|
||||
str := string(run)
|
||||
if i > 0 {
|
||||
if i < len(xs) {
|
||||
cx = xs[i]
|
||||
} else if i > 0 {
|
||||
if i-1 < len(dxs) {
|
||||
cx += dxs[i-1]
|
||||
} else {
|
||||
} else if len(dys) == 0 {
|
||||
cx += face.TextWidth(str)
|
||||
}
|
||||
}
|
||||
if i < len(ys) {
|
||||
cy = ys[i]
|
||||
} else if i > 0 {
|
||||
if i-1 < len(dys) {
|
||||
cy += dys[i-1]
|
||||
}
|
||||
@@ -509,14 +645,14 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
||||
tx, ty := ctm.Transform(cx, cy)
|
||||
canvasX, canvasY := tx+bx, pageH-(ty+by)
|
||||
text := canvas.NewTextLine(face, str, canvas.Left)
|
||||
ctx.DrawText(canvasX, canvasY, text)
|
||||
if fillColor != nil {
|
||||
ctx.SetFillColor(fillColor)
|
||||
ctx.DrawText(canvasX, canvasY, text)
|
||||
}
|
||||
if strings.Contains(obj.Decoration, "Underline") {
|
||||
uw := sizeMM * 0.05
|
||||
if uw < 0.05 {
|
||||
uw = 0.05
|
||||
}
|
||||
ctx.SetStrokeWidth(uw)
|
||||
ctx.SetStrokeColor(textColor)
|
||||
ctx.SetStrokeColor(fillColor)
|
||||
off := sizeMM * 0.1
|
||||
ctx.MoveTo(canvasX, canvasY-off)
|
||||
ctx.LineTo(canvasX+face.TextWidth(str), canvasY-off)
|
||||
@@ -542,8 +678,18 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
||||
ff := canvas.NewFontFamily(of.FontName)
|
||||
if of.FontFile != "" {
|
||||
if fontData, err := r.Reader.ResData(of.FontFile); err == nil {
|
||||
if _, fixedData, err := FixFontData(fontData); err == nil {
|
||||
if _, fixedData, mapping, _, err := FixFontDataAggressive(fontData, true, true); err == nil {
|
||||
fontData = fixedData
|
||||
if mapping != nil {
|
||||
if r.FontGIDMap == nil {
|
||||
r.FontGIDMap = make(map[string]map[uint16]rune)
|
||||
}
|
||||
inv := make(map[uint16]rune)
|
||||
for k, v := range mapping {
|
||||
inv[v] = k
|
||||
}
|
||||
r.FontGIDMap[fontID] = inv
|
||||
}
|
||||
}
|
||||
var fontStyle canvas.FontStyle
|
||||
if of.Bold {
|
||||
@@ -556,7 +702,9 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
||||
r.FontMap[fontID] = ff
|
||||
return ff
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var fontStyle canvas.FontStyle
|
||||
if of.Bold {
|
||||
@@ -593,15 +741,15 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
||||
names := []string{of.FamilyName, of.FontName}
|
||||
aliases := map[string]string{
|
||||
"simhei": "SimHei",
|
||||
"黑体": "SimHei",
|
||||
"黑体": "SimHei",
|
||||
"microsoft yahei": "Microsoft YaHei",
|
||||
"微软雅黑": "Microsoft YaHei",
|
||||
"微软雅黑": "Microsoft YaHei",
|
||||
"simsun": "SimSun",
|
||||
"宋体": "SimSun",
|
||||
"宋体": "SimSun",
|
||||
"kaiti": "KaiTi",
|
||||
"楷体": "KaiTi",
|
||||
"楷体": "KaiTi",
|
||||
"fangsong": "FangSong",
|
||||
"仿宋": "FangSong",
|
||||
"仿宋": "FangSong",
|
||||
"arial": "Arial",
|
||||
"segoe ui": "Segoe UI",
|
||||
"times new roman": "Times New Roman",
|
||||
@@ -710,3 +858,138 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
|
||||
}
|
||||
ctx.Pop()
|
||||
}
|
||||
|
||||
// applyClips 应用裁剪
|
||||
// 入参: ctx 画布上下文, clips 裁剪对象, pageH 页面高度, parentCTM 父级CTM
|
||||
func (r *Renderer) applyClips(ctx *canvas.Context, clips *Clips, pageH float64, parentCTM *Matrix) {
|
||||
if clips == nil {
|
||||
return
|
||||
}
|
||||
for _, clip := range clips.Clip {
|
||||
for _, area := range clip.Area {
|
||||
for _, pathObj := range area.Path {
|
||||
clipCTM := NewMatrix(pathObj.CTM)
|
||||
if parentCTM != nil {
|
||||
clipCTM = parentCTM.Multiply(clipCTM)
|
||||
}
|
||||
r.buildPath(pathObj, pageH, clipCTM)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildPath 解析路径并返回Canvas Path
|
||||
// 入参: obj 路径对象, pageH 页面高度, ctm 变换矩阵
|
||||
// 返回: *canvas.Path 路径对象
|
||||
func (r *Renderer) buildPath(obj PathObject, pageH float64, ctm Matrix) *canvas.Path {
|
||||
bx, by := 0.0, 0.0
|
||||
if obj.Boundary != "" {
|
||||
if box, err := ParseBox(obj.Boundary); err == nil {
|
||||
bx, by = box.X, box.Y
|
||||
}
|
||||
}
|
||||
p := &canvas.Path{}
|
||||
tokens := strings.Fields(obj.AbbreviatedData)
|
||||
for i := 0; i < len(tokens); {
|
||||
cmd := tokens[i]
|
||||
i++
|
||||
switch cmd {
|
||||
case "M", "S":
|
||||
if i+1 < len(tokens) {
|
||||
x, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
y, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
tx, ty := ctm.Transform(x, y)
|
||||
p.MoveTo(tx+bx, pageH-(ty+by))
|
||||
i += 2
|
||||
}
|
||||
case "L":
|
||||
if i+1 < len(tokens) {
|
||||
x, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
y, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
tx, ty := ctm.Transform(x, y)
|
||||
p.LineTo(tx+bx, pageH-(ty+by))
|
||||
i += 2
|
||||
}
|
||||
case "B":
|
||||
if i+5 < len(tokens) {
|
||||
x1, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
y1, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
x2, _ := strconv.ParseFloat(tokens[i+2], 64)
|
||||
y2, _ := strconv.ParseFloat(tokens[i+3], 64)
|
||||
x3, _ := strconv.ParseFloat(tokens[i+4], 64)
|
||||
y3, _ := strconv.ParseFloat(tokens[i+5], 64)
|
||||
tx1, ty1 := ctm.Transform(x1, y1)
|
||||
tx2, ty2 := ctm.Transform(x2, y2)
|
||||
tx3, ty3 := ctm.Transform(x3, y3)
|
||||
p.CubeTo(tx1+bx, pageH-(ty1+by), tx2+bx, pageH-(ty2+by), tx3+bx, pageH-(ty3+by))
|
||||
i += 6
|
||||
}
|
||||
case "Q":
|
||||
if i+3 < len(tokens) {
|
||||
x1, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
y1, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
x2, _ := strconv.ParseFloat(tokens[i+2], 64)
|
||||
y2, _ := strconv.ParseFloat(tokens[i+3], 64)
|
||||
tx1, ty1 := ctm.Transform(x1, y1)
|
||||
tx2, ty2 := ctm.Transform(x2, y2)
|
||||
p.QuadTo(tx1+bx, pageH-(ty1+by), tx2+bx, pageH-(ty2+by))
|
||||
i += 4
|
||||
}
|
||||
case "A":
|
||||
if i+6 < len(tokens) {
|
||||
rx, _ := strconv.ParseFloat(tokens[i], 64)
|
||||
ry, _ := strconv.ParseFloat(tokens[i+1], 64)
|
||||
rot, _ := strconv.ParseFloat(tokens[i+2], 64)
|
||||
large, _ := strconv.ParseBool(tokens[i+3])
|
||||
sweep, _ := strconv.ParseBool(tokens[i+4])
|
||||
x, _ := strconv.ParseFloat(tokens[i+5], 64)
|
||||
y, _ := strconv.ParseFloat(tokens[i+6], 64)
|
||||
sx := math.Hypot(ctm.a, ctm.c)
|
||||
sy := math.Hypot(ctm.b, ctm.d)
|
||||
ctmRot := math.Atan2(ctm.b, ctm.a) * 180 / math.Pi
|
||||
tx, ty := ctm.Transform(x, y)
|
||||
sweep = !sweep
|
||||
p.ArcTo(rx*sx, ry*sy, -(rot + ctmRot), large, sweep, tx+bx, pageH-(ty+by))
|
||||
i += 7
|
||||
}
|
||||
case "C":
|
||||
p.Close()
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// parseIndexRunes 解析索引字形
|
||||
// 入参: indexStr 索引字符串, fontID 字体ID
|
||||
// 返回: []rune 字形列表
|
||||
func (r *Renderer) parseIndexRunes(indexStr string, fontID string) []rune {
|
||||
var gids []int
|
||||
parts := strings.Fields(indexStr)
|
||||
for _, p := range parts {
|
||||
if strings.Contains(p, "-") {
|
||||
sub := strings.Split(p, "-")
|
||||
if len(sub) == 2 {
|
||||
start, _ := strconv.Atoi(sub[0])
|
||||
end, _ := strconv.Atoi(sub[1])
|
||||
for k := start; k <= end; k++ {
|
||||
gids = append(gids, k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val, _ := strconv.Atoi(p)
|
||||
gids = append(gids, val)
|
||||
}
|
||||
}
|
||||
mapping := r.FontGIDMap[fontID]
|
||||
var res []rune
|
||||
for _, gid := range gids {
|
||||
if mapping != nil {
|
||||
if rVal, ok := mapping[uint16(gid)]; ok {
|
||||
res = append(res, rVal)
|
||||
continue
|
||||
}
|
||||
}
|
||||
res = append(res, rune(gid))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user