mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
e2c74894f0
commit
246d3fd016
3 files changed
+126
-26
No files matched your search
@@ -238,8 +238,8 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
|||||||
spans = textGlyphSpans(runes, glyphTransforms, codePos)
|
spans = textGlyphSpans(runes, glyphTransforms, codePos)
|
||||||
}
|
}
|
||||||
positioner := NewTextPositioner(tc)
|
positioner := NewTextPositioner(tc)
|
||||||
xs, ys := positioner.xs, positioner.ys
|
positioned := textCodePositioned(tc, positioner.xs, positioner.ys)
|
||||||
drawAsPath := embeddedFont || face.FauxBold > 0 || textCodePositioned(tc, xs, ys) || fillClip != nil || shadedFill || fillPattern != nil || shouldStroke
|
drawAsPath := embeddedFont || face.FauxBold > 0 || positioned || fillClip != nil || shadedFill || fillPattern != nil || shouldStroke
|
||||||
for i, glyph := range glyphs {
|
for i, glyph := range glyphs {
|
||||||
str := glyph.Text
|
str := glyph.Text
|
||||||
drawAsGlyphPath := drawAsPath || glyph.GlyphID >= 0
|
drawAsGlyphPath := drawAsPath || glyph.GlyphID >= 0
|
||||||
@@ -343,7 +343,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
|||||||
if pdfText {
|
if pdfText {
|
||||||
glyphClip = nil
|
glyphClip = nil
|
||||||
}
|
}
|
||||||
if !pdfText && fillClip != nil && !embeddedFont && face.FauxBold == 0 && !textCodePositioned(tc, xs, ys) && !shadedFill && fillPattern == nil && glyph.GlyphID < 0 && !hasUnderline {
|
if !pdfText && fillClip != nil && !embeddedFont && face.FauxBold == 0 && !positioned && !shadedFill && fillPattern == nil && glyph.GlyphID < 0 && !hasUnderline {
|
||||||
if rect, ok := rectangularPath(fillClip); ok {
|
if rect, ok := rectangularPath(fillClip); ok {
|
||||||
transform := canvas.Identity.Translate(canvasX, canvasY)
|
transform := canvas.Identity.Translate(canvasX, canvasY)
|
||||||
if useTextMatrix {
|
if useTextMatrix {
|
||||||
|
|||||||
+12
-7
@@ -107,7 +107,8 @@ func (obj TextObject) textCodeLineBreak(index int) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
right := left + obj.Size
|
right := left + obj.Size
|
||||||
for _, dx := range previous.GetDeltaX() {
|
deltas := textPositionValues{data: previous.DeltaX, compressed: strings.Contains(previous.DeltaX, "g")}
|
||||||
|
for dx, ok := deltas.next(); ok; dx, ok = deltas.next() {
|
||||||
right += dx
|
right += dx
|
||||||
}
|
}
|
||||||
return x <= right
|
return x <= right
|
||||||
@@ -266,13 +267,17 @@ func (r *Renderer) textIndexRune(fontID string, glyphID int) rune {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// textCodePositioned 判断文本编码是否带显式定位
|
// textCodePositioned 判断文本编码是否带显式定位
|
||||||
// 入参: textCode 文本编码, xs X坐标列表, ys Y坐标列表
|
// 入参: textCode 文本编码, xs 横坐标读取器, ys 纵坐标读取器
|
||||||
// 返回: bool 是否带显式定位
|
// 返回: bool 是否带显式定位
|
||||||
func textCodePositioned(textCode TextCode, xs, ys []float64) bool {
|
func textCodePositioned(textCode TextCode, xs, ys textPositionValues) bool {
|
||||||
return strings.TrimSpace(textCode.DeltaX) != "" ||
|
if strings.TrimSpace(textCode.DeltaX) != "" || strings.TrimSpace(textCode.DeltaY) != "" {
|
||||||
strings.TrimSpace(textCode.DeltaY) != "" ||
|
return true
|
||||||
len(xs) > 1 ||
|
}
|
||||||
len(ys) > 1
|
xs.next()
|
||||||
|
ys.next()
|
||||||
|
_, multipleX := xs.next()
|
||||||
|
_, multipleY := ys.next()
|
||||||
|
return multipleX || multipleY
|
||||||
}
|
}
|
||||||
|
|
||||||
// textDelta 获取文本偏移量
|
// textDelta 获取文本偏移量
|
||||||
|
|||||||
+111
-16
@@ -14,13 +14,27 @@
|
|||||||
|
|
||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import "slices"
|
import (
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
// TextPositioner 按OFD坐标及增量定位连续字形,不依赖字体或绘图库
|
// TextPositioner 按OFD坐标及增量定位连续字形,不依赖字体或绘图库
|
||||||
type TextPositioner struct {
|
type TextPositioner struct {
|
||||||
xs, ys, dxs, dys []float64
|
xs, ys, dxs, dys textPositionValues
|
||||||
current Point
|
current Point
|
||||||
index int
|
started bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// textPositionValues 按需读取定位数值及重复位移,不展开重复数组
|
||||||
|
type textPositionValues struct {
|
||||||
|
data string
|
||||||
|
value float64
|
||||||
|
repeat int
|
||||||
|
compressed bool
|
||||||
|
present bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// textCodeOrigins 按标准继承文本段原点,首段缺失坐标时以零横坐标和字体上升高度容错恢复
|
// textCodeOrigins 按标准继承文本段原点,首段缺失坐标时以零横坐标和字体上升高度容错恢复
|
||||||
@@ -56,27 +70,108 @@ func textCodeOrigins(codes []TextCode, ascent float64) []TextCode {
|
|||||||
// 入参: code 文本定位数据
|
// 入参: code 文本定位数据
|
||||||
// 返回: TextPositioner 字形定位器
|
// 返回: TextPositioner 字形定位器
|
||||||
func NewTextPositioner(code TextCode) TextPositioner {
|
func NewTextPositioner(code TextCode) TextPositioner {
|
||||||
return TextPositioner{xs: parseFloats(code.X), ys: parseFloats(code.Y), dxs: parseFloats(code.DeltaX), dys: parseFloats(code.DeltaY)}
|
return TextPositioner{
|
||||||
|
xs: textPositionValues{data: code.X, compressed: strings.Contains(code.X, "g")},
|
||||||
|
ys: textPositionValues{data: code.Y, compressed: strings.Contains(code.Y, "g")},
|
||||||
|
dxs: textPositionValues{data: code.DeltaX, compressed: strings.Contains(code.DeltaX, "g")},
|
||||||
|
dys: textPositionValues{data: code.DeltaY, compressed: strings.Contains(code.DeltaY, "g")},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next 根据坐标与位移返回下一个字形原点
|
// Next 根据坐标与位移返回下一个字形原点
|
||||||
// 返回: Point 字形原点
|
// 返回: Point 字形原点
|
||||||
func (p *TextPositioner) Next() Point {
|
func (p *TextPositioner) Next() Point {
|
||||||
i := p.index
|
if p.started {
|
||||||
if i < len(p.xs) {
|
if p.dxs.repeat > 0 || p.dxs.data != "" {
|
||||||
p.current.X = p.xs[i]
|
p.dxs.next()
|
||||||
} else if i > 0 {
|
}
|
||||||
if dx, ok := textDelta(p.dxs, i-1); ok {
|
if p.dys.repeat > 0 || p.dys.data != "" {
|
||||||
p.current.X += dx
|
p.dys.next()
|
||||||
|
}
|
||||||
|
if p.dxs.present {
|
||||||
|
p.current.X += p.dxs.value
|
||||||
|
}
|
||||||
|
if p.dys.present {
|
||||||
|
p.current.Y += p.dys.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i < len(p.ys) {
|
if p.xs.repeat > 0 || p.xs.data != "" {
|
||||||
p.current.Y = p.ys[i]
|
if x, ok := p.xs.next(); ok {
|
||||||
} else if i > 0 {
|
p.current.X = x
|
||||||
if dy, ok := textDelta(p.dys, i-1); ok {
|
|
||||||
p.current.Y += dy
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.index++
|
if p.ys.repeat > 0 || p.ys.data != "" {
|
||||||
|
if y, ok := p.ys.next(); ok {
|
||||||
|
p.current.Y = y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.started = true
|
||||||
return p.current
|
return p.current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// next 读取下一个定位数值
|
||||||
|
// 返回: float64 数值, bool 是否存在
|
||||||
|
func (v *textPositionValues) next() (float64, bool) {
|
||||||
|
if v.repeat > 0 {
|
||||||
|
v.repeat--
|
||||||
|
return v.value, true
|
||||||
|
}
|
||||||
|
if v.data == "" {
|
||||||
|
return v.value, false
|
||||||
|
}
|
||||||
|
return v.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
// read 解析剩余定位字段并保存重复计数
|
||||||
|
// 返回: float64 数值, bool 是否存在
|
||||||
|
func (v *textPositionValues) read() (float64, bool) {
|
||||||
|
count, pending := 0, false
|
||||||
|
for field := v.field(); field != ""; field = v.field() {
|
||||||
|
if v.compressed && field == "g" {
|
||||||
|
pending = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pending {
|
||||||
|
var err error
|
||||||
|
count, err = strconv.Atoi(field)
|
||||||
|
if err != nil {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
pending = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value, err := strconv.ParseFloat(field, 64)
|
||||||
|
if count > 0 {
|
||||||
|
v.value, v.repeat, v.present = value, count-1, true
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v.value, v.present = value, true
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.value, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// field 读取定位数组中的下一个字段
|
||||||
|
// 返回: string 数值或重复标记,无剩余字段时为空
|
||||||
|
func (v *textPositionValues) field() string {
|
||||||
|
start := -1
|
||||||
|
for end, r := range v.data {
|
||||||
|
if unicode.IsSpace(r) || !v.compressed && r == ',' {
|
||||||
|
if start >= 0 {
|
||||||
|
field := v.data[start:end]
|
||||||
|
v.data = v.data[end:]
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
} else if start < 0 {
|
||||||
|
start = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var field string
|
||||||
|
if start >= 0 {
|
||||||
|
field = v.data[start:]
|
||||||
|
}
|
||||||
|
v.data = ""
|
||||||
|
return field
|
||||||
|
}
|
||||||
Reference in new issue
Block a user