blob: cc8452eebd3b5b979e0ad16daa53360a75a9bc7f [file] [log] [blame]
Nigel Tao981bf192018-05-23 12:17:55 +10001// Copyright 2018 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Nigel Tao788479d2021-08-22 10:52:51 +100015//go:build ignore
Nigel Tao981bf192018-05-23 12:17:55 +100016// +build ignore
17
18package main
19
20// make-artificial.go makes test data in various formats.
21//
22// See test/data/artificial/*.make.txt for examples.
23//
24// Usage: go run make-artificial.go < foo.make.txt
25
26import (
27 "bytes"
28 "compress/lzw"
29 "fmt"
Nigel Tao226c4762021-08-22 11:05:43 +100030 "io"
Nigel Tao981bf192018-05-23 12:17:55 +100031 "os"
32 "strconv"
33 "strings"
34)
35
36type stateFunc func(line string) (stateFunc, error)
37
38type repeat struct {
39 count uint32
40 remaining string
41}
42
43var (
44 state stateFunc
45 repeats []repeat
46 out []byte
47 formats = map[string]stateFunc{}
48)
49
50func main() {
51 if err := main1(); err != nil {
52 os.Stderr.WriteString(err.Error() + "\n")
53 os.Exit(1)
54 }
55}
56
57func main1() error {
Nigel Tao226c4762021-08-22 11:05:43 +100058 stdin, err := io.ReadAll(os.Stdin)
Nigel Tao981bf192018-05-23 12:17:55 +100059 if err != nil {
60 return err
61 }
62 s := string(stdin)
63 for remaining := ""; len(s) > 0; s, remaining = remaining, "" {
64 if i := strings.IndexByte(s, '\n'); i >= 0 {
65 s, remaining = s[:i], s[i+1:]
66 }
67 s = strings.TrimSpace(s)
68 if s == "" || s[0] == '#' {
69 continue
70 }
71
72 if state == nil {
73 const m = "make "
74 if !strings.HasPrefix(s, m) {
75 return fmt.Errorf(`input must start with "make foo"`)
76 }
77 s = s[len(m):]
78 state = formats[s]
79 if state == nil {
80 return fmt.Errorf("unsupported format %q", s)
81 }
82 continue
83 }
84
85 const rep = "repeat "
86 if strings.HasPrefix(s, rep) {
87 args := s[len(rep):]
88 count, args, ok := parseNum(args)
89 if !ok || count <= 0 || args != "[" {
90 return fmt.Errorf("bad repeat command: %q", s)
91 }
92 repeats = append(repeats, repeat{
93 count: count,
94 remaining: remaining,
95 })
96 continue
97 }
98
99 if s == "]" {
100 if len(repeats) <= 0 {
101 return fmt.Errorf(`unbalanced close-repeat command: "]"`)
102 }
103 i := len(repeats) - 1
104 repeats[i].count--
105 if repeats[i].count == 0 {
106 repeats = repeats[:i]
107 } else {
108 remaining = repeats[i].remaining
109 }
110 continue
111 }
112
113 state, err = state(s)
114 if err != nil {
115 return err
116 }
117 if state == nil {
118 return fmt.Errorf("bad state transition")
119 }
120 }
121
Nigel Tao12347602019-06-16 23:15:05 +1000122 if state == nil {
123 return fmt.Errorf("no 'make' line")
124 } else {
125 state, err = state("")
126 if err != nil {
127 return err
128 }
129 }
130
Nigel Tao981bf192018-05-23 12:17:55 +1000131 _, err = os.Stdout.Write(out)
132 return err
133}
134
135// ----
136
137func appendU16LE(b []byte, u uint16) []byte {
138 return append(b, uint8(u), uint8(u>>8))
139}
140
141func log2(u uint32) (i int32) {
142 for i, pow := uint32(0), uint32(1); i < 32; i, pow = i+1, pow<<1 {
143 if u == pow {
144 return int32(i)
145 }
146 if u < pow {
147 break
148 }
149 }
150 return -1
151}
152
153func parseHex(s string) (num uint32, remaining string, ok bool) {
154 if i := strings.IndexByte(s, ' '); i >= 0 {
155 s, remaining = s[:i], s[i+1:]
156 for len(remaining) > 0 && remaining[0] == ' ' {
157 remaining = remaining[1:]
158 }
159 }
160
161 if len(s) < 2 || s[0] != '0' || s[1] != 'x' {
162 return 0, "", false
163 }
164 s = s[2:]
165
166 u, err := strconv.ParseUint(s, 16, 32)
167 if err != nil {
168 return 0, "", false
169 }
170 return uint32(u), remaining, true
171}
172
173func parseNum(s string) (num uint32, remaining string, ok bool) {
174 if i := strings.IndexByte(s, ' '); i >= 0 {
175 s, remaining = s[:i], s[i+1:]
176 for len(remaining) > 0 && remaining[0] == ' ' {
177 remaining = remaining[1:]
178 }
179 }
180
181 u, err := strconv.ParseUint(s, 10, 32)
182 if err != nil {
183 return 0, "", false
184 }
185 return uint32(u), remaining, true
186}
187
188func reverse(x uint32, n uint32) uint32 {
189 x = uint32(reverse8[0xFF&(x>>0)])<<24 |
190 uint32(reverse8[0xFF&(x>>8)])<<16 |
191 uint32(reverse8[0xFF&(x>>16)])<<8 |
192 uint32(reverse8[0xFF&(x>>24)])<<0
193 return x >> (32 - n)
194}
195
196var reverse8 = [256]byte{
197 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, // 0x00 - 0x07
198 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, // 0x08 - 0x0F
199 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, // 0x10 - 0x17
200 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, // 0x18 - 0x1F
201 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, // 0x20 - 0x27
202 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, // 0x28 - 0x2F
203 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, // 0x30 - 0x37
204 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, // 0x38 - 0x3F
205 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, // 0x40 - 0x47
206 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, // 0x48 - 0x4F
207 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, // 0x50 - 0x57
208 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, // 0x58 - 0x5F
209 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, // 0x60 - 0x67
210 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, // 0x68 - 0x6F
211 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, // 0x70 - 0x77
212 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, // 0x78 - 0x7F
213 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, // 0x80 - 0x87
214 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, // 0x88 - 0x8F
215 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, // 0x90 - 0x97
216 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, // 0x98 - 0x9F
217 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, // 0xA0 - 0xA7
218 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, // 0xA8 - 0xAF
219 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, // 0xB0 - 0xB7
220 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, // 0xB8 - 0xBF
221 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, // 0xC0 - 0xC7
222 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, // 0xC8 - 0xCF
223 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, // 0xD0 - 0xD7
224 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, // 0xD8 - 0xDF
225 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, // 0xE0 - 0xE7
226 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, // 0xE8 - 0xEF
227 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, // 0xF0 - 0xF7
228 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, // 0xF8 - 0xFF
229}
230
231// ----
232
233func init() {
234 formats["deflate"] = stateDeflate
235}
236
Nigel Tao12347602019-06-16 23:15:05 +1000237var deflateCodeOrder = [19]uint32{
238 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
239}
240
241type deflateHuffmanTable map[uint32]string
242
Nigel Tao981bf192018-05-23 12:17:55 +1000243var deflateGlobals struct {
244 bncData []byte
245 stream deflateBitStream
Nigel Tao12347602019-06-16 23:15:05 +1000246
247 // Dynamic Huffman state.
Nigel Taoff7bffc2019-10-13 23:23:48 +1100248 whichHuffman uint32
Nigel Taodc1218f2019-10-14 09:28:28 +1100249 // 0=Unused, 1=CodeLength, 2=Literal/Length, 3=Distance.
Nigel Tao12347602019-06-16 23:15:05 +1000250 huffmans [4]deflateHuffmanTable
Nigel Taoff7bffc2019-10-13 23:23:48 +1100251
252 // DHH (Dynamic Huffman, inside a Huffman table) state.
253 prevLine string
254 etcetera bool
Nigel Tao12347602019-06-16 23:15:05 +1000255}
256
257func deflateGlobalsClearDynamicHuffmanState() {
Nigel Tao12347602019-06-16 23:15:05 +1000258 deflateGlobals.whichHuffman = 0
259 deflateGlobals.huffmans = [4]deflateHuffmanTable{}
Nigel Taoff7bffc2019-10-13 23:23:48 +1100260 deflateGlobals.prevLine = ""
261 deflateGlobals.etcetera = false
262}
263
264func deflateGlobalsCountCodes() (numLCodes uint32, numDCodes uint32, numCLCodeLengths uint32, retErr error) {
265 for k := range deflateGlobals.huffmans[2] {
266 if numLCodes < (k + 1) {
267 numLCodes = (k + 1)
268 }
269 }
270
271 for k := range deflateGlobals.huffmans[3] {
272 if numDCodes < (k + 1) {
273 numDCodes = (k + 1)
274 }
275 }
276
277 for k := range deflateGlobals.huffmans[1] {
278 if (k < 0) || (18 < k) {
279 return 0, 0, 0, fmt.Errorf("bad CodeLength: %d", k)
280 }
281 }
282 for i := len(deflateCodeOrder) - 1; i >= 0; i-- {
283 cl := deflateCodeOrder[i]
284 if _, ok := deflateGlobals.huffmans[1][cl]; ok {
285 numCLCodeLengths = uint32(i + 1)
286 break
287 }
288 }
289 if numCLCodeLengths < 4 {
290 numCLCodeLengths = 4
291 }
292
293 return numLCodes, numDCodes, numCLCodeLengths, nil
Nigel Tao12347602019-06-16 23:15:05 +1000294}
295
296func deflateGlobalsWriteDynamicHuffmanTables() error {
297 g := &deflateGlobals
Nigel Taoff7bffc2019-10-13 23:23:48 +1100298 numLCodes, numDCodes, numCLCodeLengths, err := deflateGlobalsCountCodes()
299 if err != nil {
300 return err
Nigel Tao12347602019-06-16 23:15:05 +1000301 }
Nigel Taoff7bffc2019-10-13 23:23:48 +1100302 if (numLCodes < 257) || (257+31 < numLCodes) {
303 return fmt.Errorf("bad numLCodes: %d", numLCodes)
Nigel Tao12347602019-06-16 23:15:05 +1000304 }
Nigel Taoff7bffc2019-10-13 23:23:48 +1100305 g.stream.writeBits(numLCodes-257, 5)
306 if (numDCodes < 1) || (1+31 < numDCodes) {
307 return fmt.Errorf("bad numDCodes: %d", numDCodes)
Nigel Tao12347602019-06-16 23:15:05 +1000308 }
Nigel Taoff7bffc2019-10-13 23:23:48 +1100309 g.stream.writeBits(numDCodes-1, 5)
310 if (numCLCodeLengths < 4) || (4+15 < numCLCodeLengths) {
311 return fmt.Errorf("bad numCLCodeLengths: %d", numCLCodeLengths)
312 }
313 g.stream.writeBits(numCLCodeLengths-4, 4)
Nigel Tao12347602019-06-16 23:15:05 +1000314
315 // Write the Huffman table for CodeLength.
316 {
Nigel Taoff7bffc2019-10-13 23:23:48 +1100317 for i := uint32(0); i < numCLCodeLengths; i++ {
Nigel Tao12347602019-06-16 23:15:05 +1000318 n := len(g.huffmans[1][deflateCodeOrder[i]])
319 g.stream.writeBits(uint32(n), 3)
320 }
Nigel Taoff7bffc2019-10-13 23:23:48 +1100321 for i := numCLCodeLengths; i < uint32(len(deflateCodeOrder)); i++ {
Nigel Tao12347602019-06-16 23:15:05 +1000322 n := len(g.huffmans[1][deflateCodeOrder[i]])
323 if n > 0 {
Nigel Taoff7bffc2019-10-13 23:23:48 +1100324 return fmt.Errorf("short numCLCodeLengths: %d", numCLCodeLengths)
Nigel Tao12347602019-06-16 23:15:05 +1000325 }
326 }
327 }
328
Nigel Taodc1218f2019-10-14 09:28:28 +1100329 // Write the Huffman tables for Literal/Length and Distance.
Nigel Tao12347602019-06-16 23:15:05 +1000330 {
331 numZeroes := uint32(0)
Nigel Taoff7bffc2019-10-13 23:23:48 +1100332 for i := uint32(0); i < numLCodes+numDCodes; i++ {
333 codeLen := uint32(0)
334 if i < numLCodes {
335 codeLen = uint32(len(g.huffmans[2][i]))
Nigel Tao12347602019-06-16 23:15:05 +1000336 } else {
Nigel Taoff7bffc2019-10-13 23:23:48 +1100337 codeLen = uint32(len(g.huffmans[3][i-numLCodes]))
Nigel Tao12347602019-06-16 23:15:05 +1000338 }
Nigel Taoff7bffc2019-10-13 23:23:48 +1100339 if codeLen == 0 {
Nigel Tao12347602019-06-16 23:15:05 +1000340 numZeroes++
341 continue
342 }
343
344 if err := deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes); err != nil {
345 return err
346 }
347 numZeroes = 0
348
Nigel Taoff7bffc2019-10-13 23:23:48 +1100349 codeLenCode := g.huffmans[1][codeLen]
350 if codeLenCode == "" {
351 return fmt.Errorf("no code for code-length %d", codeLen)
352 }
353 deflateGlobalsWriteDynamicHuffmanBits(g.huffmans[1][codeLen])
Nigel Tao12347602019-06-16 23:15:05 +1000354 }
355 if err := deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes); err != nil {
356 return err
357 }
358 }
359
360 return nil
361}
362
363func deflateGlobalsWriteDynamicHuffmanZeroes(numZeroes uint32) error {
364 g := &deflateGlobals
365 if numZeroes == 0 {
366 return nil
367 }
368
369 if s := g.huffmans[1][18]; s != "" {
370 for numZeroes >= 11 {
371 extra := numZeroes - 11
372 if extra > 127 {
373 extra = 127
374 }
375 deflateGlobalsWriteDynamicHuffmanBits(s)
376 g.stream.writeBits(extra, 7)
377 numZeroes -= 11 + extra
378 }
379 }
380
381 if s := g.huffmans[1][17]; s != "" {
382 for numZeroes >= 3 {
383 extra := numZeroes - 3
384 if extra > 7 {
385 extra = 7
386 }
387 deflateGlobalsWriteDynamicHuffmanBits(s)
388 g.stream.writeBits(extra, 3)
389 numZeroes -= 3 + extra
390 }
391 }
392
393 if s := g.huffmans[1][0]; s != "" {
394 for ; numZeroes > 0; numZeroes-- {
395 deflateGlobalsWriteDynamicHuffmanBits(s)
396 }
397 }
398
399 if numZeroes > 0 {
400 return fmt.Errorf("could not write a run of zero-valued code lengths")
401 }
402 return nil
403}
404
405func deflateGlobalsWriteDynamicHuffmanBits(s string) {
406 g := &deflateGlobals
407 for i := 0; i < len(s); i++ {
408 g.stream.writeBits(uint32(s[i]&1), 1)
409 }
410}
411
412func parseDeflateWhichHuffman(s string) (num uint32, remaining string, ok bool) {
413 if i := strings.IndexByte(s, ' '); i >= 0 {
414 s, remaining = s[:i], s[i+1:]
415 for len(remaining) > 0 && remaining[0] == ' ' {
416 remaining = remaining[1:]
417 }
418 }
419
420 switch s {
421 case "CodeLength":
422 return 1, remaining, true
Nigel Taodc1218f2019-10-14 09:28:28 +1100423 case "Literal/Length":
Nigel Tao12347602019-06-16 23:15:05 +1000424 return 2, remaining, true
425 case "Distance":
426 return 3, remaining, true
427 }
428 return 0, "", false
Nigel Tao981bf192018-05-23 12:17:55 +1000429}
430
431func stateDeflate(line string) (stateFunc, error) {
432 g := &deflateGlobals
433 const (
Nigel Tao0cc17982019-05-19 23:43:20 +1000434 cmdB = "bytes "
Nigel Tao12347602019-06-16 23:15:05 +1000435 cmdBDH = "blockDynamicHuffman "
Nigel Tao981bf192018-05-23 12:17:55 +1000436 cmdBFH = "blockFixedHuffman "
Nigel Tao12347602019-06-16 23:15:05 +1000437 cmdBNC = "blockNoCompression "
Nigel Tao981bf192018-05-23 12:17:55 +1000438 )
439 bits := uint32(0)
440 s := ""
441
442 retState := stateFunc(nil)
443 switch {
Nigel Tao12347602019-06-16 23:15:05 +1000444 case line == "":
445 g.stream.flush()
446 return stateDeflate, nil
447
Nigel Tao0cc17982019-05-19 23:43:20 +1000448 case strings.HasPrefix(line, cmdB):
449 s := line[len(cmdB):]
450 for s != "" {
451 x, ok := uint32(0), false
452 x, s, ok = parseHex(s)
453 if !ok {
454 return nil, fmt.Errorf("bad stateDeflate command: %q", line)
455 }
456 out = append(out, uint8(x))
457 }
458 return stateDeflate, nil
459
Nigel Tao981bf192018-05-23 12:17:55 +1000460 case strings.HasPrefix(line, cmdBNC):
461 s = line[len(cmdBNC):]
462 retState = stateDeflateNoCompression
463 bits |= 0 << 1
464 case strings.HasPrefix(line, cmdBFH):
465 s = line[len(cmdBFH):]
466 retState = stateDeflateFixedHuffman
467 bits |= 1 << 1
Nigel Tao12347602019-06-16 23:15:05 +1000468 case strings.HasPrefix(line, cmdBDH):
469 s = line[len(cmdBDH):]
470 retState = stateDeflateDynamicHuffman
471 bits |= 2 << 1
Nigel Tao981bf192018-05-23 12:17:55 +1000472 default:
473 return nil, fmt.Errorf("bad stateDeflate command: %q", line)
474 }
475
476 switch s {
477 case "(final) {":
478 bits |= 1
479 case "(nonFinal) {":
480 // No-op.
481 default:
482 return nil, fmt.Errorf("bad stateDeflate command: %q", line)
483 }
484
485 g.stream.writeBits(bits, 3)
486 return retState, nil
487}
488
489func stateDeflateNoCompression(line string) (stateFunc, error) {
490 g := &deflateGlobals
491 if line == "}" {
492 if len(g.bncData) > 0xFFFF {
493 return nil, fmt.Errorf("bncData is too long")
494 }
495 n := uint32(len(g.bncData))
496 g.stream.flush()
497 g.stream.writeBits(n, 16)
498 g.stream.writeBits(0xFFFF-n, 16)
499 g.stream.writeBytes(g.bncData)
500 g.bncData = g.bncData[:0]
501 return stateDeflate, nil
502 }
503
504 if lit, ok := deflateParseLiteral(line); ok {
505 g.bncData = append(g.bncData, lit...)
506 return stateDeflateNoCompression, nil
507 }
508
509 return nil, fmt.Errorf("bad blockNoCompression command: %q", line)
510}
511
512func stateDeflateFixedHuffman(line string) (stateFunc, error) {
513 g := &deflateGlobals
514 if line == "}" {
Nigel Tao981bf192018-05-23 12:17:55 +1000515 return stateDeflate, nil
516 }
517
518 if line == "endOfBlock" {
519 g.stream.writeBits(0, 7)
520 return stateDeflateFixedHuffman, nil
521 }
522
523 if lit, ok := deflateParseLiteral(line); ok {
Nigel Tao981bf192018-05-23 12:17:55 +1000524 for i := 0; i < len(lit); i++ {
Nigel Tao12347602019-06-16 23:15:05 +1000525 g.stream.writeFixedHuffmanLCode(uint32(lit[i]))
Nigel Tao981bf192018-05-23 12:17:55 +1000526 }
527 return stateDeflateFixedHuffman, nil
528 }
529
Nigel Tao11a68192019-02-02 13:04:09 +1100530 if line == "len 3 distCode 31" {
531 lCode, lExtra, lNExtra := deflateEncodeLength(3)
Nigel Tao12347602019-06-16 23:15:05 +1000532 g.stream.writeFixedHuffmanLCode(lCode)
Nigel Tao11a68192019-02-02 13:04:09 +1100533 g.stream.writeBits(lExtra, lNExtra)
534 dCode, dExtra, dNExtra := uint32(31), uint32(0), uint32(0)
535 g.stream.writeBits(reverse(dCode, 5), 5)
536 g.stream.writeBits(dExtra, dNExtra)
537 return stateDeflateFixedHuffman, nil
538 }
539
Nigel Tao981bf192018-05-23 12:17:55 +1000540 if l, d, ok := deflateParseLenDist(line); ok {
541 lCode, lExtra, lNExtra := deflateEncodeLength(l)
Nigel Tao12347602019-06-16 23:15:05 +1000542 g.stream.writeFixedHuffmanLCode(lCode)
Nigel Tao981bf192018-05-23 12:17:55 +1000543 g.stream.writeBits(lExtra, lNExtra)
544 dCode, dExtra, dNExtra := deflateEncodeDistance(d)
545 g.stream.writeBits(reverse(dCode, 5), 5)
546 g.stream.writeBits(dExtra, dNExtra)
547 return stateDeflateFixedHuffman, nil
548 }
549
550 return nil, fmt.Errorf("bad stateDeflateFixedHuffman command: %q", line)
551}
552
Nigel Tao12347602019-06-16 23:15:05 +1000553func stateDeflateDynamicHuffman(line string) (stateFunc, error) {
554 g := &deflateGlobals
555 const (
Nigel Taoff7bffc2019-10-13 23:23:48 +1100556 cmdH = "huffman "
Nigel Tao12347602019-06-16 23:15:05 +1000557 )
558 switch {
559 case line == "}":
560 deflateGlobalsClearDynamicHuffmanState()
561 return stateDeflate, nil
562
563 case strings.HasPrefix(line, cmdH):
564 s := line[len(cmdH):]
565 n, s, ok := parseDeflateWhichHuffman(s)
566 if !ok {
567 break
568 }
569 g.whichHuffman = n
570 return stateDeflateDynamicHuffmanHuffman, nil
Nigel Tao12347602019-06-16 23:15:05 +1000571 }
572
573 if lit, ok := deflateParseLiteral(line); ok {
574 for i := 0; i < len(lit); i++ {
575 s := g.huffmans[2][uint32(lit[i])]
576 if s == "" {
577 return nil, fmt.Errorf("no code for literal %q (%d)", lit[i:i+1], lit[i])
578 }
579 deflateGlobalsWriteDynamicHuffmanBits(s)
580 }
581 return stateDeflateDynamicHuffman, nil
Nigel Taoff7bffc2019-10-13 23:23:48 +1100582
583 } else if l, d, ok := deflateParseLenDist(line); ok {
584 if (l != 3) || (d != 2) {
585 return nil, fmt.Errorf("TODO: support len/dist pairs for dynamic Huffman blocks")
586 }
587 // len 3 is code 257, with no extra bits.
588 if s := g.huffmans[2][257]; s != "" {
589 deflateGlobalsWriteDynamicHuffmanBits(s)
590 } else {
591 return nil, fmt.Errorf("no code for literal/length symbol 257")
592 }
593 // dist 2 is code 1, with no extra bits.
594 if s := g.huffmans[3][1]; s != "" {
595 deflateGlobalsWriteDynamicHuffmanBits(s)
596 } else {
597 return nil, fmt.Errorf("no code for distance symbol 2")
598 }
599 return stateDeflateDynamicHuffman, nil
600
Nigel Tao12347602019-06-16 23:15:05 +1000601 } else if line == "endOfBlock" {
602 s := g.huffmans[2][256]
603 if s == "" {
604 return nil, fmt.Errorf("no code for end-of-block (256)")
605 }
606 deflateGlobalsWriteDynamicHuffmanBits(s)
607 return stateDeflateDynamicHuffman, nil
608 }
609
610 return nil, fmt.Errorf("bad stateDeflateDynamicHuffman command: %q", line)
611}
612
613func stateDeflateDynamicHuffmanHuffman(line string) (stateFunc, error) {
614 g := &deflateGlobals
615outer:
616 switch {
617 case line == "}":
618 g.whichHuffman = 0
Nigel Taoff7bffc2019-10-13 23:23:48 +1100619 g.prevLine = ""
620 g.etcetera = false
Nigel Tao12347602019-06-16 23:15:05 +1000621
622 // If we have all three Huffman tables, write them.
623 for i := 1; ; i++ {
624 if i == 4 {
625 if err := deflateGlobalsWriteDynamicHuffmanTables(); err != nil {
626 return nil, err
627 }
628 break
629 }
630 if g.huffmans[i] == nil {
631 break
632 }
633 }
634
635 return stateDeflateDynamicHuffman, nil
636
Nigel Taoff7bffc2019-10-13 23:23:48 +1100637 case line == "etcetera":
638 g.etcetera = true
639 return stateDeflateDynamicHuffmanHuffman, nil
640
Nigel Tao12347602019-06-16 23:15:05 +1000641 default:
642 s := line
643 n, s, ok := parseNum(s)
644 if !ok || s == "" {
645 break
646 }
647 for i := 0; i < len(s); i++ {
648 if c := s[i]; c != '0' && c != '1' {
649 break outer
650 }
651 }
Nigel Taoff7bffc2019-10-13 23:23:48 +1100652 if (len(s) < 1) || (15 < len(s)) {
653 return nil, fmt.Errorf("%q code length, %d, is out of range", s, len(s))
654 }
655
656 if g.etcetera {
657 g.etcetera = false
658 n0, s0, ok := parseNum(g.prevLine)
659 if !ok {
660 return nil, fmt.Errorf("bad etcetera command")
661 }
662 if err := stateDeflateDHHEtcetera(n0, s0, n, s); err != nil {
663 return nil, err
664 }
665 }
666
Nigel Tao12347602019-06-16 23:15:05 +1000667 if g.huffmans[g.whichHuffman] == nil {
668 g.huffmans[g.whichHuffman] = deflateHuffmanTable{}
669 }
670 g.huffmans[g.whichHuffman][n] = s
Nigel Taoff7bffc2019-10-13 23:23:48 +1100671 g.prevLine = line
Nigel Tao12347602019-06-16 23:15:05 +1000672 return stateDeflateDynamicHuffmanHuffman, nil
673 }
674
675 return nil, fmt.Errorf("bad stateDeflateDynamicHuffmanHuffman command: %q", line)
676}
677
Nigel Taoff7bffc2019-10-13 23:23:48 +1100678// stateDeflateDHHEtcetera expands the "etcetera" line in:
679//
680// 0 0000
681// 1 0001
682// etcetera
683// 7 0111
684//
685// to produce the implicit lines:
686//
687// 0 0000
688// 1 0001
689// 2 0010
690// 3 0011
691// 4 0100
692// 5 0101
693// 6 0110
694// 7 0111
695func stateDeflateDHHEtcetera(n0 uint32, s0 string, n1 uint32, s1 string) error {
696 b := []byte(s0)
697 if !incrementBitstring(b) {
698 return fmt.Errorf("etcetera: could not increment bitstring")
699 }
700 for n := n0 + 1; n < n1; n++ {
701 line := fmt.Sprintf("%d %s", n, b)
702 if _, err := stateDeflateDynamicHuffmanHuffman(line); err != nil {
703 return err
704 }
705 if !incrementBitstring(b) {
706 return fmt.Errorf("etcetera: could not increment bitstring")
707 }
708 }
709 if string(b) != s1 {
710 return fmt.Errorf("etcetera: final bitstring: got %q, want %q", b, s1)
711 }
712 return nil
713}
714
715func incrementBitstring(b []byte) (ok bool) {
716 for i := len(b) - 1; i >= 0; i-- {
717 switch b[i] {
718 case '0':
719 b[i] = '1'
720 return true
721 case '1':
722 b[i] = '0'
723 default:
724 return false
725 }
726 }
727 return false
728}
729
Nigel Tao981bf192018-05-23 12:17:55 +1000730type deflateBitStream struct {
731 bits uint32
732 nBits uint32 // Always within [0, 7].
733}
734
735// writeBits writes the low n bits of b to z.
736func (z *deflateBitStream) writeBits(b uint32, n uint32) {
737 if n > 24 {
738 panic("writeBits: n is too large")
739 }
740 z.bits |= b << z.nBits
741 z.nBits += n
742 for z.nBits >= 8 {
743 out = append(out, uint8(z.bits))
744 z.bits >>= 8
745 z.nBits -= 8
746 }
747}
748
749func (z *deflateBitStream) writeBytes(b []byte) {
750 z.flush()
751 out = append(out, b...)
752}
753
Nigel Tao12347602019-06-16 23:15:05 +1000754func (z *deflateBitStream) writeFixedHuffmanLCode(lCode uint32) {
Nigel Tao981bf192018-05-23 12:17:55 +1000755 switch {
756 case lCode < 144: // 0b._0011_0000 through 0b._1011_1111
757 lCode += 0x030
758 z.writeBits(reverse(lCode, 8), 8)
759 case lCode < 256: // 0b1_1001_0000 through 0b1_1111_1111
760 lCode += 0x190 - 144
761 z.writeBits(reverse(lCode, 9), 9)
762 case lCode < 280: // 0b._.000_0000 through 0b._.001_0111
763 lCode -= 256 - 0x000
764 z.writeBits(reverse(lCode, 7), 7)
765 default: // 0b._1100_0000 through 0b._1100_0111
766 lCode -= 280 - 0x0C0
767 z.writeBits(reverse(lCode, 8), 8)
768 }
769}
770
771func (z *deflateBitStream) flush() {
772 if z.nBits > 0 {
773 out = append(out, uint8(z.bits))
774 z.bits = 0
775 z.nBits = 0
776 }
777}
778
779func deflateEncodeLength(l uint32) (code uint32, extra uint32, nExtra uint32) {
780 switch {
781 case l < 3:
782 // No-op.
783 case l < 11:
784 l -= 3
785 return (l >> 0) + 257, l & 0x0000, 0
786 case l < 19:
787 l -= 11
788 return (l >> 1) + 265, l & 0x0001, 1
789 case l < 35:
790 l -= 19
791 return (l >> 2) + 269, l & 0x0003, 2
792 case l < 67:
793 l -= 35
794 return (l >> 3) + 273, l & 0x0007, 3
795 case l < 131:
796 l -= 67
797 return (l >> 4) + 277, l & 0x000F, 4
798 case l < 258:
799 l -= 131
800 return (l >> 5) + 281, l & 0x001F, 5
801 case l == 258:
802 return 285, 0, 0
803 }
804 panic(fmt.Sprintf("deflateEncodeLength: l=%d", l))
805}
806
807func deflateEncodeDistance(d uint32) (code uint32, extra uint32, nExtra uint32) {
808 switch {
809 case d < 1:
810 // No-op.
811 case d < 5:
812 d -= 1
813 return (d >> 0) + 0, d & 0x0000, 0
814 case d < 9:
815 d -= 5
816 return (d >> 1) + 4, d & 0x0001, 1
817 case d < 17:
818 d -= 9
819 return (d >> 2) + 6, d & 0x0003, 2
820 case d < 33:
821 d -= 17
822 return (d >> 3) + 8, d & 0x0007, 3
823 case d < 65:
824 d -= 33
825 return (d >> 4) + 10, d & 0x000F, 4
826 case d < 129:
827 d -= 65
828 return (d >> 5) + 12, d & 0x001F, 5
829 case d < 257:
830 d -= 129
831 return (d >> 6) + 14, d & 0x003F, 6
832 case d < 513:
833 d -= 257
834 return (d >> 7) + 16, d & 0x007F, 7
835 case d < 1025:
836 d -= 513
837 return (d >> 8) + 18, d & 0x00FF, 8
838 case d < 2049:
839 d -= 1025
840 return (d >> 9) + 20, d & 0x01FF, 9
841 case d < 4097:
842 d -= 2049
843 return (d >> 10) + 22, d & 0x03FF, 10
844 case d < 8193:
845 d -= 4097
846 return (d >> 11) + 24, d & 0x07FF, 11
847 case d < 16385:
848 d -= 8193
849 return (d >> 12) + 26, d & 0x0FFF, 12
850 case d < 32769:
851 d -= 16385
852 return (d >> 13) + 28, d & 0x1FFF, 13
853 }
854 panic(fmt.Sprintf("deflateEncodeDistance: d=%d", d))
855}
856
857func deflateParseLiteral(s string) (lit string, ok bool) {
Nigel Tao12347602019-06-16 23:15:05 +1000858 // TODO: support "\xAB" escape codes in the script?
Nigel Tao981bf192018-05-23 12:17:55 +1000859 const (
860 prefix = `literal "`
861 suffix = `"`
862 )
863 if strings.HasPrefix(s, prefix) {
864 s = s[len(prefix):]
865 if strings.HasSuffix(s, suffix) {
866 return s[:len(s)-len(suffix)], true
867 }
868 }
869 return "", false
870}
871
872func deflateParseLenDist(line string) (l uint32, d uint32, ok bool) {
873 const (
874 lStr = "len "
875 dStr = "dist "
876 )
877 s := line
878 if strings.HasPrefix(s, lStr) {
879 s = s[len(lStr):]
880 if l, s, ok := parseNum(s); ok && 3 <= l && l <= 258 {
881 if strings.HasPrefix(s, dStr) {
882 s = s[len(dStr):]
883 if d, s, ok := parseNum(s); ok && 1 <= d && d <= 32768 && s == "" {
884 return l, d, true
885 }
886 }
887 }
888 }
889 return 0, 0, false
890}
891
892// ----
893
894func init() {
895 formats["gif"] = stateGif
896}
897
898var gifGlobals struct {
Nigel Tao31a2bc92019-05-18 17:32:24 +1000899 imageWidth uint32
900 imageHeight uint32
901 imageBackgroundColorIndex uint32
Nigel Tao981bf192018-05-23 12:17:55 +1000902
Nigel Taoefb18032019-10-05 11:40:24 +1000903 frameInterlaced bool
904 frameLeft uint32
905 frameTop uint32
906 frameWidth uint32
907 frameHeight uint32
Nigel Tao981bf192018-05-23 12:17:55 +1000908
909 globalPalette [][4]uint8
Nigel Tao746681e2019-05-04 13:49:22 +1000910 localPalette [][4]uint8
Nigel Tao981bf192018-05-23 12:17:55 +1000911}
912
913func stateGif(line string) (stateFunc, error) {
914 const (
Nigel Tao6700c722019-05-26 09:41:55 +1000915 cmdB = "bytes "
916 cmdGC = "graphicControl "
917 cmdL = "lzw "
918 cmdLC = "loopCount "
Nigel Tao981bf192018-05-23 12:17:55 +1000919 )
920outer:
921 switch {
Nigel Tao12347602019-06-16 23:15:05 +1000922 case line == "":
923 return stateGif, nil
924
Nigel Tao981bf192018-05-23 12:17:55 +1000925 case line == "frame {":
Nigel Taoefb18032019-10-05 11:40:24 +1000926 gifGlobals.frameInterlaced = false
927 gifGlobals.frameLeft = 0
928 gifGlobals.frameTop = 0
929 gifGlobals.frameWidth = 0
930 gifGlobals.frameHeight = 0
Nigel Tao746681e2019-05-04 13:49:22 +1000931 gifGlobals.localPalette = nil
Nigel Tao981bf192018-05-23 12:17:55 +1000932 out = append(out, 0x2C)
933 return stateGifFrame, nil
934
935 case line == "header":
936 out = append(out, "GIF89a"...)
937 return stateGif, nil
938
939 case line == "image {":
940 return stateGifImage, nil
941
942 case line == "trailer":
943 out = append(out, 0x3B)
944 return stateGif, nil
945
Nigel Tao21f28902019-04-20 16:33:11 +1000946 case strings.HasPrefix(line, cmdB):
947 s := line[len(cmdB):]
948 for s != "" {
949 x, ok := uint32(0), false
950 x, s, ok = parseHex(s)
951 if !ok {
952 break outer
953 }
954 out = append(out, uint8(x))
955 }
956 return stateGif, nil
957
Nigel Tao6700c722019-05-26 09:41:55 +1000958 case strings.HasPrefix(line, cmdGC):
959 s := line[len(cmdGC):]
960
961 flags := uint8(0)
Nigel Tao344123b2020-09-25 23:00:01 +1000962 duration := uint32(0)
Nigel Tao6c2fb9a2020-09-27 00:09:25 +1000963 transparentIndex := uint8(0)
Nigel Tao344123b2020-09-25 23:00:01 +1000964 for s != "" {
965 term := ""
966 if i := strings.IndexByte(s, ' '); i >= 0 {
967 term, s = s[:i], s[i+1:]
968 } else {
969 term, s = s, ""
970 }
971
972 const (
973 ms = "ms"
974 trans = "transparentIndex="
975 )
976 switch {
977 case term == "animationDisposalNone":
Nigel Tao6700c722019-05-26 09:41:55 +1000978 flags |= 0x00
Nigel Tao344123b2020-09-25 23:00:01 +1000979 case term == "animationDisposalRestoreBackground":
Nigel Tao6700c722019-05-26 09:41:55 +1000980 flags |= 0x08
Nigel Tao344123b2020-09-25 23:00:01 +1000981 case term == "animationDisposalRestorePrevious":
Nigel Tao6700c722019-05-26 09:41:55 +1000982 flags |= 0x0C
Nigel Tao344123b2020-09-25 23:00:01 +1000983 case strings.HasPrefix(term, trans):
Nigel Tao6c2fb9a2020-09-27 00:09:25 +1000984 num, err := strconv.ParseUint(term[len(trans):], 0, 8)
985 if err != nil {
Nigel Tao344123b2020-09-25 23:00:01 +1000986 break outer
987 }
988 flags |= 0x01
Nigel Tao6c2fb9a2020-09-27 00:09:25 +1000989 transparentIndex = uint8(num)
Nigel Tao344123b2020-09-25 23:00:01 +1000990 case strings.HasSuffix(term, ms):
991 num, remaining, ok := parseNum(term[:len(term)-len(ms)])
992 if !ok || remaining != "" {
993 break outer
994 }
995 duration = num / 10 // GIF's unit of time is 10ms.
Nigel Tao6700c722019-05-26 09:41:55 +1000996 default:
997 break outer
998 }
Nigel Tao6700c722019-05-26 09:41:55 +1000999 }
1000
Nigel Taob1ff27f2019-05-12 22:00:44 +10001001 out = append(out,
Nigel Tao6700c722019-05-26 09:41:55 +10001002 0x21, 0xF9, 0x04,
1003 flags,
Nigel Taob1ff27f2019-05-12 22:00:44 +10001004 uint8(duration>>0),
1005 uint8(duration>>8),
Nigel Tao6c2fb9a2020-09-27 00:09:25 +10001006 transparentIndex,
Nigel Tao6700c722019-05-26 09:41:55 +10001007 0x00,
Nigel Taob1ff27f2019-05-12 22:00:44 +10001008 )
1009 return stateGif, nil
1010
Nigel Tao981bf192018-05-23 12:17:55 +10001011 case strings.HasPrefix(line, cmdL):
1012 s := line[len(cmdL):]
1013 litWidth, s, ok := parseNum(s)
1014 if !ok || litWidth < 2 || 8 < litWidth {
1015 break
1016 }
1017 out = append(out, uint8(litWidth))
1018
1019 uncompressed := []byte(nil)
1020 for s != "" {
1021 x := uint32(0)
1022 x, s, ok = parseHex(s)
1023 if !ok {
1024 break outer
1025 }
1026 uncompressed = append(uncompressed, uint8(x))
1027 }
1028
1029 buf := bytes.NewBuffer(nil)
1030 w := lzw.NewWriter(buf, lzw.LSB, int(litWidth))
1031 if _, err := w.Write(uncompressed); err != nil {
1032 return nil, err
1033 }
1034 if err := w.Close(); err != nil {
1035 return nil, err
1036 }
1037 compressed := buf.Bytes()
1038
1039 for len(compressed) > 0 {
1040 if len(compressed) <= 0xFF {
1041 out = append(out, uint8(len(compressed)))
1042 out = append(out, compressed...)
1043 compressed = nil
1044 } else {
1045 out = append(out, 0xFF)
1046 out = append(out, compressed[:0xFF]...)
1047 compressed = compressed[0xFF:]
1048 }
1049 }
1050 out = append(out, 0x00)
1051 return stateGif, nil
Nigel Tao7be9c392018-10-13 17:00:45 +11001052
1053 case strings.HasPrefix(line, cmdLC):
1054 s := line[len(cmdLC):]
1055 loopCount, _, ok := parseNum(s)
1056 if !ok || 0xFFFF < loopCount {
1057 break
1058 }
1059 out = append(out,
1060 0x21, // Extension Introducer.
1061 0xFF, // Application Extension Label.
1062 0x0B, // Block Size.
1063 )
1064 out = append(out, "NETSCAPE2.0"...)
1065 out = append(out,
1066 0x03, // Block Size.
1067 0x01, // Magic Number.
1068 byte(loopCount),
1069 byte(loopCount>>8),
1070 0x00, // Block Terminator.
1071 )
1072 return stateGif, nil
Nigel Tao981bf192018-05-23 12:17:55 +10001073 }
1074
1075 return nil, fmt.Errorf("bad stateGif command: %q", line)
1076}
1077
1078func stateGifImage(line string) (stateFunc, error) {
1079 g := &gifGlobals
1080 if line == "}" {
1081 out = appendU16LE(out, uint16(g.imageWidth))
1082 out = appendU16LE(out, uint16(g.imageHeight))
1083 if g.globalPalette == nil {
1084 out = append(out, 0x00)
1085 } else if n := log2(uint32(len(g.globalPalette))); n < 2 || 8 < n {
1086 return nil, fmt.Errorf("bad len(g.globalPalette): %d", len(g.globalPalette))
1087 } else {
1088 out = append(out, 0x80|uint8(n-1))
1089 }
Nigel Tao31a2bc92019-05-18 17:32:24 +10001090 out = append(out, uint8(g.imageBackgroundColorIndex))
Nigel Tao981bf192018-05-23 12:17:55 +10001091 out = append(out, 0x00)
1092 for _, x := range g.globalPalette {
1093 out = append(out, x[0], x[1], x[2])
1094 }
1095 return stateGif, nil
1096 }
1097
1098 const (
Nigel Tao31a2bc92019-05-18 17:32:24 +10001099 cmdBCI = "backgroundColorIndex "
Nigel Tao981bf192018-05-23 12:17:55 +10001100 cmdIWH = "imageWidthHeight "
1101 cmdP = "palette {"
1102 )
1103 switch {
Nigel Tao31a2bc92019-05-18 17:32:24 +10001104 case strings.HasPrefix(line, cmdBCI):
1105 s := line[len(cmdBCI):]
1106 if i, _, ok := parseNum(s); ok {
1107 g.imageBackgroundColorIndex = i
1108 }
1109 return stateGifImage, nil
1110
Nigel Tao981bf192018-05-23 12:17:55 +10001111 case strings.HasPrefix(line, cmdIWH):
1112 s := line[len(cmdIWH):]
1113 if w, s, ok := parseNum(s); ok {
1114 if h, _, ok := parseNum(s); ok {
1115 g.imageWidth = w
1116 g.imageHeight = h
1117 return stateGifImage, nil
1118 }
1119 }
1120
1121 case strings.HasPrefix(line, cmdP):
1122 return stateGifImagePalette, nil
1123 }
1124
1125 return nil, fmt.Errorf("bad stateGifImage command: %q", line)
1126}
1127
1128func stateGifFrame(line string) (stateFunc, error) {
1129 g := &gifGlobals
1130 if line == "}" {
1131 out = appendU16LE(out, uint16(g.frameLeft))
1132 out = appendU16LE(out, uint16(g.frameTop))
1133 out = appendU16LE(out, uint16(g.frameWidth))
1134 out = appendU16LE(out, uint16(g.frameHeight))
Nigel Taoefb18032019-10-05 11:40:24 +10001135 flags := byte(0x00)
1136 if g.frameInterlaced {
1137 flags |= 0x40
1138 }
Nigel Tao746681e2019-05-04 13:49:22 +10001139 if g.localPalette == nil {
Nigel Taoefb18032019-10-05 11:40:24 +10001140 out = append(out, flags)
Nigel Tao746681e2019-05-04 13:49:22 +10001141 } else if n := log2(uint32(len(g.localPalette))); n < 2 || 8 < n {
1142 return nil, fmt.Errorf("bad len(g.localPalette): %d", len(g.localPalette))
1143 } else {
Nigel Taoefb18032019-10-05 11:40:24 +10001144 out = append(out, flags|0x80|uint8(n-1))
Nigel Tao746681e2019-05-04 13:49:22 +10001145 }
1146 for _, x := range g.localPalette {
1147 out = append(out, x[0], x[1], x[2])
1148 }
Nigel Tao981bf192018-05-23 12:17:55 +10001149 return stateGif, nil
1150 }
1151
1152 const (
1153 cmdFLTWH = "frameLeftTopWidthHeight "
Nigel Tao746681e2019-05-04 13:49:22 +10001154 cmdP = "palette {"
Nigel Tao981bf192018-05-23 12:17:55 +10001155 )
1156 switch {
Nigel Taoefb18032019-10-05 11:40:24 +10001157 case line == "interlaced":
1158 g.frameInterlaced = true
1159 return stateGifFrame, nil
1160
Nigel Tao981bf192018-05-23 12:17:55 +10001161 case strings.HasPrefix(line, cmdFLTWH):
1162 s := line[len(cmdFLTWH):]
1163 if l, s, ok := parseNum(s); ok {
1164 if t, s, ok := parseNum(s); ok {
1165 if w, s, ok := parseNum(s); ok {
1166 if h, _, ok := parseNum(s); ok {
1167 g.frameLeft = l
1168 g.frameTop = t
1169 g.frameWidth = w
1170 g.frameHeight = h
1171 return stateGifFrame, nil
1172 }
1173 }
1174 }
1175 }
Nigel Tao746681e2019-05-04 13:49:22 +10001176
1177 case strings.HasPrefix(line, cmdP):
1178 return stateGifFramePalette, nil
Nigel Tao981bf192018-05-23 12:17:55 +10001179 }
1180
1181 return nil, fmt.Errorf("bad stateGifFrame command: %q", line)
1182}
1183
1184func stateGifImagePalette(line string) (stateFunc, error) {
1185 g := &gifGlobals
1186 if line == "}" {
1187 return stateGifImage, nil
1188 }
1189
1190 s := line
1191 if rgb0, s, ok := parseHex(s); ok {
1192 if rgb1, s, ok := parseHex(s); ok {
1193 if rgb2, _, ok := parseHex(s); ok {
1194 g.globalPalette = append(g.globalPalette,
1195 [4]uint8{uint8(rgb0), uint8(rgb1), uint8(rgb2), 0xFF})
1196 return stateGifImagePalette, nil
1197 }
1198 }
1199 }
1200
1201 return nil, fmt.Errorf("bad stateGifImagePalette command: %q", line)
1202}
Nigel Tao746681e2019-05-04 13:49:22 +10001203
1204func stateGifFramePalette(line string) (stateFunc, error) {
1205 g := &gifGlobals
1206 if line == "}" {
1207 return stateGifFrame, nil
1208 }
1209
1210 s := line
1211 if rgb0, s, ok := parseHex(s); ok {
1212 if rgb1, s, ok := parseHex(s); ok {
1213 if rgb2, _, ok := parseHex(s); ok {
1214 g.localPalette = append(g.localPalette,
1215 [4]uint8{uint8(rgb0), uint8(rgb1), uint8(rgb2), 0xFF})
1216 return stateGifFramePalette, nil
1217 }
1218 }
1219 }
1220
1221 return nil, fmt.Errorf("bad stateGifFramePalette command: %q", line)
1222}