mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
Compare commits
2
Commits
04efce42d8
...
4fee3dfd8e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fee3dfd8e | ||
|
|
f322daa616 |
No files matched your search
@@ -3279,6 +3279,7 @@ button.outline-link:hover {
|
|||||||
|
|
||||||
.edit-text span[data-glyph]::after {
|
.edit-text span[data-glyph]::after {
|
||||||
content: attr(data-glyph);
|
content: attr(data-glyph);
|
||||||
|
letter-spacing: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@@ -1851,12 +1851,15 @@ export class CanvasEditor {
|
|||||||
right = Math.max(right, x);
|
right = Math.max(right, x);
|
||||||
bottom = Math.max(bottom, y + lineHeight - baseline);
|
bottom = Math.max(bottom, y + lineHeight - baseline);
|
||||||
}
|
}
|
||||||
for (const char of run.text) {
|
const chars = [...run.text];
|
||||||
|
while (index < chars.length) {
|
||||||
|
const count = source.glyphCounts?.[glyphIndex] || 1;
|
||||||
|
const char = chars.slice(index, index + count).join("");
|
||||||
x = run.positions[index][0] * unit;
|
x = run.positions[index][0] * unit;
|
||||||
y = run.positions[index][1] * unit;
|
y = run.positions[index][1] * unit;
|
||||||
const span = document.createElement("span"), node = document.createTextNode(char);
|
const span = document.createElement("span"), node = document.createTextNode(char);
|
||||||
span.append(node);
|
span.append(node);
|
||||||
const glyph = source.glyphs?.[glyphIndex++];
|
const glyph = source.glyphs?.[glyphIndex];
|
||||||
if (glyph) {
|
if (glyph) {
|
||||||
span.dataset.glyph = glyph;
|
span.dataset.glyph = glyph;
|
||||||
glyphPositions.push({ span, start: position, end: position + char.length });
|
glyphPositions.push({ span, start: position, end: position + char.length });
|
||||||
@@ -1869,7 +1872,11 @@ export class CanvasEditor {
|
|||||||
positions.push({ node, start: position, end: position + char.length });
|
positions.push({ node, start: position, end: position + char.length });
|
||||||
position += char.length;
|
position += char.length;
|
||||||
const advance = context.measureText(glyph || char).width;
|
const advance = context.measureText(glyph || char).width;
|
||||||
if (glyph) span.style.width = `${advance}px`;
|
if (glyph) {
|
||||||
|
const naturalAdvance = [...char].reduce((width, value) => width + context.measureText(value).width, 0);
|
||||||
|
span.style.width = `${advance}px`;
|
||||||
|
span.style.letterSpacing = `${(advance - naturalAdvance) / count}px`;
|
||||||
|
}
|
||||||
for (const dx of [0, advance * scale]) {
|
for (const dx of [0, advance * scale]) {
|
||||||
for (const dy of [-baseline * vscale, (lineHeight - baseline) * vscale]) {
|
for (const dy of [-baseline * vscale, (lineHeight - baseline) * vscale]) {
|
||||||
const px = x + dx * cos - dy * sin, py = y + dx * sin + dy * cos;
|
const px = x + dx * cos - dy * sin, py = y + dx * sin + dy * cos;
|
||||||
@@ -1880,7 +1887,8 @@ export class CanvasEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
naturalX += advance;
|
naturalX += advance;
|
||||||
index++;
|
index += count;
|
||||||
|
glyphIndex += count;
|
||||||
}
|
}
|
||||||
if (!line.hasChildNodes()) {
|
if (!line.hasChildNodes()) {
|
||||||
const node = document.createTextNode("");
|
const node = document.createTextNode("");
|
||||||
|
|||||||
+30
-19
@@ -4353,9 +4353,28 @@ func (e *businessEngine) previewText(args []platformValue) (any, error) {
|
|||||||
} else if err := editor.RewriteTextWithFont(&text, args[2].String(), fontID); err != nil {
|
} else if err := editor.RewriteTextWithFont(&text, args[2].String(), fontID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
glyphs := make(map[int]string)
|
||||||
|
glyphCounts := make(map[int]int)
|
||||||
|
if fontID == "" {
|
||||||
|
for _, transform := range text.CGTransform {
|
||||||
|
if transform.CodeCount != transform.GlyphCount && transform.GlyphCount != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, value := range strings.Fields(transform.Glyphs) {
|
||||||
|
glyph, err := strconv.ParseUint(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
glyphs[transform.CodePosition+i] = string(rune(0xF0000 + glyph))
|
||||||
|
}
|
||||||
|
if transform.GlyphCount == 1 && transform.CodeCount > 1 {
|
||||||
|
glyphCounts[transform.CodePosition] = transform.CodeCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var runs []any
|
var runs []any
|
||||||
var x, y float64
|
var x, y float64
|
||||||
value, offset := []rune(text.Text()), 0
|
value, offset, glyphOffset := []rune(text.Text()), 0, 0
|
||||||
for _, code := range text.TextCode {
|
for _, code := range text.TextCode {
|
||||||
if code.X != "" {
|
if code.X != "" {
|
||||||
x, _ = strconv.ParseFloat(code.X, 64)
|
x, _ = strconv.ParseFloat(code.X, 64)
|
||||||
@@ -4371,10 +4390,17 @@ func (e *businessEngine) previewText(args []platformValue) (any, error) {
|
|||||||
code.X, code.Y = fmt.Sprint(x), fmt.Sprint(y)
|
code.X, code.Y = fmt.Sprint(x), fmt.Sprint(y)
|
||||||
positioner := ofdgo.NewTextPositioner(code)
|
positioner := ofdgo.NewTextPositioner(code)
|
||||||
positions := make([][2]float64, len([]rune(content)))
|
positions := make([][2]float64, len([]rune(content)))
|
||||||
|
var point ofdgo.Point
|
||||||
|
remaining := 0
|
||||||
for i := range positions {
|
for i := range positions {
|
||||||
point := positioner.Next()
|
if remaining == 0 {
|
||||||
positions[i] = [2]float64{point.X, point.Y}
|
point = positioner.Next()
|
||||||
|
remaining = max(1, glyphCounts[glyphOffset+i])
|
||||||
}
|
}
|
||||||
|
positions[i] = [2]float64{point.X, point.Y}
|
||||||
|
remaining--
|
||||||
|
}
|
||||||
|
glyphOffset += len(positions)
|
||||||
runs = append(runs, map[string]any{"text": content, "x": x, "y": y, "positions": positions, "line": line})
|
runs = append(runs, map[string]any{"text": content, "x": x, "y": y, "positions": positions, "line": line})
|
||||||
offset += len(positions)
|
offset += len(positions)
|
||||||
}
|
}
|
||||||
@@ -4382,22 +4408,7 @@ func (e *businessEngine) previewText(args []platformValue) (any, error) {
|
|||||||
if scale == 0 {
|
if scale == 0 {
|
||||||
scale = 1
|
scale = 1
|
||||||
}
|
}
|
||||||
glyphs := make(map[int]string)
|
return map[string]any{"runs": runs, "glyphs": glyphs, "glyphCounts": glyphCounts, "scale": scale, "vscale": text.VScale, "rotation": text.CharDirection, "weight": text.Weight, "italic": text.Italic}, nil
|
||||||
if fontID == "" {
|
|
||||||
for _, transform := range text.CGTransform {
|
|
||||||
if transform.CodeCount != transform.GlyphCount {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i, value := range strings.Fields(transform.Glyphs) {
|
|
||||||
glyph, err := strconv.ParseUint(value, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
glyphs[transform.CodePosition+i] = string(rune(0xF0000 + glyph))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map[string]any{"runs": runs, "glyphs": glyphs, "scale": scale, "vscale": text.VScale, "rotation": text.CharDirection, "weight": text.Weight, "italic": text.Italic}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// readTextSelection 将画布路径转换为同一容器的库层文字选区
|
// readTextSelection 将画布路径转换为同一容器的库层文字选区
|
||||||
|
|||||||
@@ -963,7 +963,7 @@ func pruneUnreferencedFiles(ctx context.Context, reader *Reader, candidates ...s
|
|||||||
if root == "" {
|
if root == "" {
|
||||||
root = node.Name.Local
|
root = node.Name.Local
|
||||||
}
|
}
|
||||||
if classifyOFDNamespace(node.Name.Space) == ofdXMLUnknown || slices.Contains([]string{"CustomTags", "Extensions", "ExtendData", "Data"}, node.Name.Local) {
|
if classifyOFDNamespace(node.Name.Space) == ofdXMLUnknown || root == "CustomTags" || slices.Contains([]string{"Extensions", "ExtendData", "Data"}, node.Name.Local) {
|
||||||
uncertain = true
|
uncertain = true
|
||||||
return fmt.Errorf("unknown file dependencies in %s", name)
|
return fmt.Errorf("unknown file dependencies in %s", name)
|
||||||
}
|
}
|
||||||
@@ -990,7 +990,19 @@ func pruneUnreferencedFiles(ctx context.Context, reader *Reader, candidates ...s
|
|||||||
versions[name] = append(versions[name], versionEditLink{node: node, target: target})
|
versions[name] = append(versions[name], versionEditLink{node: node, target: target})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := reader.scanDocumentFileReferences(ctx, name, true, add, record, inspect); err != nil {
|
err := reader.scanDocumentFileReferences(ctx, name, true, add, record, inspect)
|
||||||
|
if uncertain && (root == "CustomTags" || root == "RichText") {
|
||||||
|
data, readErr := reader.readFileViewContext(ctx, name)
|
||||||
|
if readErr != nil {
|
||||||
|
return readErr
|
||||||
|
}
|
||||||
|
refs := &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)}
|
||||||
|
if _, safe := refs.scanContext(ctx, bytes.NewReader(data), name); safe {
|
||||||
|
uncertain = false
|
||||||
|
err = reader.scanDocumentFileReferences(ctx, name, true, add, record, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
var syntax *xml.SyntaxError
|
var syntax *xml.SyntaxError
|
||||||
if uncertain || errors.As(err, &syntax) {
|
if uncertain || errors.As(err, &syntax) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ type editorReferenceScan struct {
|
|||||||
stack []string
|
stack []string
|
||||||
fonts []*editorFontUsage
|
fonts []*editorFontUsage
|
||||||
text strings.Builder
|
text strings.Builder
|
||||||
|
richText strings.Builder
|
||||||
resource bool
|
resource bool
|
||||||
capture bool
|
capture bool
|
||||||
safe bool
|
safe bool
|
||||||
@@ -140,14 +141,39 @@ func (s *editorReferenceScan) accept(token xml.Token) bool {
|
|||||||
}
|
}
|
||||||
switch token := token.(type) {
|
switch token := token.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
if classifyOFDNamespace(token.Name.Space) == ofdXMLUnknown {
|
rich := token.Name.Space == richTextNamespace
|
||||||
|
known := classifyOFDNamespace(token.Name.Space) != ofdXMLUnknown
|
||||||
|
if rich {
|
||||||
|
known = len(s.stack) == 0 && token.Name.Local == "RichText" || len(s.stack) == 1 && s.stack[0] == "RichText" && token.Name.Local == "ObjectRef"
|
||||||
|
} else if len(s.stack) > 0 && s.stack[0] == "RichText" {
|
||||||
|
known = false
|
||||||
|
}
|
||||||
|
if len(s.stack) > 0 && s.stack[0] == "CustomTags" {
|
||||||
|
known = known && (len(s.stack) == 1 && token.Name.Local == "CustomTag" || len(s.stack) == 2 && s.stack[1] == "CustomTag" && (token.Name.Local == "FileLoc" || token.Name.Local == "SchemaLoc"))
|
||||||
|
}
|
||||||
|
if !known {
|
||||||
s.safe = false
|
s.safe = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if token.Name.Local == "CustomTags" || token.Name.Local == "Extensions" || token.Name.Local == "ExtendData" || token.Name.Local == "Data" {
|
if token.Name.Local == "Extensions" || token.Name.Local == "ExtendData" || token.Name.Local == "Data" {
|
||||||
s.safe = false
|
s.safe = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if token.Name.Local == "CustomTag" {
|
||||||
|
var kind, namespace string
|
||||||
|
for _, attr := range token.Attr {
|
||||||
|
if attr.Name.Space == "" && attr.Name.Local == "TypeID" {
|
||||||
|
kind = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Name.Space == "" && attr.Name.Local == "NameSpace" {
|
||||||
|
namespace = attr.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kind != richTextTagType || namespace != richTextNamespace {
|
||||||
|
s.safe = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(s.stack) == 0 {
|
if len(s.stack) == 0 {
|
||||||
if s.seen {
|
if s.seen {
|
||||||
s.safe = false
|
s.safe = false
|
||||||
@@ -184,7 +210,8 @@ func (s *editorReferenceScan) accept(token xml.Token) bool {
|
|||||||
if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" || attr.Name.Local == "xmlns:ofd" || attr.Name.Space == XMLNamespace {
|
if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" || attr.Name.Local == "xmlns:ofd" || attr.Name.Space == XMLNamespace {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if attr.Name.Space != "" {
|
custom := len(s.stack) == 0 && token.Name.Local == "CustomTags" || len(s.stack) > 0 && s.stack[0] == "CustomTags"
|
||||||
|
if attr.Name.Space != "" || rich && (token.Name.Local != "ObjectRef" || attr.Name.Local != "PageRef") || custom && (token.Name.Local != "CustomTag" || attr.Name.Local != "TypeID" && attr.Name.Local != "NameSpace") {
|
||||||
s.safe = false
|
s.safe = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -225,6 +252,9 @@ func (s *editorReferenceScan) accept(token xml.Token) bool {
|
|||||||
s.capture = true
|
s.capture = true
|
||||||
}
|
}
|
||||||
case xml.CharData:
|
case xml.CharData:
|
||||||
|
if len(s.stack) == 1 && s.stack[0] == "RichText" {
|
||||||
|
s.richText.Write(token)
|
||||||
|
}
|
||||||
if len(s.stack) == 0 && len(bytes.Trim(token, " \t\r\n")) != 0 {
|
if len(s.stack) == 0 && len(bytes.Trim(token, " \t\r\n")) != 0 {
|
||||||
s.safe = false
|
s.safe = false
|
||||||
return false
|
return false
|
||||||
@@ -233,6 +263,14 @@ func (s *editorReferenceScan) accept(token xml.Token) bool {
|
|||||||
s.text.Write(token)
|
s.text.Write(token)
|
||||||
}
|
}
|
||||||
case xml.EndElement:
|
case xml.EndElement:
|
||||||
|
if len(s.stack) == 3 && s.stack[0] == "CustomTags" && token.Name.Local == "SchemaLoc" && strings.TrimSpace(s.text.String()) != "" {
|
||||||
|
s.safe = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(s.stack) == 1 && s.stack[0] == "RichText" && !richTextMetadataFontSafe(s.richText.String()) {
|
||||||
|
s.safe = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
if len(s.stack) == 0 || s.stack[len(s.stack)-1] != token.Name.Local {
|
if len(s.stack) == 0 || s.stack[len(s.stack)-1] != token.Name.Local {
|
||||||
s.safe = false
|
s.safe = false
|
||||||
return false
|
return false
|
||||||
|
|||||||
+26
-16
@@ -909,22 +909,7 @@ func richTextTagsFontSafe(ctx context.Context, reader *Reader, index int) (bool,
|
|||||||
if root.Name != "RichText" || root.Namespace != richTextNamespace || len(root.Attributes) != 0 {
|
if root.Name != "RichText" || root.Namespace != richTextNamespace || len(root.Attributes) != 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
var meta richTextMetadata
|
if !richTextMetadataFontSafe(root.Text) {
|
||||||
decoder := json.NewDecoder(bytes.NewBufferString(root.Text))
|
|
||||||
decoder.DisallowUnknownFields()
|
|
||||||
if err := decoder.Decode(&meta); err != nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if decoder.Decode(new(any)) != io.EOF || meta.Version != 1 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
for i := range meta.Value.Runs {
|
|
||||||
if meta.Value.Runs[i].Format.Font != "" {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
meta.Value.Runs[i].Format.Font = "1"
|
|
||||||
}
|
|
||||||
if err := meta.Value.Validate(); err != nil {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
for _, child := range root.Children {
|
for _, child := range root.Children {
|
||||||
@@ -935,3 +920,28 @@ func richTextTagsFontSafe(ctx context.Context, reader *Reader, index int) (bool,
|
|||||||
}
|
}
|
||||||
return true, ctx.Err()
|
return true, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// richTextMetadataFontSafe 核对本库富文本元数据不包含字体资源编号或未知字段
|
||||||
|
// 入参: value 元数据JSON
|
||||||
|
// 返回: bool 是否可由标准图元完整统计字体引用
|
||||||
|
func richTextMetadataFontSafe(value string) bool {
|
||||||
|
var meta richTextMetadata
|
||||||
|
decoder := json.NewDecoder(bytes.NewBufferString(value))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
if err := decoder.Decode(&meta); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if decoder.Decode(new(any)) != io.EOF || meta.Version != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range meta.Value.Runs {
|
||||||
|
if meta.Value.Runs[i].Format.Font != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
meta.Value.Runs[i].Format.Font = "1"
|
||||||
|
}
|
||||||
|
if err := meta.Value.Validate(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
Reference in new issue
Block a user