1 package xcf 2 3 import ( 4 "bytes" 5 "encoding/xml" 6 "fmt" 7 "html" 8 "image/color" 9 "io" 10 "strconv" 11 "strings" 12 "unsafe" 13 14 "vimagination.zapto.org/limage" 15 "vimagination.zapto.org/limage/lcolor" 16 ) 17 18 func parseTextData(t *parasite) (limage.TextData, error) { 19 tags, err := t.Parse() 20 if err != nil { 21 return nil, err 22 } 23 var ( 24 textData string 25 defaultText limage.TextDatum 26 ) 27 defaultText.BackColor = color.Alpha{} 28 defaultText.ForeColor = color.Gray{} 29 for _, tg := range tags { 30 switch tg.Name { 31 case "text": 32 defaultText.Data, _ = tg.Values[0].(string) 33 case "markup": 34 if len(tg.Values) == 1 { 35 textData, _ = tg.Values[0].(string) 36 } 37 case "font": 38 if len(tg.Values) == 1 { 39 defaultText.Font, _ = tg.Values[0].(string) 40 } 41 case "font-size": 42 if len(tg.Values) == 1 { 43 f, _ := tg.Values[0].(float64) 44 defaultText.Size = uint32(f) 45 } 46 case "font-size-unit": 47 case "antialias": 48 case "language": 49 case "base-direction": 50 case "color": 51 if len(tg.Values) == 1 { 52 t, _ := tg.Values[0].(tag) 53 if t.Name == "color-rgb" && len(t.Values) != 3 { 54 r, _ := t.Values[0].(float64) 55 g, _ := t.Values[1].(float64) 56 b, _ := t.Values[2].(float64) 57 defaultText.ForeColor = lcolor.RGB{R: uint8(r), G: uint8(g), B: uint8(b)} 58 } 59 } 60 case "justify": 61 case "box-mode": 62 case "box-width": 63 case "box-height": 64 case "box-unit": 65 case "hinting": 66 } 67 } 68 if defaultText.Data != "" { 69 return limage.TextData{defaultText}, nil 70 } 71 xd := xml.NewDecoder(strings.NewReader(textData)) 72 stack := limage.TextData{defaultText} 73 td := make(limage.TextData, 0, 32) 74 for { 75 t, err := xd.Token() 76 if err != nil { 77 if err == io.EOF { 78 break 79 } 80 return nil, err 81 } 82 switch t := t.(type) { 83 case xml.StartElement: 84 nt := stack[len(stack)-1] 85 switch t.Name.Local { 86 case "markup": 87 case "span": 88 for _, a := range t.Attr { 89 switch a.Name.Local { 90 case "font": 91 nt.Font = a.Value 92 case "foreground": 93 if len(a.Value) == 7 && a.Value[0] == '#' { 94 n, err := strconv.ParseUint(a.Value[1:], 16, 32) 95 if err != nil { 96 return nil, err 97 } 98 nt.ForeColor = color.RGBA{uint8(n >> 16), uint8((n >> 8) & 255), uint8(n & 255), 255} 99 } else if len(a.Value) == 4 && a.Value[0] == '#' { 100 n, err := strconv.ParseUint(a.Value[1:], 16, 32) 101 if err != nil { 102 return nil, err 103 } 104 r := (n >> 4) & 240 105 r |= r >> 4 106 g := n & 240 107 g |= g >> 4 108 b := n & 15 109 b |= b << 4 110 nt.ForeColor = color.RGBA{uint8(r), uint8(g), uint8(b), 255} 111 } 112 case "size": 113 s, err := strconv.ParseUint(a.Value, 10, 32) 114 if err != nil { 115 return nil, err 116 } 117 nt.Size = uint32(s) >> 10 118 case "letter_spacing": 119 ls, err := strconv.ParseUint(a.Value, 10, 32) 120 if err != nil { 121 return nil, err 122 } 123 nt.LetterSpacing = uint32(ls) >> 10 124 case "rise": 125 r, err := strconv.ParseUint(a.Value, 10, 32) 126 if err != nil { 127 return nil, err 128 } 129 nt.Rise = uint32(r) >> 10 130 } 131 } 132 case "b": 133 nt.Bold = true 134 case "i": 135 nt.Italic = true 136 case "s": 137 nt.Strikethrough = true 138 case "u": 139 nt.Underline = true 140 } 141 stack = append(stack, nt) 142 case xml.CharData: 143 nt := stack[len(stack)-1] 144 nt.Data = string(t) 145 td = append(td, nt) 146 case xml.EndElement: 147 stack = stack[:len(stack)-1] 148 } 149 } 150 return td, nil 151 } 152 153 type quoteWriter struct { 154 *bytes.Buffer 155 } 156 157 func (q *quoteWriter) Write(b []byte) (int, error) { 158 return q.WriteString(*(*string)(unsafe.Pointer(&b))) 159 } 160 161 func (q *quoteWriter) WriteString(s string) (int, error) { 162 for _, r := range s { 163 switch r { 164 case '\\': 165 q.Buffer.WriteString("\\\\") 166 case '"': 167 q.Buffer.WriteString("\\\"") 168 default: 169 q.Buffer.WriteRune(r) 170 } 171 } 172 return len(s), nil 173 } 174 175 func (e *encoder) WriteText(text limage.TextData, dx, dy uint32) { 176 var ( 177 data []byte 178 base limage.TextDatum 179 ) 180 181 if len(text) == 1 { 182 base = text[0] 183 data = fmt.Appendf(data, "(text %q)\n", base.Data) 184 } else { 185 base = limage.TextDatum{ 186 BackColor: lcolor.RGB{}, 187 ForeColor: lcolor.RGB{}, 188 Font: "Sans", 189 Size: 18, 190 } 191 192 data = append(data, "(markup \"<markup>"...) 193 194 for _, td := range text { 195 var foreground, background bool 196 if r, g, b, _ := td.ForeColor.RGBA(); r != 0 || g != 0 || b != 0 { 197 foreground = true 198 data = fmt.Appendf(data, "<span foreground=\\\"#%02X%02X%02X\\\">", r>>8, g>>8, b>>8) 199 } 200 if r, g, b, _ := td.BackColor.RGBA(); r != 0 || g != 0 || b != 0 { 201 background = true 202 data = fmt.Appendf(data, "<span background=\\\"#%02X%02X%02X\\\">", r, g, b) 203 } 204 if td.Font != "Sans" { 205 data = fmt.Appendf(data, "<span font=%q>", td.Font) 206 } 207 if td.Bold { 208 data = append(data, "<b>"...) 209 } 210 if td.Italic { 211 data = append(data, "<i>"...) 212 } 213 if td.Underline { 214 data = append(data, "<u>"...) 215 } 216 if td.Strikethrough { 217 data = append(data, "<s>"...) 218 } 219 if td.LetterSpacing != 0 { 220 data = fmt.Appendf(data, "<span letter_spacing=\\\"%d\\\">", td.LetterSpacing<<10) 221 } 222 if td.Size != 18 { 223 data = fmt.Appendf(data, "<span size=\\\"%d\\\">", td.Size<<10) 224 } 225 if td.Rise != 0 { 226 data = fmt.Appendf(data, "<span rise=\\\"%d\\\">", td.Rise<<10) 227 } 228 data = fmt.Appendf(data, "%q", html.EscapeString(td.Data)) 229 if td.Rise != 0 { 230 data = append(data, "</span>"...) 231 } 232 if td.Size != 18 { 233 data = append(data, "</span>"...) 234 } 235 if td.LetterSpacing != 0 { 236 data = append(data, "</span>"...) 237 } 238 if td.Strikethrough { 239 data = append(data, "</s>"...) 240 } 241 if td.Underline { 242 data = append(data, "</u>"...) 243 } 244 if td.Italic { 245 data = append(data, "</i>"...) 246 } 247 if td.Bold { 248 data = append(data, "</b>"...) 249 } 250 if td.Font != "Sans" { 251 data = append(data, "</span>"...) 252 } 253 if background { 254 data = append(data, "</span>"...) 255 } 256 if foreground { 257 data = append(data, "</span>"...) 258 } 259 } 260 261 data = append(data, "</markup>\")\n"...) 262 } 263 264 r, g, b, _ := base.ForeColor.RGBA() 265 266 data = fmt.Appendf(data, "(font %q)\n"+ 267 "(font-size %d.000000000)\n"+ 268 "(font-size-units pixels)\n"+ 269 "(antialias yes)\n"+ 270 "(base-direction ltr)\n"+ 271 "(color (color-rgb %d.000000 %d.000000 %d.000000))\n"+ 272 "(justify left)\n"+ 273 "(box-mode dynamic)\n"+ 274 "(box-width %d.000000)\n"+ 275 "(box-height %d.000000)\n"+ 276 "(box-unit pixels)\n"+ 277 "(hinting yes)\n"+ 278 "\x00", base.Font, base.Size, r>>8, g>>8, b>>8, dx, dy) 279 280 // write base 281 282 e.WriteUint32(propTextLayerFlags) 283 e.WriteUint32(4) 284 e.WriteUint32(1) 285 286 e.WriteUint32(propParasites) 287 e.WriteUint32(uint32(4 + len(textParasiteName) + 1 + 4 + 4 + len(data))) 288 e.WriteString(textParasiteName) 289 e.WriteUint32(0) // flags 290 e.WriteUint32(uint32(len(data))) 291 e.Write(data) 292 } 293