Merge branch 'master' of https://github.com/golang-china/gopl-zh
3
Makefile
@ -30,6 +30,9 @@ review:
|
|||||||
gitbook build
|
gitbook build
|
||||||
go run zh2tw.go . .md$$ zh2tw
|
go run zh2tw.go . .md$$ zh2tw
|
||||||
|
|
||||||
|
qrcode:
|
||||||
|
go run mkqrcode.go
|
||||||
|
|
||||||
fixlink:
|
fixlink:
|
||||||
go run fixlinks.go . .md$$
|
go run fixlinks.go . .md$$
|
||||||
|
|
||||||
|
@ -171,3 +171,4 @@
|
|||||||
* [附録A:原文勘誤](appendix/appendix-a-errata.md)
|
* [附録A:原文勘誤](appendix/appendix-a-errata.md)
|
||||||
* [附録B:作者譯者](appendix/appendix-b-author.md)
|
* [附録B:作者譯者](appendix/appendix-b-author.md)
|
||||||
* [附録C:譯文授權](appendix/appendix-c-cpoyright.md)
|
* [附録C:譯文授權](appendix/appendix-c-cpoyright.md)
|
||||||
|
* [附録D:其它語言](appendix/appendix-d-translations.md)
|
||||||
|
@ -126,3 +126,4 @@
|
|||||||
* [附録A:原文勘誤](appendix/appendix-a-errata.md)
|
* [附録A:原文勘誤](appendix/appendix-a-errata.md)
|
||||||
* [附録B:作者譯者](appendix/appendix-b-author.md)
|
* [附録B:作者譯者](appendix/appendix-b-author.md)
|
||||||
* [附録C:譯文授權](appendix/appendix-c-cpoyright.md)
|
* [附録C:譯文授權](appendix/appendix-c-cpoyright.md)
|
||||||
|
* [附録D:其它語言](appendix/appendix-d-translations.md)
|
||||||
|
@ -6,15 +6,17 @@
|
|||||||
|
|
||||||
`rand.Seed(time.Now().UTC().UnixNano())`
|
`rand.Seed(time.Now().UTC().UnixNano())`
|
||||||
|
|
||||||
**p.15, ¶2:** For "inner loop", read "outer loop". (Thanks to Ralph Corderoy, 2015-11-28.)
|
**p.15, ¶2:** For "inner loop", read "outer loop". (Thanks to Ralph Corderoy, 2015-11-28. Corrected in the third printing.)
|
||||||
|
|
||||||
**p.19, ¶2:** For "Go's libraries makes", read "Go's library makes". (Thanks to Victor Farazdagi, 2015-11-30.)
|
**p.19, ¶2:** For "Go's libraries makes", read "Go's library makes". (Thanks to Victor Farazdagi, 2015-11-30. Corrected in the third printing.)
|
||||||
|
|
||||||
**p.40, ¶4:** For "value of the underlying type", read "value of an unnamed type with the same underlying type". (Thanks to Carlos Romero Brox, 2015-12-19.)
|
**p.40, ¶4:** For "value of the underlying type", read "value of an unnamed type with the same underlying type". (Thanks to Carlos Romero Brox, 2015-12-19.)
|
||||||
|
|
||||||
**p.40, ¶1:** The paragraph should end with a period, not a comma. (Thanks to Victor Farazdagi, 2015-11-30.)
|
**p.40, ¶1:** The paragraph should end with a period, not a comma. (Thanks to Victor Farazdagi, 2015-11-30. Corrected in the third printing.)
|
||||||
|
|
||||||
**p.43, ¶3:** Import declarations are explained in §10.4, not §10.3. (Thanks to Peter Jurgensen, 2015-11-21.)
|
**p.43, ¶3:** Import declarations are explained in §10.4, not §10.3. (Thanks to Peter Jurgensen, 2015-11-21. Corrected in the third printing.)
|
||||||
|
|
||||||
|
**p.48:** `f.ReadByte()` serves as an example of a reference to f, but `*os.File` has no such method. For "ReadByte", read "Stat", four times. (Thanks to Peter Olsen, 2016-01-06. Corrected in the third printing.)
|
||||||
|
|
||||||
**p.52, ¶2:** for "an synonym", read "a synonym", twice. (Corrected in the second printing.)
|
**p.52, ¶2:** for "an synonym", read "a synonym", twice. (Corrected in the second printing.)
|
||||||
|
|
||||||
@ -38,19 +40,19 @@
|
|||||||
**p.76:** the comment `// "time.Duration 5ms0s` should have a closing double-quotation mark. (Corrected in the second printing.)
|
**p.76:** the comment `// "time.Duration 5ms0s` should have a closing double-quotation mark. (Corrected in the second printing.)
|
||||||
|
|
||||||
**p.79, ¶4:** "When an untyped constant is assigned to a variable, as in the first statement below, or
|
**p.79, ¶4:** "When an untyped constant is assigned to a variable, as in the first statement below, or
|
||||||
appears on the right-hand side of a variable declaration with an explicit type, as in the other three statements, ..." has it backwards: the <i>first</i> statement is a declaration; the other three are assignments. (Thanks to Yoshiki Shibata, 2015-11-09.)
|
appears on the right-hand side of a variable declaration with an explicit type, as in the other three statements, ..." has it backwards: the <i>first</i> statement is a declaration; the other three are assignments. (Thanks to Yoshiki Shibata, 2015-11-09. Corrected in the third printing.)
|
||||||
|
|
||||||
**p.132, code display following ¶3:** the final comment should read: `// compile error: can't assign func(int, int) int to func(int) int` (Thanks to Toni Suter, 2015-11-21.)
|
**p.132, code display following ¶3:** the final comment should read: `// compile error: can't assign func(int, int) int to func(int) int` (Thanks to Toni Suter, 2015-11-21. Corrected in the third printing.)
|
||||||
|
|
||||||
**p.166, ¶2:** for "way", read "a way".
|
**p.166, ¶2:** for "way", read "a way". (Corrected in the third printing.)
|
||||||
|
|
||||||
|
**p.200, TestEval function:** the format string in the final call to t.Errorf should format test.env with %v, not %s. (Thanks to Mitsuteru Sawa, 2015-12-07. Corrected in the third printing.)
|
||||||
|
|
||||||
**p.222. Exercise 8.1:** The port numbers for `London` and `Tokyo` should be swapped in the final command to match the earlier commands. (Thanks to Kiyoshi Kamishima, 2016-01-08.)
|
**p.222. Exercise 8.1:** The port numbers for `London` and `Tokyo` should be swapped in the final command to match the earlier commands. (Thanks to Kiyoshi Kamishima, 2016-01-08.)
|
||||||
|
|
||||||
**p.288, code display following ¶4:** In the import declaration, for `"database/mysql"`, read `"database/sql"`. (Thanks to Jose Colon Rodriguez, 2016-01-09.)
|
**p.288, code display following ¶4:** In the import declaration, for `"database/mysql"`, read `"database/sql"`. (Thanks to Jose Colon Rodriguez, 2016-01-09.)
|
||||||
|
|
||||||
**p.200, TestEval function:** the format string in the final call to t.Errorf should format test.env with %v, not %s. (Thanks to Mitsuteru Sawa, 2015-12-07.)
|
|
||||||
|
|
||||||
**p.347, Exercise 12.8:** for "like json.Marshal", read "like json.Unmarshal". (Thanks to @chai2010, 2016-01-01.)
|
**p.347, Exercise 12.8:** for "like json.Marshal", read "like json.Unmarshal". (Thanks to @chai2010, 2016-01-01.)
|
||||||
|
|
||||||
**p.362:** the `gopl.io/ch13/bzip` program does not comply with the [proposed rules for passing pointers between Go and C code](https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) because the C function `bz2compress` temporarily stores a Go pointer (in) into the Go heap (the `bz_stream` variable). The `bz_stream` variable should be allocated, and explicitly freed after the call to `BZ2_bzCompressEnd`, by C functions. (Thanks to Joe Tsai, 2015-11-18.)
|
**p.362:** the `gopl.io/ch13/bzip` program does not comply with the [proposed rules for passing pointers between Go and C code](https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) because the C function `bz2compress` temporarily stores a Go pointer (in) into the Go heap (the `bz_stream` variable). The `bz_stream` variable should be allocated, and explicitly freed after the call to `BZ2_bzCompressEnd`, by C functions. (Thanks to Joe Tsai, 2015-11-18. Corrected in the third printing.)
|
||||||
|
|
||||||
|
@ -12,6 +12,6 @@
|
|||||||
中文譯者 | 章節
|
中文譯者 | 章節
|
||||||
-------------------------------------- | -------------------------
|
-------------------------------------- | -------------------------
|
||||||
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
|
`chai2010 <chaishushan@gmail.com>` | 前言/第2~4章/第10~13章
|
||||||
|
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
|
||||||
`CrazySssst` | 第5章
|
`CrazySssst` | 第5章
|
||||||
`foreversmart <njutree@gmail.com>` | 第7章
|
`foreversmart <njutree@gmail.com>` | 第7章
|
||||||
`Xargin <cao1988228@163.com>` | 第1章/第6章/第8~9章
|
|
||||||
|
22
appendix/appendix-d-translations.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
## 附録D:其它語言
|
||||||
|
|
||||||
|
下表是 [The Go Programming Language](http://www.gopl.io/) 其它語言版本:
|
||||||
|
|
||||||
|
語言 | 鏈接 | 時間 | 譯者 | ISBN
|
||||||
|
---- | ---- | ---- | ---- | ----
|
||||||
|
中文 | [《Go語言聖經》][gopl-zh] | 2016/2/1 | [chai2010][chai2010], [Xargin][Xargin], [CrazySssst][CrazySssst], [foreversmart][foreversmart] | ?
|
||||||
|
韓語 | [Acorn Publishing (Korea)](http://www.acornpub.co.kr/) | 2016 | ? | ?
|
||||||
|
俄語 | [Williams Publishing (Russia)](http://www.williamspublishing.com/) | 2016 | ? | ?
|
||||||
|
波蘭語 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ?
|
||||||
|
日語 | [Maruzen Publishing (Japan)](http://www.maruzen.co.jp/corp/en/services/publishing.html) | 2017 | Yoshiki Shibata | ?
|
||||||
|
葡萄牙語 | [Novatec Editora (Brazil)](http://novatec.com.br/) |2017 | ? | ?
|
||||||
|
中文簡體 | [Pearson Education Asia](http://www.pearsonapac.com/) |2017 | ? | ?
|
||||||
|
中文繁體 | [Gotop Information (Taiwan)](http://www.gotop.com.tw/) | 2017 | ? | ?
|
||||||
|
|
||||||
|
|
||||||
|
[gopl-zh]: http://golang-china.github.io/gopl-zh/ "《Go語言聖經》"
|
||||||
|
|
||||||
|
[chai2010]: https://github.com/chai2010
|
||||||
|
[Xargin]: https://github.com/cch123
|
||||||
|
[CrazySssst]: https://github.com/CrazySssst
|
||||||
|
[foreversmart]: https://github.com/foreversmart
|
3
appendix/appendix-z-index-good.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 索引
|
||||||
|
|
||||||
|
TODO
|
152
appendix/appendix-z-index.md
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# 索引
|
||||||
|
|
||||||
|
<!-- 索引有三列,每列寬度40個字符
|
||||||
|
+------------------------------------- + ------------------------------------- + ------------
|
||||||
|
-->
|
||||||
|
|
||||||
|
### P367
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P368
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P369
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P390
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P391
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P392
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P393
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P394
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P395
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P396
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P397
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P398
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
### P399
|
||||||
|
|
||||||
|
```
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### P400
|
||||||
|
|
||||||
|
<!-- 索引有三列,每列寬度40個字符
|
||||||
|
+------------------------------------- + ------------------------------------- + ------------
|
||||||
|
-->
|
||||||
|
|
||||||
|
```
|
||||||
|
standard 2, 27, 52, 66, 67, 69, 97 variables, shared 257
|
||||||
|
unicodepackage 71 variable-size stack 124
|
||||||
|
unicode.IsDigit function 71 variadic function 142, 172
|
||||||
|
unicode.IsLetter function 71 vector, bit 165
|
||||||
|
unicode.IsLower function 71 vendoring 293
|
||||||
|
unicode.IsSpace function 93 visibility 28, 29, 41, 168, 297
|
||||||
|
unicode.IsUpper function 71 visit function 122
|
||||||
|
unicode/utf8package 69
|
||||||
|
unidirectional channel type 230, 231 wait example 130
|
||||||
|
union, discriminated 211, 213, 214 WaitForServer function 130
|
||||||
|
universe block 46 walkDir function 247
|
||||||
|
Unix domain socket 219 web
|
||||||
|
unmarshaling JSON 110 crawler 119
|
||||||
|
unnamed struct type 163 crawler, concurrent 239
|
||||||
|
unnamed variable 34, 88 framework 193
|
||||||
|
unreachable statement 120 while loop 6
|
||||||
|
unsafe package 354 white-box test 311
|
||||||
|
unsafe.AlignOf function 355 Wilkes, Maurice 301
|
||||||
|
unsafe.Offsetof function 355 Wirth, Niklaus xiii
|
||||||
|
unsafe.Pointer conversion 356 word example 303, 305, 308
|
||||||
|
unsafe.Pointer type 356 word example, test of 303
|
||||||
|
unsafe.Pointer zero value 356 workspace organization 291
|
||||||
|
unsafe.Sizeof function 354 writer lock 266
|
||||||
|
unsigned integer 52, 54 writing effective tests 316, 317
|
||||||
|
untyped constant types 78
|
||||||
|
unused parameter 120 xkcd JSON interface 113
|
||||||
|
URL 123 XML decoding 213
|
||||||
|
URL escape 111 XML (Extensible Markup Language)
|
||||||
|
url.QueryEscape function 111 107
|
||||||
|
url.URLtype 193 (*xml.Decoder).Token method
|
||||||
|
url values example 160 213
|
||||||
|
UTF-8 66, 67, 98 xmlselect example 215
|
||||||
|
UTF-8 encodings, table of 67
|
||||||
|
utf8.DecodeRuneInString function zero length slice 87
|
||||||
|
69 zero value
|
||||||
|
utf8.RuneCountInString function array 82
|
||||||
|
69 boolean 30
|
||||||
|
utf8.UTFMaxvalue 98 channel 225, 246
|
||||||
|
func tion 132
|
||||||
|
value interface 182
|
||||||
|
addressable 32 map 95
|
||||||
|
call by 83, 120, 158 named result 120, 127
|
||||||
|
func tion 132 number 5, 30
|
||||||
|
interface 181 pointer 32
|
||||||
|
method 164 reflect.Value 332
|
||||||
|
utf8.UTFMax 98 slice 74, 87
|
||||||
|
var declaration 5, 30 string 5, 7, 30
|
||||||
|
variable struct 102
|
||||||
|
confinement 261 unsafe.Pointer 356
|
||||||
|
heap 36
|
||||||
|
http.DefaultClient 253
|
||||||
|
io.Discard 18
|
||||||
|
io.EOF 132
|
||||||
|
lifetime 35, 46, 135
|
||||||
|
local 29, 141
|
||||||
|
os.Args 4
|
||||||
|
stack 36
|
||||||
|
unnamed 34, 88
|
||||||
|
variables, escaping 36
|
||||||
|
```
|
@ -1,3 +1,69 @@
|
|||||||
## 5.10. Recover捕獲異常
|
## 5.10. Recover捕獲異常
|
||||||
|
|
||||||
TODO
|
通常來説,不應該對panic異常做任何處理,但有時,也許我們可以從異常中恢複,至少我們可以在程序崩潰前,做一些操作。舉個例子,當web服務器遇到不可預料的嚴重問題時,在崩潰前應該將所有的連接關閉;如果不做任何處理,會使得客戶端一直處於等待狀態。如果web服務器還在開發階段,服務器甚至可以將異常信息反饋到客戶端,幫助調試。
|
||||||
|
|
||||||
|
如果在deferred函數中調用了內置函數recover,併且定義該defer語句的函數發生了panic異常,recover會使程序從panic中恢複,併返迴panic value。導致panic異常的函數不會繼續運行,但能正常返迴。在未發生panic時調用recover,recover會返迴nil。
|
||||||
|
|
||||||
|
讓我們以語言解析器爲例,説明recover的使用場景。考慮到語言解析器的複雜性,卽使某個語言解析器目前工作正常,也無法肯定它沒有漏洞。因此,當某個異常出現時,我們不會選擇讓解析器崩潰,而是會將panic異常當作普通的解析錯誤,併附加額外信息提醒用戶報告此錯誤。
|
||||||
|
|
||||||
|
```Go
|
||||||
|
func Parse(input string) (s *Syntax, err error) {
|
||||||
|
defer func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
err = fmt.Errorf("internal error: %v", p)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// ...parser...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
deferred函數幫助Parse從panic中恢複。在deferred函數內部,panic value被附加到錯誤信息中;併用err變量接收錯誤信息,返迴給調用者。我們也可以通過調用runtime.Stack往錯誤信息中添加完整的堆棧調用信息。
|
||||||
|
|
||||||
|
不加區分的恢複所有的panic異常,不是可取的做法;因爲在panic之後,無法保證包級變量的狀態仍然和我們預期一致。比如,對數據結構的一次重要更新沒有被完整完成、文件或者網絡連接沒有被關閉、獲得的鎖沒有被釋放。此外,如果寫日誌時産生的panic被不加區分的恢複,可能會導致漏洞被忽略。
|
||||||
|
|
||||||
|
雖然把對panic的處理都集中在一個包下,有助於簡化對複雜和不可以預料問題的處理,但作爲被廣泛遵守的規范,你不應該試圖去恢複其他包引起的panic。公有的API應該將函數的運行失敗作爲error返迴,而不是panic。同樣的,你也不應該恢複一個由他人開發的函數引起的panic,比如説調用者傳入的迴調函數,因爲你無法確保這樣做是安全的。
|
||||||
|
|
||||||
|
有時我們很難完全遵循規范,舉個例子,net/http包中提供了一個web服務器,將收到的請求分發給用戶提供的處理函數。很顯然,我們不能因爲某個處理函數引發的panic異常,殺掉整個進程;web服務器遇到處理函數導致的panic時會調用recover,輸出堆棧信息,繼續運行。這樣的做法在實踐中很便捷,但也會引起資源洩漏,或是因爲recover操作,導致其他問題。
|
||||||
|
|
||||||
|
基於以上原因,安全的做法是有選擇性的recover。換句話説,隻恢複應該被恢複的panic異常,此外,這些異常所占的比例應該盡可能的低。爲了標識某個panic是否應該被恢複,我們可以將panic value設置成特殊類型。在recover時對panic value進行檢査,如果發現panic value是特殊類型,就將這個panic作爲errror處理,如果不是,則按照正常的panic進行處理(在下面的例子中,我們會看到這種方式)。
|
||||||
|
|
||||||
|
下面的例子是title函數的變形,如果HTML頁面包含多個`<title>`,該函數會給調用者返迴一個錯誤(error)。在soleTitle內部處理時,如果檢測到有多個`<title>`,會調用panic,阻止函數繼續遞歸,併將特殊類型bailout作爲panic的參數。
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// soleTitle returns the text of the first non-empty title element
|
||||||
|
// in doc, and an error if there was not exactly one.
|
||||||
|
func soleTitle(doc *html.Node) (title string, err error) {
|
||||||
|
type bailout struct{}
|
||||||
|
defer func() {
|
||||||
|
switch p := recover(); p {
|
||||||
|
case nil:
|
||||||
|
// no panic
|
||||||
|
case bailout{}:
|
||||||
|
// "expected" panic
|
||||||
|
err = fmt.Errorf("multiple title elements")
|
||||||
|
default:
|
||||||
|
panic(p) // unexpected panic; carry on panicking
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// Bail out of recursion if we find more than one nonempty title.
|
||||||
|
forEachNode(doc, func(n *html.Node) {
|
||||||
|
if n.Type == html.ElementNode && n.Data == "title" &&
|
||||||
|
n.FirstChild != nil {
|
||||||
|
if title != "" {
|
||||||
|
panic(bailout{}) // multiple titleelements
|
||||||
|
}
|
||||||
|
title = n.FirstChild.Data
|
||||||
|
}
|
||||||
|
}, nil)
|
||||||
|
if title == "" {
|
||||||
|
return "", fmt.Errorf("no title element")
|
||||||
|
}
|
||||||
|
return title, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在上例中,deferred函數調用recover,併檢査panic value。當panic value是bailout{}類型時,deferred函數生成一個error返迴給調用者。當panic value是其他non-nil值時,表示發生了未知的panic異常,deferred函數將調用panic函數併將當前的panic value作爲參數傳入;此時,等同於recover沒有做任何操作。(請註意:在例子中,對可預期的錯誤采用了panic,這違反了之前的建議,我們在此隻是想向讀者演示這種機製。)
|
||||||
|
|
||||||
|
有些情況下,我們無法恢複。某些致命錯誤會導致Go在運行時終止程序,如內存不足。
|
||||||
|
|
||||||
|
**練習5.19:** 使用panic和recover編寫一個不包含return語句但能返迴一個非零值的函數。
|
114
ch7/ch7-06.md
@ -4,23 +4,28 @@
|
|||||||
幸運的是,sort包內置的提供了根據一些排序函數來對任何序列排序的功能。它的設計非常獨到。在很多語言中,排序算法都是和序列數據類型關聯,同時排序函數和具體類型元素關聯。相比之下,Go語言的sort.Sort函數不會對具體的序列和它的元素做任何假設。相反,它使用了一個接口類型sort.Interface來指定通用的排序算法和可能被排序到的序列類型之間的約定。這個接口的實現由序列的具體表示和它希望排序的元素決定,序列的表示經常是一個切片。
|
幸運的是,sort包內置的提供了根據一些排序函數來對任何序列排序的功能。它的設計非常獨到。在很多語言中,排序算法都是和序列數據類型關聯,同時排序函數和具體類型元素關聯。相比之下,Go語言的sort.Sort函數不會對具體的序列和它的元素做任何假設。相反,它使用了一個接口類型sort.Interface來指定通用的排序算法和可能被排序到的序列類型之間的約定。這個接口的實現由序列的具體表示和它希望排序的元素決定,序列的表示經常是一個切片。
|
||||||
|
|
||||||
一個內置的排序算法需要知道三個東西:序列的長度,表示兩個元素比較的結果,一種交換兩個元素的方式;這就是sort.Interface的三個方法:
|
一個內置的排序算法需要知道三個東西:序列的長度,表示兩個元素比較的結果,一種交換兩個元素的方式;這就是sort.Interface的三個方法:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package sort
|
package sort
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
Len() int
|
Len() int
|
||||||
Less(i, j int) bool // i, j are indices of sequence elements
|
Less(i, j int) bool // i, j are indices of sequence elements
|
||||||
Swap(i, j int)
|
Swap(i, j int)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
爲了對序列進行排序,我們需要定義一個實現了這三個方法的類型,然後對這個類型的一個實例應用sort.Sort函數。思考對一個字符串切片進行排序,這可能是最簡單的例子了。下面是這個新的類型StringSlice和它的Len,Less和Swap方法
|
爲了對序列進行排序,我們需要定義一個實現了這三個方法的類型,然後對這個類型的一個實例應用sort.Sort函數。思考對一個字符串切片進行排序,這可能是最簡單的例子了。下面是這個新的類型StringSlice和它的Len,Less和Swap方法
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type StringSlice []string
|
type StringSlice []string
|
||||||
func (p StringSlice) Len() int { return len(p) }
|
func (p StringSlice) Len() int { return len(p) }
|
||||||
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
|
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
|
||||||
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
現在我們可以通過像下面這樣將一個切片轉換爲一個StringSlice類型來進行排序:
|
現在我們可以通過像下面這樣將一個切片轉換爲一個StringSlice類型來進行排序:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(StringSlice(names))
|
sort.Sort(StringSlice(names))
|
||||||
```
|
```
|
||||||
@ -33,56 +38,65 @@ sort.Sort(StringSlice(names))
|
|||||||
我們會運行上面的例子來對一個表格中的音樂播放列表進行排序。每個track都是單獨的一行,每一列都是這個track的屬性像藝術家,標題,和運行時間。想象一個圖形用戶界面來呈現這個表格,併且點擊一個屬性的頂部會使這個列表按照這個屬性進行排序;再一次點擊相同屬性的頂部會進行逆向排序。讓我們看下每個點擊會發生什麽響應。
|
我們會運行上面的例子來對一個表格中的音樂播放列表進行排序。每個track都是單獨的一行,每一列都是這個track的屬性像藝術家,標題,和運行時間。想象一個圖形用戶界面來呈現這個表格,併且點擊一個屬性的頂部會使這個列表按照這個屬性進行排序;再一次點擊相同屬性的頂部會進行逆向排序。讓我們看下每個點擊會發生什麽響應。
|
||||||
|
|
||||||
下面的變量tracks包好了一個播放列表。(One of the authors apologizes for the other author’s musical tastes.)每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作,sort函數會交換很多對元素,所以如果每個元素都是指針會更快而不是全部Track類型,指針是一個機器字碼長度而Track類型可能是八個或更多。
|
下面的變量tracks包好了一個播放列表。(One of the authors apologizes for the other author’s musical tastes.)每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作,sort函數會交換很多對元素,所以如果每個元素都是指針會更快而不是全部Track類型,指針是一個機器字碼長度而Track類型可能是八個或更多。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
//gopl.io/ch7/sorting
|
//gopl.io/ch7/sorting
|
||||||
type Track struct {
|
type Track struct {
|
||||||
Title string
|
Title string
|
||||||
Artist string
|
Artist string
|
||||||
Album string
|
Album string
|
||||||
Year int
|
Year int
|
||||||
Length time.Duration
|
Length time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var tracks = []*Track{
|
var tracks = []*Track{
|
||||||
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
|
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
|
||||||
{"Go", "Moby", "Moby", 1992, length("3m37s")},
|
{"Go", "Moby", "Moby", 1992, length("3m37s")},
|
||||||
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
|
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
|
||||||
{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
|
{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
|
||||||
}
|
}
|
||||||
|
|
||||||
func length(s string) time.Duration {
|
func length(s string) time.Duration {
|
||||||
d, err := time.ParseDuration(s)
|
d, err := time.ParseDuration(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(s)
|
panic(s)
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
printTracks函數將播放列表打印成一個表格。一個圖形化的展示可能會更好點,但是這個小程序使用text/tabwriter包來生成一個列是整齊對齊和隔開的表格,像下面展示的這樣。註意到*tabwriter.Writer是滿足io.Writer接口的。它會收集每一片寫向它的數據;它的Flush方法會格式化整個表格併且將它寫向os.Stdout(標準輸出)。
|
printTracks函數將播放列表打印成一個表格。一個圖形化的展示可能會更好點,但是這個小程序使用text/tabwriter包來生成一個列是整齊對齊和隔開的表格,像下面展示的這樣。註意到*tabwriter.Writer是滿足io.Writer接口的。它會收集每一片寫向它的數據;它的Flush方法會格式化整個表格併且將它寫向os.Stdout(標準輸出)。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func printTracks(tracks []*Track) {
|
func printTracks(tracks []*Track) {
|
||||||
const format = "%v\t%v\t%v\t%v\t%v\t\n"
|
const format = "%v\t%v\t%v\t%v\t%v\t\n"
|
||||||
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
||||||
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
|
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
|
||||||
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
|
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
|
||||||
for _, t := range tracks {
|
for _, t := range tracks {
|
||||||
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
|
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
|
||||||
}
|
}
|
||||||
tw.Flush() // calculate column widths and print table
|
tw.Flush() // calculate column widths and print table
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
爲了能按照Artist字段對播放列表進行排序,我們會像對StringSlice那樣定義一個新的帶有必鬚Len,Less和Swap方法的切片類型。
|
爲了能按照Artist字段對播放列表進行排序,我們會像對StringSlice那樣定義一個新的帶有必鬚Len,Less和Swap方法的切片類型。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type byArtist []*Track
|
type byArtist []*Track
|
||||||
func (x byArtist) Len() int { return len(x) }
|
func (x byArtist) Len() int { return len(x) }
|
||||||
func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
|
func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
|
||||||
func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
爲了調用通用的排序程序,我們必鬚先將tracks轉換爲新的byArtist類型,它定義了具體的排序:
|
爲了調用通用的排序程序,我們必鬚先將tracks轉換爲新的byArtist類型,它定義了具體的排序:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(byArtist(tracks))
|
sort.Sort(byArtist(tracks))
|
||||||
```
|
```
|
||||||
|
|
||||||
在按照artist對這個切片進行排序後,printTrack的輸出如下
|
在按照artist對這個切片進行排序後,printTrack的輸出如下
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -91,11 +105,15 @@ Go Delilah From the Roots Up 2012 3m38s
|
|||||||
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
||||||
Go Moby Moby 1992 3m37s
|
Go Moby Moby 1992 3m37s
|
||||||
```
|
```
|
||||||
|
|
||||||
如果用戶第二次請求“按照artist排序”,我們會對tracks進行逆向排序。然而我們不需要定義一個有顛倒Less方法的新類型byReverseArtist,因爲sort包中提供了Reverse函數將排序順序轉換成逆序。
|
如果用戶第二次請求“按照artist排序”,我們會對tracks進行逆向排序。然而我們不需要定義一個有顛倒Less方法的新類型byReverseArtist,因爲sort包中提供了Reverse函數將排序順序轉換成逆序。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(sort.Reverse(byArtist(tracks)))
|
sort.Sort(sort.Reverse(byArtist(tracks)))
|
||||||
```
|
```
|
||||||
|
|
||||||
在按照artist對這個切片進行逆向排序後,printTrack的輸出如下
|
在按照artist對這個切片進行逆向排序後,printTrack的輸出如下
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -104,7 +122,9 @@ Ready 2 Go Martin Solveig Smash 2011 4m24s
|
|||||||
Go Delilah From the Roots Up 2012 3m38s
|
Go Delilah From the Roots Up 2012 3m38s
|
||||||
Go Ahead Alicia Keys As I Am 2007 4m36s
|
Go Ahead Alicia Keys As I Am 2007 4m36s
|
||||||
```
|
```
|
||||||
|
|
||||||
sort.Reverse函數值得進行更近一步的學習因爲它使用了(§6.3)章中的組合,這是一個重要的思路。sort包定義了一個不公開的struct類型reverse,它嵌入了一個sort.Interface。reverse的Less方法調用了內嵌的sort.Interface值的Less方法,但是通過交換索引的方式使排序結果變成逆序。
|
sort.Reverse函數值得進行更近一步的學習因爲它使用了(§6.3)章中的組合,這是一個重要的思路。sort包定義了一個不公開的struct類型reverse,它嵌入了一個sort.Interface。reverse的Less方法調用了內嵌的sort.Interface值的Less方法,但是通過交換索引的方式使排序結果變成逆序。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package sort
|
package sort
|
||||||
|
|
||||||
@ -114,16 +134,20 @@ func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
|
|||||||
|
|
||||||
func Reverse(data Interface) Interface { return reverse{data} }
|
func Reverse(data Interface) Interface { return reverse{data} }
|
||||||
```
|
```
|
||||||
|
|
||||||
reverse的另外兩個方法Len和Swap隱式地由原有內嵌的sort.Interface提供。因爲reverse是一個不公開的類型,所以導出函數Reverse函數返迴一個包含原有sort.Interface值的reverse類型實例。
|
reverse的另外兩個方法Len和Swap隱式地由原有內嵌的sort.Interface提供。因爲reverse是一個不公開的類型,所以導出函數Reverse函數返迴一個包含原有sort.Interface值的reverse類型實例。
|
||||||
|
|
||||||
爲了可以按照不同的列進行排序,我們必鬚定義一個新的類型例如byYear:
|
爲了可以按照不同的列進行排序,我們必鬚定義一個新的類型例如byYear:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type byYear []*Track
|
type byYear []*Track
|
||||||
func (x byYear) Len() int { return len(x) }
|
func (x byYear) Len() int { return len(x) }
|
||||||
func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year }
|
func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year }
|
||||||
func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
在使用sort.Sort(byYear(tracks))按照年對tracks進行排序後,printTrack展示了一個按時間先後順序的列表:
|
在使用sort.Sort(byYear(tracks))按照年對tracks進行排序後,printTrack展示了一個按時間先後順序的列表:
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -132,33 +156,39 @@ Go Ahead Alicia Keys As I Am 2007 4m36s
|
|||||||
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
||||||
Go Delilah From the Roots Up 2012 3m38s
|
Go Delilah From the Roots Up 2012 3m38s
|
||||||
```
|
```
|
||||||
|
|
||||||
對於我們需要的每個切片元素類型和每個排序函數,我們需要定義一個新的sort.Interface實現。如你所見,Len和Swap方法對於所有的切片類型都有相同的定義。下個例子,具體的類型customSort會將一個切片和函數結合,使我們隻需要寫比較函數就可以定義一個新的排序。順便説下,實現了sort.Interface的具體類型不一定是切片類型;customSort是一個結構體類型。
|
對於我們需要的每個切片元素類型和每個排序函數,我們需要定義一個新的sort.Interface實現。如你所見,Len和Swap方法對於所有的切片類型都有相同的定義。下個例子,具體的類型customSort會將一個切片和函數結合,使我們隻需要寫比較函數就可以定義一個新的排序。順便説下,實現了sort.Interface的具體類型不一定是切片類型;customSort是一個結構體類型。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type customSort struct {
|
type customSort struct {
|
||||||
t []*Track
|
t []*Track
|
||||||
less func(x, y *Track) bool
|
less func(x, y *Track) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x customSort) Len() int
|
func (x customSort) Len() int
|
||||||
func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) }
|
func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) }
|
||||||
func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }
|
func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
讓我們定義一個多層的排序函數,它主要的排序鍵是標題,第二個鍵是年,第三個鍵是運行時間Length。下面是該排序的調用,其中這個排序使用了匿名排序函數:
|
讓我們定義一個多層的排序函數,它主要的排序鍵是標題,第二個鍵是年,第三個鍵是運行時間Length。下面是該排序的調用,其中這個排序使用了匿名排序函數:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(customSort{tracks, func(x, y *Track) bool {
|
sort.Sort(customSort{tracks, func(x, y *Track) bool {
|
||||||
if x.Title != y.Title {
|
if x.Title != y.Title {
|
||||||
return x.Title < y.Title
|
return x.Title < y.Title
|
||||||
}
|
}
|
||||||
if x.Year != y.Year {
|
if x.Year != y.Year {
|
||||||
return x.Year < y.Year
|
return x.Year < y.Year
|
||||||
}
|
}
|
||||||
if x.Length != y.Length {
|
if x.Length != y.Length {
|
||||||
return x.Length < y.Length
|
return x.Length < y.Length
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}})
|
}})
|
||||||
```
|
```
|
||||||
|
|
||||||
這下面是排序的結果。註意到兩個標題是“Go”的track按照標題排序是相同的順序,但是在按照year排序上更久的那個track優先。
|
這下面是排序的結果。註意到兩個標題是“Go”的track按照標題排序是相同的順序,但是在按照year排序上更久的那個track優先。
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -167,7 +197,9 @@ Go Delilah From the Roots Up 2012 3m38s
|
|||||||
Go Ahead Alicia Keys As I Am 2007 4m36s
|
Go Ahead Alicia Keys As I Am 2007 4m36s
|
||||||
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
||||||
```
|
```
|
||||||
|
|
||||||
盡管對長度爲n的序列排序需要 O(n log n)次比較操作,檢査一個序列是否已經有序至少需要n−1次比較。sort包中的IsSorted函數幫我們做這樣的檢査。像sort.Sort一樣,它也使用sort.Interface對這個序列和它的排序函數進行抽象,但是它從不會調用Swap方法:這段代碼示范了IntsAreSorted和Ints函數和IntSlice類型的使用:
|
盡管對長度爲n的序列排序需要 O(n log n)次比較操作,檢査一個序列是否已經有序至少需要n−1次比較。sort包中的IsSorted函數幫我們做這樣的檢査。像sort.Sort一樣,它也使用sort.Interface對這個序列和它的排序函數進行抽象,但是它從不會調用Swap方法:這段代碼示范了IntsAreSorted和Ints函數和IntSlice類型的使用:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
values := []int{3, 1, 4, 1}
|
values := []int{3, 1, 4, 1}
|
||||||
fmt.Println(sort.IntsAreSorted(values)) // "false"
|
fmt.Println(sort.IntsAreSorted(values)) // "false"
|
||||||
@ -180,8 +212,8 @@ fmt.Println(sort.IntsAreSorted(values)) // "false"
|
|||||||
```
|
```
|
||||||
爲了使用方便,sort包爲[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他類型,例如[]int64或者[]uint,盡管路徑也很簡單,還是依賴我們自己實現。
|
爲了使用方便,sort包爲[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他類型,例如[]int64或者[]uint,盡管路徑也很簡單,還是依賴我們自己實現。
|
||||||
|
|
||||||
練習7.8:很多圖形界面提供了一個有狀態的多重排序表格插件:主要的排序鍵是最近一次點擊過列頭的列,第二個排序鍵是第二最近點擊過列頭的列,等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重複使用sort.Stable來排序的方式。
|
**練習 7.8:** 很多圖形界面提供了一個有狀態的多重排序表格插件:主要的排序鍵是最近一次點擊過列頭的列,第二個排序鍵是第二最近點擊過列頭的列,等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重複使用sort.Stable來排序的方式。
|
||||||
|
|
||||||
練習7.9:使用html/template包 (§4.6) 替代printTracks將tracks展示成一個HTML表格。將這個解決方案用在前一個練習中,讓每次點擊一個列的頭部産生一個HTTP請求來排序這個表格。
|
**練習 7.9:** 使用html/template包 (§4.6) 替代printTracks將tracks展示成一個HTML表格。將這個解決方案用在前一個練習中,讓每次點擊一個列的頭部産生一個HTTP請求來排序這個表格。
|
||||||
|
|
||||||
練習7.10:sort.Interface類型也可以適用在其它地方。編寫一個IsPalindrome(s sort.Interface) bool函數表明序列s是否是迴文序列,換句話説反向排序不會改變這個序列。假設如果!s.Less(i, j) && !s.Less(j, i)則索引i和j上的元素相等。
|
**練習 7.10:** sort.Interface類型也可以適用在其它地方。編寫一個IsPalindrome(s sort.Interface) bool函數表明序列s是否是迴文序列,換句話説反向排序不會改變這個序列。假設如果!s.Less(i, j) && !s.Less(j, i)則索引i和j上的元素相等。
|
||||||
|
173
ch9/ch9-01.md
@ -1,3 +1,174 @@
|
|||||||
## 9.1. 競爭條件
|
## 9.1. 競爭條件
|
||||||
|
|
||||||
TODO
|
在一個線性(就是説隻有一個goroutine的)的程序中,程序的執行順序隻由程序的邏輯來決定。例如,我們有一段語句序列,第一個在第二個之前(廢話),以此類推。在有兩個或更多goroutine的程序中,每一個goroutine內的語句也是按照旣定的順序去執行的,但是一般情況下我們沒法去知道分别位於兩個goroutine的事件x和y的執行順序,x是在y之前還是之後還是同時發生是沒法判斷的。當我們能夠沒有辦法自信地確認一個事件是在另一個事件的前面或者後面發生的話,就説明x和y這兩個事件是併發的。
|
||||||
|
|
||||||
|
考慮一下,一個函數在線性程序中可以正確地工作。如果在併發的情況下,這個函數依然可以正確地工作的話,那麽我們就説這個函數是併發安全的,併發安全的函數不需要額外的同步工作。我們可以把這個概念概括爲一個特定類型的一些方法和操作函數,如果這個類型是併發安全的話,那麽所有它的訪問方法和操作就都是併發安全的。
|
||||||
|
|
||||||
|
在一個程序中有非併發安全的類型的情況下,我們依然可以使這個程序併發安全。確實,併發安全的類型是例外,而不是規則,所以隻有當文檔中明確地説明了其是併發安全的情況下,你才可以併發地去訪問它。我們會避免併發訪問大多數的類型,無論是將變量局限在單一的一個goroutine內還是用互斥條件維持更高級别的不變性都是爲了這個目的。我們會在本章中説明這些術語。
|
||||||
|
|
||||||
|
相反,導出包級别的函數一般情況下都是併發安全的。由於package級的變量沒法被限製在單一的gorouine,所以脩改這些變量“必鬚”使用互斥條件。
|
||||||
|
|
||||||
|
一個函數在併發調用時沒法工作的原因太多了,比如死鎖(deadlock)、活鎖(livelock)和餓死(resource starvation)。我們沒有空去討論所有的問題,這里我們隻聚焦在競爭條件上。
|
||||||
|
|
||||||
|
競爭條件指的是程序在多個goroutine交叉執行操作時,沒有給出正確的結果。競爭條件是很惡劣的一種場景,因爲這種問題會一直潛伏在你的程序里,然後在非常少見的時候蹦出來,或許隻是會在很大的負載時才會發生,又或許是會在使用了某一個編譯器、某一種平台或者某一種架構的時候才會出現。這些使得競爭條件帶來的問題非常難以複現而且難以分析診斷。
|
||||||
|
|
||||||
|
傳統上經常用經濟損失來爲競爭條件做比喻,所以我們來看一個簡單的銀行賬戶程序。
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Package bank implements a bank with only one account.
|
||||||
|
package bank
|
||||||
|
var balance int
|
||||||
|
func Deposit(amount int) { balance = balance + amount }
|
||||||
|
func Balance() int { return balance }
|
||||||
|
```
|
||||||
|
|
||||||
|
(當然我們也可以把Deposit存款函數寫成balance += amount,這種形式也是等價的,不過長一些的形式解釋起來更方便一些。)
|
||||||
|
|
||||||
|
對於這個具體的程序而言,我們可以瞅一眼各種存款和査餘額的順序調用,都能給出正確的結果。也就是説,Balance函數會給出之前的所有存入的額度之和。然而,當我們併發地而不是順序地調用這些函數的話,Balance就再也沒辦法保證結果正確了。考慮一下下面的兩個goroutine,其代表了一個銀行聯合賬戶的兩筆交易:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Alice:
|
||||||
|
go func() {
|
||||||
|
bank.Deposit(200) // A1
|
||||||
|
fmt.Println("=", bank.Balance()) // A2
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Bob:
|
||||||
|
go bank.Deposit(100) // B
|
||||||
|
```
|
||||||
|
|
||||||
|
Alice存了$200,然後檢査她的餘額,同時Bob存了$100。因爲A1和A2是和B併發執行的,我們沒法預測他們發生的先後順序。直觀地來看的話,我們會認爲其執行順序隻有三種可能性:“Alice先”,“Bob先”以及“Alice/Bob/Alice”交錯執行。下面的表格會展示經過每一步驟後balance變量的值。引號里的字符串表示餘額單。
|
||||||
|
|
||||||
|
```
|
||||||
|
Alice first Bob first Alice/Bob/Alice
|
||||||
|
0 0 0
|
||||||
|
A1 200 B 100 A1 200
|
||||||
|
A2 "=200" A1 300 B 300
|
||||||
|
B 300 A2 "=300" A2 "=300"
|
||||||
|
```
|
||||||
|
|
||||||
|
所有情況下最終的餘額都是$300。唯一的變數是Alice的餘額單是否包含了Bob交易,不過無論怎麽着客戶都不會在意。
|
||||||
|
|
||||||
|
但是事實是上面的直覺推斷是錯誤的。第四種可能的結果是事實存在的,這種情況下Bob的存款會在Alice存款操作中間,在餘額被讀到(balance + amount)之後,在餘額被更新之前(balance = ...),這樣會導致Bob的交易丟失。而這是因爲Alice的存款操作A1實際上是兩個操作的一個序列,讀取然後寫;可以稱之爲A1r和A1w。下面是交叉時産生的問題:
|
||||||
|
|
||||||
|
```
|
||||||
|
Data race
|
||||||
|
0
|
||||||
|
A1r 0 ... = balance + amount
|
||||||
|
B 100
|
||||||
|
A1w 200 balance = ...
|
||||||
|
A2 "= 200"
|
||||||
|
```
|
||||||
|
|
||||||
|
在A1r之後,balance + amount會被計算爲200,所以這是A1w會寫入的值,併不受其它存款操作的榦預。最終的餘額是$200。銀行的賬戶上的資産比Bob實際的資産多了$100。(譯註:因爲丟失了Bob的存款操作,所以其實是説Bob的錢丟了)
|
||||||
|
|
||||||
|
這個程序包含了一個特定的競爭條件,叫作數據競爭。無論任何時候,隻要有兩個goroutine併發訪問同一變量,且至少其中的一個是寫操作的時候就會發生數據競爭。
|
||||||
|
|
||||||
|
如果數據競爭的對象是一個比一個機器字(譯註:32位機器上一個字=4個字節)更大的類型時,事情就變得更麻煩了,比如interface,string或者slice類型都是如此。下面的代碼會併發地更新兩個不同長度的slice:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var x []int
|
||||||
|
go func() { x = make([]int, 10) }()
|
||||||
|
go func() { x = make([]int, 1000000) }()
|
||||||
|
x[999999] = 1 // NOTE: undefined behavior; memory corruption possible!
|
||||||
|
```
|
||||||
|
|
||||||
|
最後一個語句中的x的值是未定義的;其可能是nil,或者也可能是一個長度爲10的slice,也可能是一個程度爲1,000,000的slice。但是迴憶一下slice的三個組成部分:指針(pointer)、長度(length)和容量(capacity)。如果指針是從第一個make調用來,而長度從第二個make來,x就變成了一個混合體,一個自稱長度爲1,000,000但實際上內部隻有10個元素的slice。這樣導致的結果是存儲999,999元素的位置會碰撞一個遙遠的內存位置,這種情況下難以對值進行預測,而且定位和debug也會變成噩夢。這種語義雷區被稱爲未定義行爲,對C程序員來説應該很熟悉;幸運的是在Go語言里造成的麻煩要比C里小得多。
|
||||||
|
|
||||||
|
盡管併發程序的概念讓我們知道併發併不是簡單的語句交叉執行。我們將會在9.4節中看到,數據競爭可能會有奇怪的結果。許多程序員,甚至一些非常聰明的人也還是會偶爾提出一些理由來允許數據競爭,比如:“互斥條件代價太高”,“這個邏輯隻是用來做logging”,“我不介意丟失一些消息”等等。因爲在他們的編譯器或者平台上很少遇到問題,可能給了他們錯誤的信心。一個好的經驗法則是根本就沒有什麽所謂的良性數據競爭。所以我們一定要避免數據競爭,那麽在我們的程序中要如何做到呢?
|
||||||
|
|
||||||
|
我們來重複一下數據競爭的定義,因爲實在太重要了:數據競爭會在兩個以上的goroutine併發訪問相同的變量且至少其中一個爲寫操作時發生。根據上述定義,有三種方式可以避免數據競爭:
|
||||||
|
|
||||||
|
第一種方法是不要去寫變量。考慮一下下面的map,會被“懶”填充,也就是説在每個key被第一次請求到的時候才會去填值。如果Icon是被順序調用的話,這個程序會工作很正常,但如果Icon被併發調用,那麽對於這個map來説就會存在數據競爭。
|
||||||
|
|
||||||
|
```go
|
||||||
|
var icons = make(map[string]image.Image)
|
||||||
|
func loadIcon(name string) image.Image
|
||||||
|
|
||||||
|
// NOTE: not concurrency-safe!
|
||||||
|
func Icon(name string) image.Image {
|
||||||
|
icon, ok := icons[name]
|
||||||
|
if !ok {
|
||||||
|
icon = loadIcon(name)
|
||||||
|
icons[name] = icon
|
||||||
|
}
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
反之,如果我們在創建goroutine之前的初始化階段,就初始化了map中的所有條目併且再也不去脩改它們,那麽任意數量的goroutine併發訪問Icon都是安全的,因爲每一個goroutine都隻是去讀取而已。
|
||||||
|
|
||||||
|
```go
|
||||||
|
var icons = map[string]image.Image{
|
||||||
|
"spades.png": loadIcon("spades.png"),
|
||||||
|
"hearts.png": loadIcon("hearts.png"),
|
||||||
|
"diamonds.png": loadIcon("diamonds.png"),
|
||||||
|
"clubs.png": loadIcon("clubs.png"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concurrency-safe.
|
||||||
|
func Icon(name string) image.Image { return icons[name] }
|
||||||
|
```
|
||||||
|
|
||||||
|
上面的例子里icons變量在包初始化階段就已經被賦值了,包的初始化是在程序main函數開始執行之前就完成了的。隻要初始化完成了,icons就再也不會脩改的或者不變量是本來就併發安全的,這種變量不需要進行同步。不過顯然我們沒法用這種方法,因爲update操作是必要的操作,尤其對於銀行賬戶來説。
|
||||||
|
|
||||||
|
第二種避免數據競爭的方法是,避免從多個goroutine訪問變量。這也是前一章中大多數程序所采用的方法。例如前面的併發web爬蟲(§8.6)的main goroutine是唯一一個能夠訪問seen map的goroutine,而聊天服務器(§8.10)中的broadcaster goroutine是唯一一個能夠訪問clients map的goroutine。這些變量都被限定在了一個單獨的goroutine中。
|
||||||
|
|
||||||
|
由於其它的goroutine不能夠直接訪問變量,它們隻能使用一個channel來發送給指定的goroutine請求來査詢更新變量。這也就是Go的口頭禪“不要使用共享數據來通信;使用通信來共享數據”。一個提供對一個指定的變量通過cahnnel來請求的goroutine叫做這個變量的監控(monitor)goroutine。例如broadcaster goroutine會監控(monitor)clients map的全部訪問。
|
||||||
|
|
||||||
|
下面是一個重寫了的銀行的例子,這個例子中balance變量被限製在了monitor goroutine中,名爲teller:
|
||||||
|
|
||||||
|
```go
|
||||||
|
gopl.io/ch9/bank1
|
||||||
|
// Package bank provides a concurrency-safe bank with one account.
|
||||||
|
package bank
|
||||||
|
|
||||||
|
var deposits = make(chan int) // send amount to deposit
|
||||||
|
var balances = make(chan int) // receive balance
|
||||||
|
|
||||||
|
func Deposit(amount int) { deposits <- amount }
|
||||||
|
func Balance() int { return <-balances }
|
||||||
|
|
||||||
|
func teller() {
|
||||||
|
var balance int // balance is confined to teller goroutine
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case amount := <-deposits:
|
||||||
|
balance += amount
|
||||||
|
case balances <- balance:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go teller() // start the monitor goroutine
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
卽使當一個變量無法在其整個生命週期內被綁定到一個獨立的goroutine,綁定依然是併發問題的一個解決方案。例如在一條流水線上的goroutine之間共享變量是很普遍的行爲,在這兩者間會通過channel來傳輸地址信息。如果流水線的每一個階段都能夠避免在將變量傳送到下一階段時再去訪問它,那麽對這個變量的所有訪問就是線性的。其效果是變量會被綁定到流水線的一個階段,傳送完之後被綁定到下一個,以此類推。這種規則有時被稱爲串行綁定。
|
||||||
|
|
||||||
|
下面的例子中,Cakes會被嚴格地順序訪問,先是baker gorouine,然後是icer gorouine:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Cake struct{ state string }
|
||||||
|
|
||||||
|
func baker(cooked chan<- *Cake) {
|
||||||
|
for {
|
||||||
|
cake := new(Cake)
|
||||||
|
cake.state = "cooked"
|
||||||
|
cooked <- cake // baker never touches this cake again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func icer(iced chan<- *Cake, cooked <-chan *Cake) {
|
||||||
|
for cake := range cooked {
|
||||||
|
cake.state = "iced"
|
||||||
|
iced <- cake // icer never touches this cake again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
第三種避免數據競爭的方法是允許很多goroutine去訪問變量,但是在同一個時刻最多隻有一個goroutine在訪問。這種方式被稱爲“互斥”,在下一節來討論這個主題。
|
||||||
|
|
||||||
|
練習 9.1: 給gopl.io/ch9/bank1程序添加一個Withdraw(amount int)取款函數。其返迴結果應該要表明事務是成功了還是因爲沒有足夠資金失敗了。這條消息會被發送給monitor的goroutine,且消息需要包含取款的額度和一個新的channel,這個新channel會被monitor goroutine來把boolean結果發迴給Withdraw。
|
||||||
|
|
||||||
|
BIN
cover.jpg
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 301 KiB |
BIN
cover_middle.jpg
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 34 KiB |
BIN
cover_patch.png
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 62 KiB |
BIN
cover_small.jpg
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
33
mkqrcode.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2015 <chaishushan{AT}gmail.com>. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ingore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
qr "github.com/chai2010/image/qrencoder"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
gopl_zh_url = "http://golang-china.github.io/gopl-zh/"
|
||||||
|
output = "gopl-zh-qrcode.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := qr.Encode(gopl_zh_url, qr.H)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(output, c.PNG(), 0666)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("output:", output)
|
||||||
|
}
|
@ -29,13 +29,13 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本
|
|||||||
|
|
||||||
從早期提交日誌中也可以看出,Go語言是從[Ken Thompson](http://genius.cat-v.org/ken-thompson/)發明的B語言、[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)發明的C語言逐步演化過來的,是C語言家族的成員,因此很多人將Go語言稱爲21世紀的C語言。縱觀這幾年來的發展趨勢,Go語言已經成爲雲計算、雲存儲時代最重要的基礎編程語言。
|
從早期提交日誌中也可以看出,Go語言是從[Ken Thompson](http://genius.cat-v.org/ken-thompson/)發明的B語言、[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)發明的C語言逐步演化過來的,是C語言家族的成員,因此很多人將Go語言稱爲21世紀的C語言。縱觀這幾年來的發展趨勢,Go語言已經成爲雲計算、雲存儲時代最重要的基礎編程語言。
|
||||||
|
|
||||||
在C語言發明之後約5年的時間之後(1978年),[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](The C Programming Language)》,該書被譽爲C語言程序員的聖經,作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布(2009年)約5年之後(2014年開始寫作,2015年出版),由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言,如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約,那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性,併且隨着語言的深入層層遞進,對每個細節都解讀得非常細致,每一節內容都精綵不容錯過,是廣大Gopher的必讀書目。同時,大部分Go語言核心糰隊的成員都參與了該書校對工作,因此該書的質量是可以完全放心的。
|
在C語言發明之後約5年的時間之後(1978年),[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)》,該書被譽爲C語言程序員的聖經,作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布(2009年)約5年之後(2014年開始寫作,2015年出版),由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言,如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約,那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性,併且隨着語言的深入層層遞進,對每個細節都解讀得非常細致,每一節內容都精綵不容錯過,是廣大Gopher的必讀書目。大部分Go語言核心糰隊的成員都參與了該書校對工作,因此該書的質量是可以完全放心的。
|
||||||
|
|
||||||
同時,單憑閲讀和學習其語法結構併不能眞正地掌握一門編程語言,必鬚進行足夠多的編程實踐——親自編寫一些程序併研究學習别人寫的程序。要從利用Go語言良好的特性使得程序模塊化,充分利用Go的標準函數庫以Go語言自己的風格來編寫程序。書中包含了上百個精心挑選的習題,希望大家能先用自己的方式嚐試完成習題,然後再參考官方給出的解決方案。
|
同時,單憑閲讀和學習其語法結構併不能眞正地掌握一門編程語言,必鬚進行足夠多的編程實踐——親自編寫一些程序併研究學習别人寫的程序。要從利用Go語言良好的特性使得程序模塊化,充分利用Go的標準函數庫以Go語言自己的風格來編寫程序。書中包含了上百個精心挑選的習題,希望大家能先用自己的方式嚐試完成習題,然後再參考官方給出的解決方案。
|
||||||
|
|
||||||
該書英文版約從2015年10月開始公開發售,同時同步發售的還有日文版本。不過比較可惜的是,中文版併沒有在同步發售之列,甚至連中文版是否會引進、是由哪個出版社引進、卽使引進將由何人來翻譯、何時能出版都成了一個祕密。中国的Go語言社區是全球最大的Go語言社區,我們從一開始就始終緊跟着Go語言的發展腳步。我們應該也完全有能力以中国Go語言社區的力量同步完成Go語言聖經中文版的翻譯工作。與此同時,国內有很多Go語言愛好者也在積極關註該書(本人也在第一時間購買了紙質版本,[亞馬遜價格314人民幣](http://www.amazon.cn/The-Go-Programming-Language-Donovan-Alan-A-A/dp/0134190440/))。爲了Go語言的學習和交流,大家決定合作免費翻譯該書。
|
該書英文版約從2015年10月開始公開發售,其中日文版本最早參與翻譯和審校(參考致謝部分)。在2015年10月,我們併不知道中文版是否會及時引進、將由哪家出版社引進、引進將由何人來翻譯、何時能出版,這些信息都成了一個祕密。中国的Go語言社區是全球最大的Go語言社區,我們從一開始就始終緊跟着Go語言的發展腳步。我們應該也完全有能力以中国Go語言社區的力量同步完成Go語言聖經中文版的翻譯工作。與此同時,国內有很多Go語言愛好者也在積極關註該書(本人也在第一時間購買了紙質版本,[亞馬遜價格314人民幣](http://www.amazon.cn/The-Go-Programming-Language-Donovan-Alan-A-A/dp/0134190440/))。爲了Go語言的學習和交流,大家決定合作免費翻譯該書。
|
||||||
|
|
||||||
翻譯工作從2015年11月20日前後開始,到2016年1月底初步完成,前後歷時約2個月時間。其中,[chai2010](https://github.com/chai2010)翻譯了前言、第2~4章、第10~13章,[Xargin](https://github.com/cch123)翻譯了第1章、第6章、第8~9章,[CrazySssst](https://github.com/CrazySssst)翻譯了第5章,[foreversmart](https://github.com/foreversmart)翻譯了第7章,大家共同參與了基本的校驗工作,還有其他一些朋友提供了積極的反饋建議。如果大家還有任何問題或建議,可以直接到中文版項目頁面提交[Issue](https://github.com/golang-china/gopl-zh/issues),如果發現英文版原文在[勘誤](http://www.gopl.io/errata.html)中未提到的任何錯誤,可以直接去[英文版項目](https://github.com/adonovan/gopl.io/)提交。
|
翻譯工作從2015年11月20日前後開始,到2016年1月底初步完成,前後歷時約2個月時間(在其它語言版本中,全球第一個完成翻譯的,基本做到和原版同步)。其中,[chai2010](https://github.com/chai2010)翻譯了前言、第2~4章、第10~13章,[Xargin](https://github.com/cch123)翻譯了第1章、第6章、第8~9章,[CrazySssst](https://github.com/CrazySssst)翻譯了第5章,[foreversmart](https://github.com/foreversmart)翻譯了第7章,大家共同參與了基本的校驗工作,還有其他一些朋友提供了積極的反饋建議。如果大家還有任何問題或建議,可以直接到中文版項目頁面提交[Issue](https://github.com/golang-china/gopl-zh/issues),如果發現英文版原文在[勘誤](http://www.gopl.io/errata.html)中未提到的任何錯誤,可以直接去[英文版項目](https://github.com/adonovan/gopl.io/)提交。
|
||||||
|
|
||||||
最後,希望這本書能夠幫助大家用Go語言快樂地編程。
|
最後,希望這本書能夠幫助大家用Go語言快樂地編程。
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
- [x] 5.7 Variadic Functions
|
- [x] 5.7 Variadic Functions
|
||||||
- [x] 5.8 Deferred Function Calls
|
- [x] 5.8 Deferred Function Calls
|
||||||
- [x] 5.9 Panic
|
- [x] 5.9 Panic
|
||||||
- [ ] 5.10 Recover
|
- [x] 5.10 Recover
|
||||||
- [x] Chapter 6: Methods
|
- [x] Chapter 6: Methods
|
||||||
- [x] 6.1 Method Declarations
|
- [x] 6.1 Method Declarations
|
||||||
- [x] 6.2 Methods with a Pointer Receiver
|
- [x] 6.2 Methods with a Pointer Receiver
|
||||||
@ -56,7 +56,7 @@
|
|||||||
- [x] 7.3 Interface Satisfaction
|
- [x] 7.3 Interface Satisfaction
|
||||||
- [x] 7.4 Parsing Flags with flag.Value
|
- [x] 7.4 Parsing Flags with flag.Value
|
||||||
- [x] 7.5 Interface Values
|
- [x] 7.5 Interface Values
|
||||||
- [ ] 7.6 Sorting with sort.Interface
|
- [x] 7.6 Sorting with sort.Interface
|
||||||
- [ ] 7.7 The http.Handler Interface
|
- [ ] 7.7 The http.Handler Interface
|
||||||
- [ ] 7.8 The error Interface
|
- [ ] 7.8 The error Interface
|
||||||
- [ ] 7.9 Example: Expression Evaluator
|
- [ ] 7.9 Example: Expression Evaluator
|
||||||
@ -78,7 +78,7 @@
|
|||||||
- [x] 8.9 Cancellation
|
- [x] 8.9 Cancellation
|
||||||
- [x] 8.10 Example: Chat Server
|
- [x] 8.10 Example: Chat Server
|
||||||
- [x] Chapter 9: Concurrency with Shared Variables
|
- [x] Chapter 9: Concurrency with Shared Variables
|
||||||
- [ ] 9.1 Race Conditions
|
- [x] 9.1 Race Conditions
|
||||||
- [ ] 9.2 Mutual Exclusion: sync.Mutex
|
- [ ] 9.2 Mutual Exclusion: sync.Mutex
|
||||||
- [x] 9.3 Read/Write Mutexes: sync.RWMutex
|
- [x] 9.3 Read/Write Mutexes: sync.RWMutex
|
||||||
- [x] 9.4 Memory Synchronization
|
- [x] 9.4 Memory Synchronization
|
||||||
@ -117,4 +117,4 @@
|
|||||||
- [x] 13.3 Example: Deep Equivalence
|
- [x] 13.3 Example: Deep Equivalence
|
||||||
- [x] 13.4 Calling C Code with cgo
|
- [x] 13.4 Calling C Code with cgo
|
||||||
- [x] 13.5 Another Word of Caution
|
- [x] 13.5 Another Word of Caution
|
||||||
- [ ] Errata
|
|
||||||
|
28
vendor/github.com/chai2010/image/qrencoder/hello.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2015 <chaishushan{AT}gmail.com>. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ingore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
qr "github.com/chai2010/image/qrencoder"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := qr.Encode("hello, world", qr.L)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile("zz_qrout.png", c.PNG(), 0666)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("output: zz_qrout.png\n")
|
||||||
|
}
|
149
vendor/github.com/chai2010/image/qrencoder/internal/coding/gen.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// tables from qrencode-3.1.1/qrspec.c
|
||||||
|
|
||||||
|
var capacity = [41]struct {
|
||||||
|
width int
|
||||||
|
words int
|
||||||
|
remainder int
|
||||||
|
ec [4]int
|
||||||
|
}{
|
||||||
|
{0, 0, 0, [4]int{0, 0, 0, 0}},
|
||||||
|
{21, 26, 0, [4]int{7, 10, 13, 17}}, // 1
|
||||||
|
{25, 44, 7, [4]int{10, 16, 22, 28}},
|
||||||
|
{29, 70, 7, [4]int{15, 26, 36, 44}},
|
||||||
|
{33, 100, 7, [4]int{20, 36, 52, 64}},
|
||||||
|
{37, 134, 7, [4]int{26, 48, 72, 88}}, // 5
|
||||||
|
{41, 172, 7, [4]int{36, 64, 96, 112}},
|
||||||
|
{45, 196, 0, [4]int{40, 72, 108, 130}},
|
||||||
|
{49, 242, 0, [4]int{48, 88, 132, 156}},
|
||||||
|
{53, 292, 0, [4]int{60, 110, 160, 192}},
|
||||||
|
{57, 346, 0, [4]int{72, 130, 192, 224}}, //10
|
||||||
|
{61, 404, 0, [4]int{80, 150, 224, 264}},
|
||||||
|
{65, 466, 0, [4]int{96, 176, 260, 308}},
|
||||||
|
{69, 532, 0, [4]int{104, 198, 288, 352}},
|
||||||
|
{73, 581, 3, [4]int{120, 216, 320, 384}},
|
||||||
|
{77, 655, 3, [4]int{132, 240, 360, 432}}, //15
|
||||||
|
{81, 733, 3, [4]int{144, 280, 408, 480}},
|
||||||
|
{85, 815, 3, [4]int{168, 308, 448, 532}},
|
||||||
|
{89, 901, 3, [4]int{180, 338, 504, 588}},
|
||||||
|
{93, 991, 3, [4]int{196, 364, 546, 650}},
|
||||||
|
{97, 1085, 3, [4]int{224, 416, 600, 700}}, //20
|
||||||
|
{101, 1156, 4, [4]int{224, 442, 644, 750}},
|
||||||
|
{105, 1258, 4, [4]int{252, 476, 690, 816}},
|
||||||
|
{109, 1364, 4, [4]int{270, 504, 750, 900}},
|
||||||
|
{113, 1474, 4, [4]int{300, 560, 810, 960}},
|
||||||
|
{117, 1588, 4, [4]int{312, 588, 870, 1050}}, //25
|
||||||
|
{121, 1706, 4, [4]int{336, 644, 952, 1110}},
|
||||||
|
{125, 1828, 4, [4]int{360, 700, 1020, 1200}},
|
||||||
|
{129, 1921, 3, [4]int{390, 728, 1050, 1260}},
|
||||||
|
{133, 2051, 3, [4]int{420, 784, 1140, 1350}},
|
||||||
|
{137, 2185, 3, [4]int{450, 812, 1200, 1440}}, //30
|
||||||
|
{141, 2323, 3, [4]int{480, 868, 1290, 1530}},
|
||||||
|
{145, 2465, 3, [4]int{510, 924, 1350, 1620}},
|
||||||
|
{149, 2611, 3, [4]int{540, 980, 1440, 1710}},
|
||||||
|
{153, 2761, 3, [4]int{570, 1036, 1530, 1800}},
|
||||||
|
{157, 2876, 0, [4]int{570, 1064, 1590, 1890}}, //35
|
||||||
|
{161, 3034, 0, [4]int{600, 1120, 1680, 1980}},
|
||||||
|
{165, 3196, 0, [4]int{630, 1204, 1770, 2100}},
|
||||||
|
{169, 3362, 0, [4]int{660, 1260, 1860, 2220}},
|
||||||
|
{173, 3532, 0, [4]int{720, 1316, 1950, 2310}},
|
||||||
|
{177, 3706, 0, [4]int{750, 1372, 2040, 2430}}, //40
|
||||||
|
}
|
||||||
|
|
||||||
|
var eccTable = [41][4][2]int{
|
||||||
|
{{0, 0}, {0, 0}, {0, 0}, {0, 0}},
|
||||||
|
{{1, 0}, {1, 0}, {1, 0}, {1, 0}}, // 1
|
||||||
|
{{1, 0}, {1, 0}, {1, 0}, {1, 0}},
|
||||||
|
{{1, 0}, {1, 0}, {2, 0}, {2, 0}},
|
||||||
|
{{1, 0}, {2, 0}, {2, 0}, {4, 0}},
|
||||||
|
{{1, 0}, {2, 0}, {2, 2}, {2, 2}}, // 5
|
||||||
|
{{2, 0}, {4, 0}, {4, 0}, {4, 0}},
|
||||||
|
{{2, 0}, {4, 0}, {2, 4}, {4, 1}},
|
||||||
|
{{2, 0}, {2, 2}, {4, 2}, {4, 2}},
|
||||||
|
{{2, 0}, {3, 2}, {4, 4}, {4, 4}},
|
||||||
|
{{2, 2}, {4, 1}, {6, 2}, {6, 2}}, //10
|
||||||
|
{{4, 0}, {1, 4}, {4, 4}, {3, 8}},
|
||||||
|
{{2, 2}, {6, 2}, {4, 6}, {7, 4}},
|
||||||
|
{{4, 0}, {8, 1}, {8, 4}, {12, 4}},
|
||||||
|
{{3, 1}, {4, 5}, {11, 5}, {11, 5}},
|
||||||
|
{{5, 1}, {5, 5}, {5, 7}, {11, 7}}, //15
|
||||||
|
{{5, 1}, {7, 3}, {15, 2}, {3, 13}},
|
||||||
|
{{1, 5}, {10, 1}, {1, 15}, {2, 17}},
|
||||||
|
{{5, 1}, {9, 4}, {17, 1}, {2, 19}},
|
||||||
|
{{3, 4}, {3, 11}, {17, 4}, {9, 16}},
|
||||||
|
{{3, 5}, {3, 13}, {15, 5}, {15, 10}}, //20
|
||||||
|
{{4, 4}, {17, 0}, {17, 6}, {19, 6}},
|
||||||
|
{{2, 7}, {17, 0}, {7, 16}, {34, 0}},
|
||||||
|
{{4, 5}, {4, 14}, {11, 14}, {16, 14}},
|
||||||
|
{{6, 4}, {6, 14}, {11, 16}, {30, 2}},
|
||||||
|
{{8, 4}, {8, 13}, {7, 22}, {22, 13}}, //25
|
||||||
|
{{10, 2}, {19, 4}, {28, 6}, {33, 4}},
|
||||||
|
{{8, 4}, {22, 3}, {8, 26}, {12, 28}},
|
||||||
|
{{3, 10}, {3, 23}, {4, 31}, {11, 31}},
|
||||||
|
{{7, 7}, {21, 7}, {1, 37}, {19, 26}},
|
||||||
|
{{5, 10}, {19, 10}, {15, 25}, {23, 25}}, //30
|
||||||
|
{{13, 3}, {2, 29}, {42, 1}, {23, 28}},
|
||||||
|
{{17, 0}, {10, 23}, {10, 35}, {19, 35}},
|
||||||
|
{{17, 1}, {14, 21}, {29, 19}, {11, 46}},
|
||||||
|
{{13, 6}, {14, 23}, {44, 7}, {59, 1}},
|
||||||
|
{{12, 7}, {12, 26}, {39, 14}, {22, 41}}, //35
|
||||||
|
{{6, 14}, {6, 34}, {46, 10}, {2, 64}},
|
||||||
|
{{17, 4}, {29, 14}, {49, 10}, {24, 46}},
|
||||||
|
{{4, 18}, {13, 32}, {48, 14}, {42, 32}},
|
||||||
|
{{20, 4}, {40, 7}, {43, 22}, {10, 67}},
|
||||||
|
{{19, 6}, {18, 31}, {34, 34}, {20, 61}}, //40
|
||||||
|
}
|
||||||
|
|
||||||
|
var align = [41][2]int{
|
||||||
|
{0, 0},
|
||||||
|
{0, 0}, {18, 0}, {22, 0}, {26, 0}, {30, 0}, // 1- 5
|
||||||
|
{34, 0}, {22, 38}, {24, 42}, {26, 46}, {28, 50}, // 6-10
|
||||||
|
{30, 54}, {32, 58}, {34, 62}, {26, 46}, {26, 48}, //11-15
|
||||||
|
{26, 50}, {30, 54}, {30, 56}, {30, 58}, {34, 62}, //16-20
|
||||||
|
{28, 50}, {26, 50}, {30, 54}, {28, 54}, {32, 58}, //21-25
|
||||||
|
{30, 58}, {34, 62}, {26, 50}, {30, 54}, {26, 52}, //26-30
|
||||||
|
{30, 56}, {34, 60}, {30, 58}, {34, 62}, {30, 54}, //31-35
|
||||||
|
{24, 50}, {28, 54}, {32, 58}, {26, 54}, {30, 58}, //35-40
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionPattern = [41]int{
|
||||||
|
0,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
|
||||||
|
0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
|
||||||
|
0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
|
||||||
|
0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
|
||||||
|
0x27541, 0x28c69,
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Printf("\t{},\n")
|
||||||
|
for i := 1; i <= 40; i++ {
|
||||||
|
apos := align[i][0] - 2
|
||||||
|
if apos < 0 {
|
||||||
|
apos = 100
|
||||||
|
}
|
||||||
|
astride := align[i][1] - align[i][0]
|
||||||
|
if astride < 1 {
|
||||||
|
astride = 100
|
||||||
|
}
|
||||||
|
fmt.Printf("\t{%v, %v, %v, %#x, [4]level{{%v, %v}, {%v, %v}, {%v, %v}, {%v, %v}}}, // %v\n",
|
||||||
|
apos, astride, capacity[i].words,
|
||||||
|
versionPattern[i],
|
||||||
|
eccTable[i][0][0]+eccTable[i][0][1],
|
||||||
|
float64(capacity[i].ec[0])/float64(eccTable[i][0][0]+eccTable[i][0][1]),
|
||||||
|
eccTable[i][1][0]+eccTable[i][1][1],
|
||||||
|
float64(capacity[i].ec[1])/float64(eccTable[i][1][0]+eccTable[i][1][1]),
|
||||||
|
eccTable[i][2][0]+eccTable[i][2][1],
|
||||||
|
float64(capacity[i].ec[2])/float64(eccTable[i][2][0]+eccTable[i][2][1]),
|
||||||
|
eccTable[i][3][0]+eccTable[i][3][1],
|
||||||
|
float64(capacity[i].ec[3])/float64(eccTable[i][3][0]+eccTable[i][3][1]),
|
||||||
|
i,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
815
vendor/github.com/chai2010/image/qrencoder/internal/coding/qr.go
generated
vendored
Normal file
@ -0,0 +1,815 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package coding implements low-level QR coding details.
|
||||||
|
package coding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chai2010/image/qrencoder/internal/gf256"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Field is the field for QR error correction.
|
||||||
|
var Field = gf256.NewField(0x11d, 2)
|
||||||
|
|
||||||
|
// A Version represents a QR version.
|
||||||
|
// The version specifies the size of the QR code:
|
||||||
|
// a QR code with version v has 4v+17 pixels on a side.
|
||||||
|
// Versions number from 1 to 40: the larger the version,
|
||||||
|
// the more information the code can store.
|
||||||
|
type Version int
|
||||||
|
|
||||||
|
const MinVersion = 1
|
||||||
|
const MaxVersion = 40
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
return strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) sizeClass() int {
|
||||||
|
if v <= 9 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if v <= 26 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataBytes returns the number of data bytes that can be
|
||||||
|
// stored in a QR code with the given version and level.
|
||||||
|
func (v Version) DataBytes(l Level) int {
|
||||||
|
vt := &vtab[v]
|
||||||
|
lev := &vt.level[l]
|
||||||
|
return vt.bytes - lev.nblock*lev.check
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding implements a QR data encoding scheme.
|
||||||
|
// The implementations--Numeric, Alphanumeric, and String--specify
|
||||||
|
// the character set and the mapping from UTF-8 to code bits.
|
||||||
|
// The more restrictive the mode, the fewer code bits are needed.
|
||||||
|
type Encoding interface {
|
||||||
|
Check() error
|
||||||
|
Bits(v Version) int
|
||||||
|
Encode(b *Bits, v Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bits struct {
|
||||||
|
b []byte
|
||||||
|
nbit int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Reset() {
|
||||||
|
b.b = b.b[:0]
|
||||||
|
b.nbit = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Bits() int {
|
||||||
|
return b.nbit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Bytes() []byte {
|
||||||
|
if b.nbit%8 != 0 {
|
||||||
|
panic("fractional byte")
|
||||||
|
}
|
||||||
|
return b.b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Append(p []byte) {
|
||||||
|
if b.nbit%8 != 0 {
|
||||||
|
panic("fractional byte")
|
||||||
|
}
|
||||||
|
b.b = append(b.b, p...)
|
||||||
|
b.nbit += 8 * len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Write(v uint, nbit int) {
|
||||||
|
for nbit > 0 {
|
||||||
|
n := nbit
|
||||||
|
if n > 8 {
|
||||||
|
n = 8
|
||||||
|
}
|
||||||
|
if b.nbit%8 == 0 {
|
||||||
|
b.b = append(b.b, 0)
|
||||||
|
} else {
|
||||||
|
m := -b.nbit & 7
|
||||||
|
if n > m {
|
||||||
|
n = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.nbit += n
|
||||||
|
sh := uint(nbit - n)
|
||||||
|
b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7))
|
||||||
|
v -= v >> sh << sh
|
||||||
|
nbit -= n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Num is the encoding for numeric data.
|
||||||
|
// The only valid characters are the decimal digits 0 through 9.
|
||||||
|
type Num string
|
||||||
|
|
||||||
|
func (s Num) String() string {
|
||||||
|
return fmt.Sprintf("Num(%#q)", string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Num) Check() error {
|
||||||
|
for _, c := range s {
|
||||||
|
if c < '0' || '9' < c {
|
||||||
|
return fmt.Errorf("non-numeric string %#q", string(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var numLen = [3]int{10, 12, 14}
|
||||||
|
|
||||||
|
func (s Num) Bits(v Version) int {
|
||||||
|
return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Num) Encode(b *Bits, v Version) {
|
||||||
|
b.Write(1, 4)
|
||||||
|
b.Write(uint(len(s)), numLen[v.sizeClass()])
|
||||||
|
var i int
|
||||||
|
for i = 0; i+3 <= len(s); i += 3 {
|
||||||
|
w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0')
|
||||||
|
b.Write(w, 10)
|
||||||
|
}
|
||||||
|
switch len(s) - i {
|
||||||
|
case 1:
|
||||||
|
w := uint(s[i] - '0')
|
||||||
|
b.Write(w, 4)
|
||||||
|
case 2:
|
||||||
|
w := uint(s[i]-'0')*10 + uint(s[i+1]-'0')
|
||||||
|
b.Write(w, 7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alpha is the encoding for alphanumeric data.
|
||||||
|
// The valid characters are 0-9A-Z$%*+-./: and space.
|
||||||
|
type Alpha string
|
||||||
|
|
||||||
|
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
||||||
|
|
||||||
|
func (s Alpha) String() string {
|
||||||
|
return fmt.Sprintf("Alpha(%#q)", string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Alpha) Check() error {
|
||||||
|
for _, c := range s {
|
||||||
|
if strings.IndexRune(alphabet, c) < 0 {
|
||||||
|
return fmt.Errorf("non-alphanumeric string %#q", string(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var alphaLen = [3]int{9, 11, 13}
|
||||||
|
|
||||||
|
func (s Alpha) Bits(v Version) int {
|
||||||
|
return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Alpha) Encode(b *Bits, v Version) {
|
||||||
|
b.Write(2, 4)
|
||||||
|
b.Write(uint(len(s)), alphaLen[v.sizeClass()])
|
||||||
|
var i int
|
||||||
|
for i = 0; i+2 <= len(s); i += 2 {
|
||||||
|
w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 +
|
||||||
|
uint(strings.IndexRune(alphabet, rune(s[i+1])))
|
||||||
|
b.Write(w, 11)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < len(s) {
|
||||||
|
w := uint(strings.IndexRune(alphabet, rune(s[i])))
|
||||||
|
b.Write(w, 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the encoding for 8-bit data. All bytes are valid.
|
||||||
|
type String string
|
||||||
|
|
||||||
|
func (s String) String() string {
|
||||||
|
return fmt.Sprintf("String(%#q)", string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s String) Check() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringLen = [3]int{8, 16, 16}
|
||||||
|
|
||||||
|
func (s String) Bits(v Version) int {
|
||||||
|
return 4 + stringLen[v.sizeClass()] + 8*len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s String) Encode(b *Bits, v Version) {
|
||||||
|
b.Write(4, 4)
|
||||||
|
b.Write(uint(len(s)), stringLen[v.sizeClass()])
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
b.Write(uint(s[i]), 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Pixel describes a single pixel in a QR code.
|
||||||
|
type Pixel uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Black Pixel = 1 << iota
|
||||||
|
Invert
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p Pixel) Offset() uint {
|
||||||
|
return uint(p >> 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OffsetPixel(o uint) Pixel {
|
||||||
|
return Pixel(o << 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PixelRole) Pixel() Pixel {
|
||||||
|
return Pixel(r << 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pixel) Role() PixelRole {
|
||||||
|
return PixelRole(p>>2) & 15
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pixel) String() string {
|
||||||
|
s := p.Role().String()
|
||||||
|
if p&Black != 0 {
|
||||||
|
s += "+black"
|
||||||
|
}
|
||||||
|
if p&Invert != 0 {
|
||||||
|
s += "+invert"
|
||||||
|
}
|
||||||
|
s += "+" + strconv.FormatUint(uint64(p.Offset()), 10)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PixelRole describes the role of a QR pixel.
|
||||||
|
type PixelRole uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ PixelRole = iota
|
||||||
|
Position // position squares (large)
|
||||||
|
Alignment // alignment squares (small)
|
||||||
|
Timing // timing strip between position squares
|
||||||
|
Format // format metadata
|
||||||
|
PVersion // version pattern
|
||||||
|
Unused // unused pixel
|
||||||
|
Data // data bit
|
||||||
|
Check // error correction check bit
|
||||||
|
Extra
|
||||||
|
)
|
||||||
|
|
||||||
|
var roles = []string{
|
||||||
|
"",
|
||||||
|
"position",
|
||||||
|
"alignment",
|
||||||
|
"timing",
|
||||||
|
"format",
|
||||||
|
"pversion",
|
||||||
|
"unused",
|
||||||
|
"data",
|
||||||
|
"check",
|
||||||
|
"extra",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PixelRole) String() string {
|
||||||
|
if Position <= r && r <= Check {
|
||||||
|
return roles[r]
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Level represents a QR error correction level.
|
||||||
|
// From least to most tolerant of errors, they are L, M, Q, H.
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
L Level = iota
|
||||||
|
M
|
||||||
|
Q
|
||||||
|
H
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l Level) String() string {
|
||||||
|
if L <= l && l <= H {
|
||||||
|
return "LMQH"[l : l+1]
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Code is a square pixel grid.
|
||||||
|
type Code struct {
|
||||||
|
Bitmap []byte // 1 is black, 0 is white
|
||||||
|
Size int // number of pixels on a side
|
||||||
|
Stride int // number of bytes per row
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Code) Black(x, y int) bool {
|
||||||
|
return 0 <= x && x < c.Size && 0 <= y && y < c.Size &&
|
||||||
|
c.Bitmap[y*c.Stride+x/8]&(1<<uint(7-x&7)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Mask describes a mask that is applied to the QR
|
||||||
|
// code to avoid QR artifacts being interpreted as
|
||||||
|
// alignment and timing patterns (such as the squares
|
||||||
|
// in the corners). Valid masks are integers from 0 to 7.
|
||||||
|
type Mask int
|
||||||
|
|
||||||
|
// http://www.swetake.com/qr/qr5_en.html
|
||||||
|
var mfunc = []func(int, int) bool{
|
||||||
|
func(i, j int) bool { return (i+j)%2 == 0 },
|
||||||
|
func(i, j int) bool { return i%2 == 0 },
|
||||||
|
func(i, j int) bool { return j%3 == 0 },
|
||||||
|
func(i, j int) bool { return (i+j)%3 == 0 },
|
||||||
|
func(i, j int) bool { return (i/2+j/3)%2 == 0 },
|
||||||
|
func(i, j int) bool { return i*j%2+i*j%3 == 0 },
|
||||||
|
func(i, j int) bool { return (i*j%2+i*j%3)%2 == 0 },
|
||||||
|
func(i, j int) bool { return (i*j%3+(i+j)%2)%2 == 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Mask) Invert(y, x int) bool {
|
||||||
|
if m < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return mfunc[m](y, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Plan describes how to construct a QR code
|
||||||
|
// with a specific version, level, and mask.
|
||||||
|
type Plan struct {
|
||||||
|
Version Version
|
||||||
|
Level Level
|
||||||
|
Mask Mask
|
||||||
|
|
||||||
|
DataBytes int // number of data bytes
|
||||||
|
CheckBytes int // number of error correcting (checksum) bytes
|
||||||
|
Blocks int // number of data blocks
|
||||||
|
|
||||||
|
Pixel [][]Pixel // pixel map
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPlan returns a Plan for a QR code with the given
|
||||||
|
// version, level, and mask.
|
||||||
|
func NewPlan(version Version, level Level, mask Mask) (*Plan, error) {
|
||||||
|
p, err := vplan(version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := fplan(level, mask, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := lplan(version, level, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := mplan(mask, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) Pad(n int) {
|
||||||
|
if n < 0 {
|
||||||
|
panic("qr: invalid pad size")
|
||||||
|
}
|
||||||
|
if n <= 4 {
|
||||||
|
b.Write(0, n)
|
||||||
|
} else {
|
||||||
|
b.Write(0, 4)
|
||||||
|
n -= 4
|
||||||
|
n -= -b.Bits() & 7
|
||||||
|
b.Write(0, -b.Bits()&7)
|
||||||
|
pad := n / 8
|
||||||
|
for i := 0; i < pad; i += 2 {
|
||||||
|
b.Write(0xec, 8)
|
||||||
|
if i+1 >= pad {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.Write(0x11, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) AddCheckBytes(v Version, l Level) {
|
||||||
|
nd := v.DataBytes(l)
|
||||||
|
if b.nbit < nd*8 {
|
||||||
|
b.Pad(nd*8 - b.nbit)
|
||||||
|
}
|
||||||
|
if b.nbit != nd*8 {
|
||||||
|
panic("qr: too much data")
|
||||||
|
}
|
||||||
|
|
||||||
|
dat := b.Bytes()
|
||||||
|
vt := &vtab[v]
|
||||||
|
lev := &vt.level[l]
|
||||||
|
db := nd / lev.nblock
|
||||||
|
extra := nd % lev.nblock
|
||||||
|
chk := make([]byte, lev.check)
|
||||||
|
rs := gf256.NewRSEncoder(Field, lev.check)
|
||||||
|
for i := 0; i < lev.nblock; i++ {
|
||||||
|
if i == lev.nblock-extra {
|
||||||
|
db++
|
||||||
|
}
|
||||||
|
rs.ECC(dat[:db], chk)
|
||||||
|
b.Append(chk)
|
||||||
|
dat = dat[db:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.Bytes()) != vt.bytes {
|
||||||
|
panic("qr: internal error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plan) Encode(text ...Encoding) (*Code, error) {
|
||||||
|
var b Bits
|
||||||
|
for _, t := range text {
|
||||||
|
if err := t.Check(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.Encode(&b, p.Version)
|
||||||
|
}
|
||||||
|
if b.Bits() > p.DataBytes*8 {
|
||||||
|
return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8)
|
||||||
|
}
|
||||||
|
b.AddCheckBytes(p.Version, p.Level)
|
||||||
|
bytes := b.Bytes()
|
||||||
|
|
||||||
|
// Now we have the checksum bytes and the data bytes.
|
||||||
|
// Construct the actual code.
|
||||||
|
c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7}
|
||||||
|
c.Bitmap = make([]byte, c.Stride*c.Size)
|
||||||
|
crow := c.Bitmap
|
||||||
|
for _, row := range p.Pixel {
|
||||||
|
for x, pix := range row {
|
||||||
|
switch pix.Role() {
|
||||||
|
case Data, Check:
|
||||||
|
o := pix.Offset()
|
||||||
|
if bytes[o/8]&(1<<uint(7-o&7)) != 0 {
|
||||||
|
pix ^= Black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pix&Black != 0 {
|
||||||
|
crow[x/8] |= 1 << uint(7-x&7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crow = crow[c.Stride:]
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A version describes metadata associated with a version.
|
||||||
|
type version struct {
|
||||||
|
apos int
|
||||||
|
astride int
|
||||||
|
bytes int
|
||||||
|
pattern int
|
||||||
|
level [4]level
|
||||||
|
}
|
||||||
|
|
||||||
|
type level struct {
|
||||||
|
nblock int
|
||||||
|
check int
|
||||||
|
}
|
||||||
|
|
||||||
|
var vtab = []version{
|
||||||
|
{},
|
||||||
|
{100, 100, 26, 0x0, [4]level{{1, 7}, {1, 10}, {1, 13}, {1, 17}}}, // 1
|
||||||
|
{16, 100, 44, 0x0, [4]level{{1, 10}, {1, 16}, {1, 22}, {1, 28}}}, // 2
|
||||||
|
{20, 100, 70, 0x0, [4]level{{1, 15}, {1, 26}, {2, 18}, {2, 22}}}, // 3
|
||||||
|
{24, 100, 100, 0x0, [4]level{{1, 20}, {2, 18}, {2, 26}, {4, 16}}}, // 4
|
||||||
|
{28, 100, 134, 0x0, [4]level{{1, 26}, {2, 24}, {4, 18}, {4, 22}}}, // 5
|
||||||
|
{32, 100, 172, 0x0, [4]level{{2, 18}, {4, 16}, {4, 24}, {4, 28}}}, // 6
|
||||||
|
{20, 16, 196, 0x7c94, [4]level{{2, 20}, {4, 18}, {6, 18}, {5, 26}}}, // 7
|
||||||
|
{22, 18, 242, 0x85bc, [4]level{{2, 24}, {4, 22}, {6, 22}, {6, 26}}}, // 8
|
||||||
|
{24, 20, 292, 0x9a99, [4]level{{2, 30}, {5, 22}, {8, 20}, {8, 24}}}, // 9
|
||||||
|
{26, 22, 346, 0xa4d3, [4]level{{4, 18}, {5, 26}, {8, 24}, {8, 28}}}, // 10
|
||||||
|
{28, 24, 404, 0xbbf6, [4]level{{4, 20}, {5, 30}, {8, 28}, {11, 24}}}, // 11
|
||||||
|
{30, 26, 466, 0xc762, [4]level{{4, 24}, {8, 22}, {10, 26}, {11, 28}}}, // 12
|
||||||
|
{32, 28, 532, 0xd847, [4]level{{4, 26}, {9, 22}, {12, 24}, {16, 22}}}, // 13
|
||||||
|
{24, 20, 581, 0xe60d, [4]level{{4, 30}, {9, 24}, {16, 20}, {16, 24}}}, // 14
|
||||||
|
{24, 22, 655, 0xf928, [4]level{{6, 22}, {10, 24}, {12, 30}, {18, 24}}}, // 15
|
||||||
|
{24, 24, 733, 0x10b78, [4]level{{6, 24}, {10, 28}, {17, 24}, {16, 30}}}, // 16
|
||||||
|
{28, 24, 815, 0x1145d, [4]level{{6, 28}, {11, 28}, {16, 28}, {19, 28}}}, // 17
|
||||||
|
{28, 26, 901, 0x12a17, [4]level{{6, 30}, {13, 26}, {18, 28}, {21, 28}}}, // 18
|
||||||
|
{28, 28, 991, 0x13532, [4]level{{7, 28}, {14, 26}, {21, 26}, {25, 26}}}, // 19
|
||||||
|
{32, 28, 1085, 0x149a6, [4]level{{8, 28}, {16, 26}, {20, 30}, {25, 28}}}, // 20
|
||||||
|
{26, 22, 1156, 0x15683, [4]level{{8, 28}, {17, 26}, {23, 28}, {25, 30}}}, // 21
|
||||||
|
{24, 24, 1258, 0x168c9, [4]level{{9, 28}, {17, 28}, {23, 30}, {34, 24}}}, // 22
|
||||||
|
{28, 24, 1364, 0x177ec, [4]level{{9, 30}, {18, 28}, {25, 30}, {30, 30}}}, // 23
|
||||||
|
{26, 26, 1474, 0x18ec4, [4]level{{10, 30}, {20, 28}, {27, 30}, {32, 30}}}, // 24
|
||||||
|
{30, 26, 1588, 0x191e1, [4]level{{12, 26}, {21, 28}, {29, 30}, {35, 30}}}, // 25
|
||||||
|
{28, 28, 1706, 0x1afab, [4]level{{12, 28}, {23, 28}, {34, 28}, {37, 30}}}, // 26
|
||||||
|
{32, 28, 1828, 0x1b08e, [4]level{{12, 30}, {25, 28}, {34, 30}, {40, 30}}}, // 27
|
||||||
|
{24, 24, 1921, 0x1cc1a, [4]level{{13, 30}, {26, 28}, {35, 30}, {42, 30}}}, // 28
|
||||||
|
{28, 24, 2051, 0x1d33f, [4]level{{14, 30}, {28, 28}, {38, 30}, {45, 30}}}, // 29
|
||||||
|
{24, 26, 2185, 0x1ed75, [4]level{{15, 30}, {29, 28}, {40, 30}, {48, 30}}}, // 30
|
||||||
|
{28, 26, 2323, 0x1f250, [4]level{{16, 30}, {31, 28}, {43, 30}, {51, 30}}}, // 31
|
||||||
|
{32, 26, 2465, 0x209d5, [4]level{{17, 30}, {33, 28}, {45, 30}, {54, 30}}}, // 32
|
||||||
|
{28, 28, 2611, 0x216f0, [4]level{{18, 30}, {35, 28}, {48, 30}, {57, 30}}}, // 33
|
||||||
|
{32, 28, 2761, 0x228ba, [4]level{{19, 30}, {37, 28}, {51, 30}, {60, 30}}}, // 34
|
||||||
|
{28, 24, 2876, 0x2379f, [4]level{{19, 30}, {38, 28}, {53, 30}, {63, 30}}}, // 35
|
||||||
|
{22, 26, 3034, 0x24b0b, [4]level{{20, 30}, {40, 28}, {56, 30}, {66, 30}}}, // 36
|
||||||
|
{26, 26, 3196, 0x2542e, [4]level{{21, 30}, {43, 28}, {59, 30}, {70, 30}}}, // 37
|
||||||
|
{30, 26, 3362, 0x26a64, [4]level{{22, 30}, {45, 28}, {62, 30}, {74, 30}}}, // 38
|
||||||
|
{24, 28, 3532, 0x27541, [4]level{{24, 30}, {47, 28}, {65, 30}, {77, 30}}}, // 39
|
||||||
|
{28, 28, 3706, 0x28c69, [4]level{{25, 30}, {49, 28}, {68, 30}, {81, 30}}}, // 40
|
||||||
|
}
|
||||||
|
|
||||||
|
func grid(siz int) [][]Pixel {
|
||||||
|
m := make([][]Pixel, siz)
|
||||||
|
pix := make([]Pixel, siz*siz)
|
||||||
|
for i := range m {
|
||||||
|
m[i], pix = pix[:siz], pix[siz:]
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// vplan creates a Plan for the given version.
|
||||||
|
func vplan(v Version) (*Plan, error) {
|
||||||
|
p := &Plan{Version: v}
|
||||||
|
if v < 1 || v > 40 {
|
||||||
|
return nil, fmt.Errorf("invalid QR version %d", int(v))
|
||||||
|
}
|
||||||
|
siz := 17 + int(v)*4
|
||||||
|
m := grid(siz)
|
||||||
|
p.Pixel = m
|
||||||
|
|
||||||
|
// Timing markers (overwritten by boxes).
|
||||||
|
const ti = 6 // timing is in row/column 6 (counting from 0)
|
||||||
|
for i := range m {
|
||||||
|
p := Timing.Pixel()
|
||||||
|
if i&1 == 0 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[i][ti] = p
|
||||||
|
m[ti][i] = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position boxes.
|
||||||
|
posBox(m, 0, 0)
|
||||||
|
posBox(m, siz-7, 0)
|
||||||
|
posBox(m, 0, siz-7)
|
||||||
|
|
||||||
|
// Alignment boxes.
|
||||||
|
info := &vtab[v]
|
||||||
|
for x := 4; x+5 < siz; {
|
||||||
|
for y := 4; y+5 < siz; {
|
||||||
|
// don't overwrite timing markers
|
||||||
|
if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) {
|
||||||
|
} else {
|
||||||
|
alignBox(m, x, y)
|
||||||
|
}
|
||||||
|
if y == 4 {
|
||||||
|
y = info.apos
|
||||||
|
} else {
|
||||||
|
y += info.astride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x == 4 {
|
||||||
|
x = info.apos
|
||||||
|
} else {
|
||||||
|
x += info.astride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version pattern.
|
||||||
|
pat := vtab[v].pattern
|
||||||
|
if pat != 0 {
|
||||||
|
v := pat
|
||||||
|
for x := 0; x < 6; x++ {
|
||||||
|
for y := 0; y < 3; y++ {
|
||||||
|
p := PVersion.Pixel()
|
||||||
|
if v&1 != 0 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[siz-11+y][x] = p
|
||||||
|
m[x][siz-11+y] = p
|
||||||
|
v >>= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One lonely black pixel
|
||||||
|
m[siz-8][8] = Unused.Pixel() | Black
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fplan adds the format pixels
|
||||||
|
func fplan(l Level, m Mask, p *Plan) error {
|
||||||
|
// Format pixels.
|
||||||
|
fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10
|
||||||
|
fb |= uint32(m) << 10 // mask
|
||||||
|
const formatPoly = 0x537
|
||||||
|
rem := fb
|
||||||
|
for i := 14; i >= 10; i-- {
|
||||||
|
if rem&(1<<uint(i)) != 0 {
|
||||||
|
rem ^= formatPoly << uint(i-10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fb |= rem
|
||||||
|
invert := uint32(0x5412)
|
||||||
|
siz := len(p.Pixel)
|
||||||
|
for i := uint(0); i < 15; i++ {
|
||||||
|
pix := Format.Pixel() + OffsetPixel(i)
|
||||||
|
if (fb>>i)&1 == 1 {
|
||||||
|
pix |= Black
|
||||||
|
}
|
||||||
|
if (invert>>i)&1 == 1 {
|
||||||
|
pix ^= Invert | Black
|
||||||
|
}
|
||||||
|
// top left
|
||||||
|
switch {
|
||||||
|
case i < 6:
|
||||||
|
p.Pixel[i][8] = pix
|
||||||
|
case i < 8:
|
||||||
|
p.Pixel[i+1][8] = pix
|
||||||
|
case i < 9:
|
||||||
|
p.Pixel[8][7] = pix
|
||||||
|
default:
|
||||||
|
p.Pixel[8][14-i] = pix
|
||||||
|
}
|
||||||
|
// bottom right
|
||||||
|
switch {
|
||||||
|
case i < 8:
|
||||||
|
p.Pixel[8][siz-1-int(i)] = pix
|
||||||
|
default:
|
||||||
|
p.Pixel[siz-1-int(14-i)][8] = pix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lplan edits a version-only Plan to add information
|
||||||
|
// about the error correction levels.
|
||||||
|
func lplan(v Version, l Level, p *Plan) error {
|
||||||
|
p.Level = l
|
||||||
|
|
||||||
|
nblock := vtab[v].level[l].nblock
|
||||||
|
ne := vtab[v].level[l].check
|
||||||
|
nde := (vtab[v].bytes - ne*nblock) / nblock
|
||||||
|
extra := (vtab[v].bytes - ne*nblock) % nblock
|
||||||
|
dataBits := (nde*nblock + extra) * 8
|
||||||
|
checkBits := ne * nblock * 8
|
||||||
|
|
||||||
|
p.DataBytes = vtab[v].bytes - ne*nblock
|
||||||
|
p.CheckBytes = ne * nblock
|
||||||
|
p.Blocks = nblock
|
||||||
|
|
||||||
|
// Make data + checksum pixels.
|
||||||
|
data := make([]Pixel, dataBits)
|
||||||
|
for i := range data {
|
||||||
|
data[i] = Data.Pixel() | OffsetPixel(uint(i))
|
||||||
|
}
|
||||||
|
check := make([]Pixel, checkBits)
|
||||||
|
for i := range check {
|
||||||
|
check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split into blocks.
|
||||||
|
dataList := make([][]Pixel, nblock)
|
||||||
|
checkList := make([][]Pixel, nblock)
|
||||||
|
for i := 0; i < nblock; i++ {
|
||||||
|
// The last few blocks have an extra data byte (8 pixels).
|
||||||
|
nd := nde
|
||||||
|
if i >= nblock-extra {
|
||||||
|
nd++
|
||||||
|
}
|
||||||
|
dataList[i], data = data[0:nd*8], data[nd*8:]
|
||||||
|
checkList[i], check = check[0:ne*8], check[ne*8:]
|
||||||
|
}
|
||||||
|
if len(data) != 0 || len(check) != 0 {
|
||||||
|
panic("data/check math")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up bit sequence, taking first byte of each block,
|
||||||
|
// then second byte, and so on. Then checksums.
|
||||||
|
bits := make([]Pixel, dataBits+checkBits)
|
||||||
|
dst := bits
|
||||||
|
for i := 0; i < nde+1; i++ {
|
||||||
|
for _, b := range dataList {
|
||||||
|
if i*8 < len(b) {
|
||||||
|
copy(dst, b[i*8:(i+1)*8])
|
||||||
|
dst = dst[8:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < ne; i++ {
|
||||||
|
for _, b := range checkList {
|
||||||
|
if i*8 < len(b) {
|
||||||
|
copy(dst, b[i*8:(i+1)*8])
|
||||||
|
dst = dst[8:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(dst) != 0 {
|
||||||
|
panic("dst math")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sweep up pair of columns,
|
||||||
|
// then down, assigning to right then left pixel.
|
||||||
|
// Repeat.
|
||||||
|
// See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm
|
||||||
|
siz := len(p.Pixel)
|
||||||
|
rem := make([]Pixel, 7)
|
||||||
|
for i := range rem {
|
||||||
|
rem[i] = Extra.Pixel()
|
||||||
|
}
|
||||||
|
src := append(bits, rem...)
|
||||||
|
for x := siz; x > 0; {
|
||||||
|
for y := siz - 1; y >= 0; y-- {
|
||||||
|
if p.Pixel[y][x-1].Role() == 0 {
|
||||||
|
p.Pixel[y][x-1], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
if p.Pixel[y][x-2].Role() == 0 {
|
||||||
|
p.Pixel[y][x-2], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x -= 2
|
||||||
|
if x == 7 { // vertical timing strip
|
||||||
|
x--
|
||||||
|
}
|
||||||
|
for y := 0; y < siz; y++ {
|
||||||
|
if p.Pixel[y][x-1].Role() == 0 {
|
||||||
|
p.Pixel[y][x-1], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
if p.Pixel[y][x-2].Role() == 0 {
|
||||||
|
p.Pixel[y][x-2], src = src[0], src[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x -= 2
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mplan edits a version+level-only Plan to add the mask.
|
||||||
|
func mplan(m Mask, p *Plan) error {
|
||||||
|
p.Mask = m
|
||||||
|
for y, row := range p.Pixel {
|
||||||
|
for x, pix := range row {
|
||||||
|
if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) {
|
||||||
|
row[x] ^= Black | Invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// posBox draws a position (large) box at upper left x, y.
|
||||||
|
func posBox(m [][]Pixel, x, y int) {
|
||||||
|
pos := Position.Pixel()
|
||||||
|
// box
|
||||||
|
for dy := 0; dy < 7; dy++ {
|
||||||
|
for dx := 0; dx < 7; dx++ {
|
||||||
|
p := pos
|
||||||
|
if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[y+dy][x+dx] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// white border
|
||||||
|
for dy := -1; dy < 8; dy++ {
|
||||||
|
if 0 <= y+dy && y+dy < len(m) {
|
||||||
|
if x > 0 {
|
||||||
|
m[y+dy][x-1] = pos
|
||||||
|
}
|
||||||
|
if x+7 < len(m) {
|
||||||
|
m[y+dy][x+7] = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for dx := -1; dx < 8; dx++ {
|
||||||
|
if 0 <= x+dx && x+dx < len(m) {
|
||||||
|
if y > 0 {
|
||||||
|
m[y-1][x+dx] = pos
|
||||||
|
}
|
||||||
|
if y+7 < len(m) {
|
||||||
|
m[y+7][x+dx] = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// alignBox draw an alignment (small) box at upper left x, y.
|
||||||
|
func alignBox(m [][]Pixel, x, y int) {
|
||||||
|
// box
|
||||||
|
align := Alignment.Pixel()
|
||||||
|
for dy := 0; dy < 5; dy++ {
|
||||||
|
for dx := 0; dx < 5; dx++ {
|
||||||
|
p := align
|
||||||
|
if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 {
|
||||||
|
p |= Black
|
||||||
|
}
|
||||||
|
m[y+dy][x+dx] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
vendor/github.com/chai2010/image/qrencoder/internal/gf256/blog_test.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file contains a straightforward implementation of
|
||||||
|
// Reed-Solomon encoding, along with a benchmark.
|
||||||
|
// It goes with http://research.swtch.com/field.
|
||||||
|
//
|
||||||
|
// For an optimized implementation, see gf256.go.
|
||||||
|
|
||||||
|
package gf256
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlogECC writes to check the error correcting code bytes
|
||||||
|
// for data using the given Reed-Solomon parameters.
|
||||||
|
func BlogECC(rs *RSEncoder, m []byte, check []byte) {
|
||||||
|
if len(check) < rs.c {
|
||||||
|
panic("gf256: invalid check byte length")
|
||||||
|
}
|
||||||
|
if rs.c == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The check bytes are the remainder after dividing
|
||||||
|
// data padded with c zeros by the generator polynomial.
|
||||||
|
|
||||||
|
// p = data padded with c zeros.
|
||||||
|
var p []byte
|
||||||
|
n := len(m) + rs.c
|
||||||
|
if len(rs.p) >= n {
|
||||||
|
p = rs.p
|
||||||
|
} else {
|
||||||
|
p = make([]byte, n)
|
||||||
|
}
|
||||||
|
copy(p, m)
|
||||||
|
for i := len(m); i < len(p); i++ {
|
||||||
|
p[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
gen := rs.gen
|
||||||
|
|
||||||
|
// Divide p by gen, leaving the remainder in p[len(data):].
|
||||||
|
// p[0] is the most significant term in p, and
|
||||||
|
// gen[0] is the most significant term in the generator.
|
||||||
|
for i := 0; i < len(m); i++ {
|
||||||
|
k := f.Mul(p[i], f.Inv(gen[0])) // k = pi / g0
|
||||||
|
// p -= k·g
|
||||||
|
for j, g := range gen {
|
||||||
|
p[i+j] = f.Add(p[i+j], f.Mul(k, g))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(check, p[len(m):])
|
||||||
|
rs.p = p
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBlogECC(b *testing.B) {
|
||||||
|
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
|
||||||
|
check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f}
|
||||||
|
out := make([]byte, len(check))
|
||||||
|
rs := NewRSEncoder(f, len(check))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
BlogECC(rs, data, out)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(data)))
|
||||||
|
if !bytes.Equal(out, check) {
|
||||||
|
fmt.Printf("have %#v want %#v\n", out, check)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlogECC(t *testing.T) {
|
||||||
|
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
|
||||||
|
check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
|
||||||
|
out := make([]byte, len(check))
|
||||||
|
rs := NewRSEncoder(f, len(check))
|
||||||
|
BlogECC(rs, data, out)
|
||||||
|
if !bytes.Equal(out, check) {
|
||||||
|
t.Errorf("have %x want %x", out, check)
|
||||||
|
}
|
||||||
|
}
|
241
vendor/github.com/chai2010/image/qrencoder/internal/gf256/gf256.go
generated
vendored
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package gf256 implements arithmetic over the Galois Field GF(256).
|
||||||
|
package gf256
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// A Field represents an instance of GF(256) defined by a specific polynomial.
|
||||||
|
type Field struct {
|
||||||
|
log [256]byte // log[0] is unused
|
||||||
|
exp [510]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewField returns a new field corresponding to the polynomial poly
|
||||||
|
// and generator α. The Reed-Solomon encoding in QR codes uses
|
||||||
|
// polynomial 0x11d with generator 2.
|
||||||
|
//
|
||||||
|
// The choice of generator α only affects the Exp and Log operations.
|
||||||
|
func NewField(poly, α int) *Field {
|
||||||
|
if poly < 0x100 || poly >= 0x200 || reducible(poly) {
|
||||||
|
panic("gf256: invalid polynomial: " + strconv.Itoa(poly))
|
||||||
|
}
|
||||||
|
|
||||||
|
var f Field
|
||||||
|
x := 1
|
||||||
|
for i := 0; i < 255; i++ {
|
||||||
|
if x == 1 && i != 0 {
|
||||||
|
panic("gf256: invalid generator " + strconv.Itoa(α) +
|
||||||
|
" for polynomial " + strconv.Itoa(poly))
|
||||||
|
}
|
||||||
|
f.exp[i] = byte(x)
|
||||||
|
f.exp[i+255] = byte(x)
|
||||||
|
f.log[x] = byte(i)
|
||||||
|
x = mul(x, α, poly)
|
||||||
|
}
|
||||||
|
f.log[0] = 255
|
||||||
|
for i := 0; i < 255; i++ {
|
||||||
|
if f.log[f.exp[i]] != byte(i) {
|
||||||
|
panic("bad log")
|
||||||
|
}
|
||||||
|
if f.log[f.exp[i+255]] != byte(i) {
|
||||||
|
panic("bad log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 1; i < 256; i++ {
|
||||||
|
if f.exp[f.log[i]] != byte(i) {
|
||||||
|
panic("bad log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
// nbit returns the number of significant in p.
|
||||||
|
func nbit(p int) uint {
|
||||||
|
n := uint(0)
|
||||||
|
for ; p > 0; p >>= 1 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// polyDiv divides the polynomial p by q and returns the remainder.
|
||||||
|
func polyDiv(p, q int) int {
|
||||||
|
np := nbit(p)
|
||||||
|
nq := nbit(q)
|
||||||
|
for ; np >= nq; np-- {
|
||||||
|
if p&(1<<(np-1)) != 0 {
|
||||||
|
p ^= q << (np - nq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul returns the product x*y mod poly, a GF(256) multiplication.
|
||||||
|
func mul(x, y, poly int) int {
|
||||||
|
z := 0
|
||||||
|
for x > 0 {
|
||||||
|
if x&1 != 0 {
|
||||||
|
z ^= y
|
||||||
|
}
|
||||||
|
x >>= 1
|
||||||
|
y <<= 1
|
||||||
|
if y&0x100 != 0 {
|
||||||
|
y ^= poly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// reducible reports whether p is reducible.
|
||||||
|
func reducible(p int) bool {
|
||||||
|
// Multiplying n-bit * n-bit produces (2n-1)-bit,
|
||||||
|
// so if p is reducible, one of its factors must be
|
||||||
|
// of np/2+1 bits or fewer.
|
||||||
|
np := nbit(p)
|
||||||
|
for q := 2; q < 1<<(np/2+1); q++ {
|
||||||
|
if polyDiv(p, q) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns the sum of x and y in the field.
|
||||||
|
func (f *Field) Add(x, y byte) byte {
|
||||||
|
return x ^ y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exp returns the base-α exponential of e in the field.
|
||||||
|
// If e < 0, Exp returns 0.
|
||||||
|
func (f *Field) Exp(e int) byte {
|
||||||
|
if e < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f.exp[e%255]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log returns the base-α logarithm of x in the field.
|
||||||
|
// If x == 0, Log returns -1.
|
||||||
|
func (f *Field) Log(x byte) int {
|
||||||
|
if x == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return int(f.log[x])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inv returns the multiplicative inverse of x in the field.
|
||||||
|
// If x == 0, Inv returns 0.
|
||||||
|
func (f *Field) Inv(x byte) byte {
|
||||||
|
if x == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f.exp[255-f.log[x]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mul returns the product of x and y in the field.
|
||||||
|
func (f *Field) Mul(x, y byte) byte {
|
||||||
|
if x == 0 || y == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f.exp[int(f.log[x])+int(f.log[y])]
|
||||||
|
}
|
||||||
|
|
||||||
|
// An RSEncoder implements Reed-Solomon encoding
|
||||||
|
// over a given field using a given number of error correction bytes.
|
||||||
|
type RSEncoder struct {
|
||||||
|
f *Field
|
||||||
|
c int
|
||||||
|
gen []byte
|
||||||
|
lgen []byte
|
||||||
|
p []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) gen(e int) (gen, lgen []byte) {
|
||||||
|
// p = 1
|
||||||
|
p := make([]byte, e+1)
|
||||||
|
p[e] = 1
|
||||||
|
|
||||||
|
for i := 0; i < e; i++ {
|
||||||
|
// p *= (x + Exp(i))
|
||||||
|
// p[j] = p[j]*Exp(i) + p[j+1].
|
||||||
|
c := f.Exp(i)
|
||||||
|
for j := 0; j < e; j++ {
|
||||||
|
p[j] = f.Mul(p[j], c) ^ p[j+1]
|
||||||
|
}
|
||||||
|
p[e] = f.Mul(p[e], c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lp = log p.
|
||||||
|
lp := make([]byte, e+1)
|
||||||
|
for i, c := range p {
|
||||||
|
if c == 0 {
|
||||||
|
lp[i] = 255
|
||||||
|
} else {
|
||||||
|
lp[i] = byte(f.Log(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, lp
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSEncoder returns a new Reed-Solomon encoder
|
||||||
|
// over the given field and number of error correction bytes.
|
||||||
|
func NewRSEncoder(f *Field, c int) *RSEncoder {
|
||||||
|
gen, lgen := f.gen(c)
|
||||||
|
return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECC writes to check the error correcting code bytes
|
||||||
|
// for data using the given Reed-Solomon parameters.
|
||||||
|
func (rs *RSEncoder) ECC(data []byte, check []byte) {
|
||||||
|
if len(check) < rs.c {
|
||||||
|
panic("gf256: invalid check byte length")
|
||||||
|
}
|
||||||
|
if rs.c == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The check bytes are the remainder after dividing
|
||||||
|
// data padded with c zeros by the generator polynomial.
|
||||||
|
|
||||||
|
// p = data padded with c zeros.
|
||||||
|
var p []byte
|
||||||
|
n := len(data) + rs.c
|
||||||
|
if len(rs.p) >= n {
|
||||||
|
p = rs.p
|
||||||
|
} else {
|
||||||
|
p = make([]byte, n)
|
||||||
|
}
|
||||||
|
copy(p, data)
|
||||||
|
for i := len(data); i < len(p); i++ {
|
||||||
|
p[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide p by gen, leaving the remainder in p[len(data):].
|
||||||
|
// p[0] is the most significant term in p, and
|
||||||
|
// gen[0] is the most significant term in the generator,
|
||||||
|
// which is always 1.
|
||||||
|
// To avoid repeated work, we store various values as
|
||||||
|
// lv, not v, where lv = log[v].
|
||||||
|
f := rs.f
|
||||||
|
lgen := rs.lgen[1:]
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
c := p[i]
|
||||||
|
if c == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
q := p[i+1:]
|
||||||
|
exp := f.exp[f.log[c]:]
|
||||||
|
for j, lg := range lgen {
|
||||||
|
if lg != 255 { // lgen uses 255 for log 0
|
||||||
|
q[j] ^= exp[lg]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy(check, p[len(data):])
|
||||||
|
rs.p = p
|
||||||
|
}
|
194
vendor/github.com/chai2010/image/qrencoder/internal/gf256/gf256_test.go
generated
vendored
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gf256
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var f = NewField(0x11d, 2) // x^8 + x^4 + x^3 + x^2 + 1
|
||||||
|
|
||||||
|
func TestBasic(t *testing.T) {
|
||||||
|
if f.Exp(0) != 1 || f.Exp(1) != 2 || f.Exp(255) != 1 {
|
||||||
|
panic("bad Exp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECC(t *testing.T) {
|
||||||
|
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
|
||||||
|
check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
|
||||||
|
out := make([]byte, len(check))
|
||||||
|
rs := NewRSEncoder(f, len(check))
|
||||||
|
rs.ECC(data, out)
|
||||||
|
if !bytes.Equal(out, check) {
|
||||||
|
t.Errorf("have %x want %x", out, check)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinear(t *testing.T) {
|
||||||
|
d1 := []byte{0x00, 0x00}
|
||||||
|
c1 := []byte{0x00, 0x00}
|
||||||
|
out := make([]byte, len(c1))
|
||||||
|
rs := NewRSEncoder(f, len(c1))
|
||||||
|
if rs.ECC(d1, out); !bytes.Equal(out, c1) {
|
||||||
|
t.Errorf("ECBytes(%x, %d) = %x, want 0", d1, len(c1), out)
|
||||||
|
}
|
||||||
|
d2 := []byte{0x00, 0x01}
|
||||||
|
c2 := make([]byte, 2)
|
||||||
|
rs.ECC(d2, c2)
|
||||||
|
d3 := []byte{0x00, 0x02}
|
||||||
|
c3 := make([]byte, 2)
|
||||||
|
rs.ECC(d3, c3)
|
||||||
|
cx := make([]byte, 2)
|
||||||
|
for i := range cx {
|
||||||
|
cx[i] = c2[i] ^ c3[i]
|
||||||
|
}
|
||||||
|
d4 := []byte{0x00, 0x03}
|
||||||
|
c4 := make([]byte, 2)
|
||||||
|
rs.ECC(d4, c4)
|
||||||
|
if !bytes.Equal(cx, c4) {
|
||||||
|
t.Errorf("ECBytes(%x, 2) = %x\nECBytes(%x, 2) = %x\nxor = %x\nECBytes(%x, 2) = %x",
|
||||||
|
d2, c2, d3, c3, cx, d4, c4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGaussJordan(t *testing.T) {
|
||||||
|
rs := NewRSEncoder(f, 2)
|
||||||
|
m := make([][]byte, 16)
|
||||||
|
for i := range m {
|
||||||
|
m[i] = make([]byte, 4)
|
||||||
|
m[i][i/8] = 1 << uint(i%8)
|
||||||
|
rs.ECC(m[i][:2], m[i][2:])
|
||||||
|
}
|
||||||
|
if false {
|
||||||
|
fmt.Printf("---\n")
|
||||||
|
for _, row := range m {
|
||||||
|
fmt.Printf("%x\n", row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b := []uint{0, 1, 2, 3, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27}
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
bi := b[i]
|
||||||
|
if m[i][bi/8]&(1<<(7-bi%8)) == 0 {
|
||||||
|
for j := i + 1; ; j++ {
|
||||||
|
if j >= len(m) {
|
||||||
|
t.Errorf("lost track for %d", bi)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
|
||||||
|
m[i], m[j] = m[j], m[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for j := i + 1; j < len(m); j++ {
|
||||||
|
if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
|
||||||
|
for k := range m[j] {
|
||||||
|
m[j][k] ^= m[i][k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if false {
|
||||||
|
fmt.Printf("---\n")
|
||||||
|
for _, row := range m {
|
||||||
|
fmt.Printf("%x\n", row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 15; i >= 0; i-- {
|
||||||
|
bi := b[i]
|
||||||
|
for j := i - 1; j >= 0; j-- {
|
||||||
|
if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
|
||||||
|
for k := range m[j] {
|
||||||
|
m[j][k] ^= m[i][k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if false {
|
||||||
|
fmt.Printf("---\n")
|
||||||
|
for _, row := range m {
|
||||||
|
fmt.Printf("%x", row)
|
||||||
|
out := make([]byte, 2)
|
||||||
|
if rs.ECC(row[:2], out); !bytes.Equal(out, row[2:]) {
|
||||||
|
fmt.Printf(" - want %x", out)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkECC(b *testing.B) {
|
||||||
|
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
|
||||||
|
check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f}
|
||||||
|
out := make([]byte, len(check))
|
||||||
|
rs := NewRSEncoder(f, len(check))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
rs.ECC(data, out)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(data)))
|
||||||
|
if !bytes.Equal(out, check) {
|
||||||
|
fmt.Printf("have %#v want %#v\n", out, check)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGen(t *testing.T) {
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
_, lg := f.gen(i)
|
||||||
|
if lg[0] != 0 {
|
||||||
|
t.Errorf("#%d: %x", i, lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReducible(t *testing.T) {
|
||||||
|
var count = []int{1, 2, 3, 6, 9, 18, 30, 56, 99, 186} // oeis.org/A1037
|
||||||
|
for i, want := range count {
|
||||||
|
n := 0
|
||||||
|
for p := 1 << uint(i+2); p < 1<<uint(i+3); p++ {
|
||||||
|
if !reducible(p) {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n != want {
|
||||||
|
t.Errorf("#reducible(%d-bit) = %d, want %d", i+2, n, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExhaustive(t *testing.T) {
|
||||||
|
for poly := 0x100; poly < 0x200; poly++ {
|
||||||
|
if reducible(poly) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
α := 2
|
||||||
|
for !generates(α, poly) {
|
||||||
|
α++
|
||||||
|
}
|
||||||
|
f := NewField(poly, α)
|
||||||
|
for p := 0; p < 256; p++ {
|
||||||
|
for q := 0; q < 256; q++ {
|
||||||
|
fm := int(f.Mul(byte(p), byte(q)))
|
||||||
|
pm := mul(p, q, poly)
|
||||||
|
if fm != pm {
|
||||||
|
t.Errorf("NewField(%#x).Mul(%#x, %#x) = %#x, want %#x", poly, p, q, fm, pm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generates(α, poly int) bool {
|
||||||
|
x := α
|
||||||
|
for i := 0; i < 254; i++ {
|
||||||
|
if x == 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
x = mul(x, α, poly)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
400
vendor/github.com/chai2010/image/qrencoder/png.go
generated
vendored
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package qrcode
|
||||||
|
|
||||||
|
// PNG writer for QR codes.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"hash"
|
||||||
|
"hash/crc32"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PNG returns a PNG image displaying the code.
|
||||||
|
//
|
||||||
|
// PNG uses a custom encoder tailored to QR codes.
|
||||||
|
// Its compressed size is about 2x away from optimal,
|
||||||
|
// but it runs about 20x faster than calling png.Encode
|
||||||
|
// on c.Image().
|
||||||
|
func (c *Code) PNG() []byte {
|
||||||
|
var p pngWriter
|
||||||
|
return p.encode(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pngWriter struct {
|
||||||
|
tmp [16]byte
|
||||||
|
wctmp [4]byte
|
||||||
|
buf bytes.Buffer
|
||||||
|
zlib bitWriter
|
||||||
|
crc hash.Hash32
|
||||||
|
}
|
||||||
|
|
||||||
|
var pngHeader = []byte("\x89PNG\r\n\x1a\n")
|
||||||
|
|
||||||
|
func (w *pngWriter) encode(c *Code) []byte {
|
||||||
|
scale := c.Scale
|
||||||
|
siz := c.Size
|
||||||
|
|
||||||
|
w.buf.Reset()
|
||||||
|
|
||||||
|
// Header
|
||||||
|
w.buf.Write(pngHeader)
|
||||||
|
|
||||||
|
// Header block
|
||||||
|
binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale))
|
||||||
|
binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale))
|
||||||
|
w.tmp[8] = 1 // 1-bit
|
||||||
|
w.tmp[9] = 0 // gray
|
||||||
|
w.tmp[10] = 0
|
||||||
|
w.tmp[11] = 0
|
||||||
|
w.tmp[12] = 0
|
||||||
|
w.writeChunk("IHDR", w.tmp[:13])
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
w.writeChunk("tEXt", comment)
|
||||||
|
|
||||||
|
// Data
|
||||||
|
w.zlib.writeCode(c)
|
||||||
|
w.writeChunk("IDAT", w.zlib.bytes.Bytes())
|
||||||
|
|
||||||
|
// End
|
||||||
|
w.writeChunk("IEND", nil)
|
||||||
|
|
||||||
|
return w.buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
var comment = []byte("Software\x00QR-PNG <chaishushan{AT}gmail.com>")
|
||||||
|
|
||||||
|
func (w *pngWriter) writeChunk(name string, data []byte) {
|
||||||
|
if w.crc == nil {
|
||||||
|
w.crc = crc32.NewIEEE()
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data)))
|
||||||
|
w.buf.Write(w.wctmp[0:4])
|
||||||
|
w.crc.Reset()
|
||||||
|
copy(w.wctmp[0:4], name)
|
||||||
|
w.buf.Write(w.wctmp[0:4])
|
||||||
|
w.crc.Write(w.wctmp[0:4])
|
||||||
|
w.buf.Write(data)
|
||||||
|
w.crc.Write(data)
|
||||||
|
crc := w.crc.Sum32()
|
||||||
|
binary.BigEndian.PutUint32(w.wctmp[0:4], crc)
|
||||||
|
w.buf.Write(w.wctmp[0:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) writeCode(c *Code) {
|
||||||
|
const ftNone = 0
|
||||||
|
|
||||||
|
b.adler32.Reset()
|
||||||
|
b.bytes.Reset()
|
||||||
|
b.nbit = 0
|
||||||
|
|
||||||
|
scale := c.Scale
|
||||||
|
siz := c.Size
|
||||||
|
|
||||||
|
// zlib header
|
||||||
|
b.tmp[0] = 0x78
|
||||||
|
b.tmp[1] = 0
|
||||||
|
b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31)
|
||||||
|
b.bytes.Write(b.tmp[0:2])
|
||||||
|
|
||||||
|
// Start flate block.
|
||||||
|
b.writeBits(1, 1, false) // final block
|
||||||
|
b.writeBits(1, 2, false) // compressed, fixed Huffman tables
|
||||||
|
|
||||||
|
// White border.
|
||||||
|
// First row.
|
||||||
|
b.byte(ftNone)
|
||||||
|
n := (scale*(siz+8) + 7) / 8
|
||||||
|
b.byte(255)
|
||||||
|
b.repeat(n-1, 1)
|
||||||
|
// 4*scale rows total.
|
||||||
|
b.repeat((4*scale-1)*(1+n), 1+n)
|
||||||
|
|
||||||
|
for i := 0; i < 4*scale; i++ {
|
||||||
|
b.adler32.WriteNByte(ftNone, 1)
|
||||||
|
b.adler32.WriteNByte(255, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
row := make([]byte, 1+n)
|
||||||
|
for y := 0; y < siz; y++ {
|
||||||
|
row[0] = ftNone
|
||||||
|
j := 1
|
||||||
|
var z uint8
|
||||||
|
nz := 0
|
||||||
|
for x := -4; x < siz+4; x++ {
|
||||||
|
// Raw data.
|
||||||
|
for i := 0; i < scale; i++ {
|
||||||
|
z <<= 1
|
||||||
|
if !c.Black(x, y) {
|
||||||
|
z |= 1
|
||||||
|
}
|
||||||
|
if nz++; nz == 8 {
|
||||||
|
row[j] = z
|
||||||
|
j++
|
||||||
|
nz = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if j < len(row) {
|
||||||
|
row[j] = z
|
||||||
|
}
|
||||||
|
for _, z := range row {
|
||||||
|
b.byte(z)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale-1 copies.
|
||||||
|
b.repeat((scale-1)*(1+n), 1+n)
|
||||||
|
|
||||||
|
b.adler32.WriteN(row, scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// White border.
|
||||||
|
// First row.
|
||||||
|
b.byte(ftNone)
|
||||||
|
b.byte(255)
|
||||||
|
b.repeat(n-1, 1)
|
||||||
|
// 4*scale rows total.
|
||||||
|
b.repeat((4*scale-1)*(1+n), 1+n)
|
||||||
|
|
||||||
|
for i := 0; i < 4*scale; i++ {
|
||||||
|
b.adler32.WriteNByte(ftNone, 1)
|
||||||
|
b.adler32.WriteNByte(255, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of block.
|
||||||
|
b.hcode(256)
|
||||||
|
b.flushBits()
|
||||||
|
|
||||||
|
// adler32
|
||||||
|
binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32())
|
||||||
|
b.bytes.Write(b.tmp[0:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bitWriter is a write buffer for bit-oriented data like deflate.
|
||||||
|
type bitWriter struct {
|
||||||
|
bytes bytes.Buffer
|
||||||
|
bit uint32
|
||||||
|
nbit uint
|
||||||
|
|
||||||
|
tmp [4]byte
|
||||||
|
adler32 adigest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) {
|
||||||
|
// reverse, for huffman codes
|
||||||
|
if rev {
|
||||||
|
br := uint32(0)
|
||||||
|
for i := uint(0); i < nbit; i++ {
|
||||||
|
br |= ((bit >> i) & 1) << (nbit - 1 - i)
|
||||||
|
}
|
||||||
|
bit = br
|
||||||
|
}
|
||||||
|
b.bit |= bit << b.nbit
|
||||||
|
b.nbit += nbit
|
||||||
|
for b.nbit >= 8 {
|
||||||
|
b.bytes.WriteByte(byte(b.bit))
|
||||||
|
b.bit >>= 8
|
||||||
|
b.nbit -= 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) flushBits() {
|
||||||
|
if b.nbit > 0 {
|
||||||
|
b.bytes.WriteByte(byte(b.bit))
|
||||||
|
b.nbit = 0
|
||||||
|
b.bit = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) hcode(v int) {
|
||||||
|
/*
|
||||||
|
Lit Value Bits Codes
|
||||||
|
--------- ---- -----
|
||||||
|
0 - 143 8 00110000 through
|
||||||
|
10111111
|
||||||
|
144 - 255 9 110010000 through
|
||||||
|
111111111
|
||||||
|
256 - 279 7 0000000 through
|
||||||
|
0010111
|
||||||
|
280 - 287 8 11000000 through
|
||||||
|
11000111
|
||||||
|
*/
|
||||||
|
switch {
|
||||||
|
case v <= 143:
|
||||||
|
b.writeBits(uint32(v)+0x30, 8, true)
|
||||||
|
case v <= 255:
|
||||||
|
b.writeBits(uint32(v-144)+0x190, 9, true)
|
||||||
|
case v <= 279:
|
||||||
|
b.writeBits(uint32(v-256)+0, 7, true)
|
||||||
|
case v <= 287:
|
||||||
|
b.writeBits(uint32(v-280)+0xc0, 8, true)
|
||||||
|
default:
|
||||||
|
panic("invalid hcode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) byte(x byte) {
|
||||||
|
b.hcode(int(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) codex(c int, val int, nx uint) {
|
||||||
|
b.hcode(c + val>>nx)
|
||||||
|
b.writeBits(uint32(val)&(1<<nx-1), nx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) repeat(n, d int) {
|
||||||
|
for ; n >= 258+3; n -= 258 {
|
||||||
|
b.repeat1(258, d)
|
||||||
|
}
|
||||||
|
if n > 258 {
|
||||||
|
// 258 < n < 258+3
|
||||||
|
b.repeat1(10, d)
|
||||||
|
b.repeat1(n-10, d)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n < 3 {
|
||||||
|
panic("invalid flate repeat")
|
||||||
|
}
|
||||||
|
b.repeat1(n, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) repeat1(n, d int) {
|
||||||
|
/*
|
||||||
|
Extra Extra Extra
|
||||||
|
Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
|
||||||
|
---- ---- ------ ---- ---- ------- ---- ---- -------
|
||||||
|
257 0 3 267 1 15,16 277 4 67-82
|
||||||
|
258 0 4 268 1 17,18 278 4 83-98
|
||||||
|
259 0 5 269 2 19-22 279 4 99-114
|
||||||
|
260 0 6 270 2 23-26 280 4 115-130
|
||||||
|
261 0 7 271 2 27-30 281 5 131-162
|
||||||
|
262 0 8 272 2 31-34 282 5 163-194
|
||||||
|
263 0 9 273 3 35-42 283 5 195-226
|
||||||
|
264 0 10 274 3 43-50 284 5 227-257
|
||||||
|
265 1 11,12 275 3 51-58 285 0 258
|
||||||
|
266 1 13,14 276 3 59-66
|
||||||
|
*/
|
||||||
|
switch {
|
||||||
|
case n <= 10:
|
||||||
|
b.codex(257, n-3, 0)
|
||||||
|
case n <= 18:
|
||||||
|
b.codex(265, n-11, 1)
|
||||||
|
case n <= 34:
|
||||||
|
b.codex(269, n-19, 2)
|
||||||
|
case n <= 66:
|
||||||
|
b.codex(273, n-35, 3)
|
||||||
|
case n <= 130:
|
||||||
|
b.codex(277, n-67, 4)
|
||||||
|
case n <= 257:
|
||||||
|
b.codex(281, n-131, 5)
|
||||||
|
case n == 258:
|
||||||
|
b.hcode(285)
|
||||||
|
default:
|
||||||
|
panic("invalid repeat length")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Extra Extra Extra
|
||||||
|
Code Bits Dist Code Bits Dist Code Bits Distance
|
||||||
|
---- ---- ---- ---- ---- ------ ---- ---- --------
|
||||||
|
0 0 1 10 4 33-48 20 9 1025-1536
|
||||||
|
1 0 2 11 4 49-64 21 9 1537-2048
|
||||||
|
2 0 3 12 5 65-96 22 10 2049-3072
|
||||||
|
3 0 4 13 5 97-128 23 10 3073-4096
|
||||||
|
4 1 5,6 14 6 129-192 24 11 4097-6144
|
||||||
|
5 1 7,8 15 6 193-256 25 11 6145-8192
|
||||||
|
6 2 9-12 16 7 257-384 26 12 8193-12288
|
||||||
|
7 2 13-16 17 7 385-512 27 12 12289-16384
|
||||||
|
8 3 17-24 18 8 513-768 28 13 16385-24576
|
||||||
|
9 3 25-32 19 8 769-1024 29 13 24577-32768
|
||||||
|
*/
|
||||||
|
if d <= 4 {
|
||||||
|
b.writeBits(uint32(d-1), 5, true)
|
||||||
|
} else if d <= 32768 {
|
||||||
|
nbit := uint(16)
|
||||||
|
for d <= 1<<(nbit-1) {
|
||||||
|
nbit--
|
||||||
|
}
|
||||||
|
v := uint32(d - 1)
|
||||||
|
v &^= 1 << (nbit - 1) // top bit is implicit
|
||||||
|
code := uint32(2*nbit - 2) // second bit is low bit of code
|
||||||
|
code |= v >> (nbit - 2)
|
||||||
|
v &^= 1 << (nbit - 2)
|
||||||
|
b.writeBits(code, 5, true)
|
||||||
|
// rest of bits follow
|
||||||
|
b.writeBits(uint32(v), nbit-2, false)
|
||||||
|
} else {
|
||||||
|
panic("invalid repeat distance")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitWriter) run(v byte, n int) {
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.byte(v)
|
||||||
|
if n-1 < 3 {
|
||||||
|
for i := 0; i < n-1; i++ {
|
||||||
|
b.byte(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.repeat(n-1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type adigest struct {
|
||||||
|
a, b uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) Reset() { d.a, d.b = 1, 0 }
|
||||||
|
|
||||||
|
const amod = 65521
|
||||||
|
|
||||||
|
func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) {
|
||||||
|
// TODO(rsc): 6g doesn't do magic multiplies for b %= amod,
|
||||||
|
// only for b = b%amod.
|
||||||
|
|
||||||
|
// invariant: a, b < amod
|
||||||
|
if pi == 0 {
|
||||||
|
b += uint32(n%amod) * a
|
||||||
|
b = b % amod
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// n times:
|
||||||
|
// a += pi
|
||||||
|
// b += a
|
||||||
|
// is same as
|
||||||
|
// b += n*a + n*(n+1)/2*pi
|
||||||
|
// a += n*pi
|
||||||
|
m := uint32(n)
|
||||||
|
b += (m % amod) * a
|
||||||
|
b = b % amod
|
||||||
|
b += (m * (m + 1) / 2) % amod * uint32(pi)
|
||||||
|
b = b % amod
|
||||||
|
a += (m % amod) * uint32(pi)
|
||||||
|
a = a % amod
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
func afinish(a, b uint32) uint32 {
|
||||||
|
return b<<16 | a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) WriteN(p []byte, n int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
for _, pi := range p {
|
||||||
|
d.a, d.b = aupdate(d.a, d.b, pi, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) WriteNByte(pi byte, n int) {
|
||||||
|
d.a, d.b = aupdate(d.a, d.b, pi, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) }
|
69
vendor/github.com/chai2010/image/qrencoder/png_test.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package qrcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPNG(t *testing.T) {
|
||||||
|
c, err := Encode("hello, world", L)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pngdat := c.PNG()
|
||||||
|
m, err := png.Decode(bytes.NewBuffer(pngdat))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gm := m.(*image.Gray)
|
||||||
|
|
||||||
|
scale := c.Scale
|
||||||
|
siz := c.Size
|
||||||
|
nbad := 0
|
||||||
|
for y := 0; y < scale*(8+siz); y++ {
|
||||||
|
for x := 0; x < scale*(8+siz); x++ {
|
||||||
|
v := byte(255)
|
||||||
|
if c.Black(x/scale-4, y/scale-4) {
|
||||||
|
v = 0
|
||||||
|
}
|
||||||
|
if gv := gm.At(x, y).(color.Gray).Y; gv != v {
|
||||||
|
t.Errorf("%d,%d = %d, want %d", x, y, gv, v)
|
||||||
|
if nbad++; nbad >= 20 {
|
||||||
|
t.Fatalf("too many bad pixels")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPNG(b *testing.B) {
|
||||||
|
c, err := Encode("0123456789012345678901234567890123456789", L)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var bytes []byte
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bytes = c.PNG()
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkImagePNG(b *testing.B) {
|
||||||
|
c, err := Encode("0123456789012345678901234567890123456789", L)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
buf.Reset()
|
||||||
|
png.Encode(&buf, c.Image())
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
}
|
114
vendor/github.com/chai2010/image/qrencoder/qr.go
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package qrencoder implements a encoder for QR code.
|
||||||
|
package qrcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/chai2010/image/qrencoder/internal/coding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Level denotes a QR error correction level.
|
||||||
|
// From least to most tolerant of errors, they are L, M, Q, H.
|
||||||
|
type Level int
|
||||||
|
|
||||||
|
const (
|
||||||
|
L Level = iota // 20% redundant
|
||||||
|
M // 38% redundant
|
||||||
|
Q // 55% redundant
|
||||||
|
H // 65% redundant
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode returns an encoding of text at the given error correction level.
|
||||||
|
func Encode(text string, level Level) (*Code, error) {
|
||||||
|
// Pick data encoding, smallest first.
|
||||||
|
// We could split the string and use different encodings
|
||||||
|
// but that seems like overkill for now.
|
||||||
|
var enc coding.Encoding
|
||||||
|
switch {
|
||||||
|
case coding.Num(text).Check() == nil:
|
||||||
|
enc = coding.Num(text)
|
||||||
|
case coding.Alpha(text).Check() == nil:
|
||||||
|
enc = coding.Alpha(text)
|
||||||
|
default:
|
||||||
|
enc = coding.String(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick size.
|
||||||
|
l := coding.Level(level)
|
||||||
|
var v coding.Version
|
||||||
|
for v = coding.MinVersion; ; v++ {
|
||||||
|
if v > coding.MaxVersion {
|
||||||
|
return nil, errors.New("text too long to encode as QR")
|
||||||
|
}
|
||||||
|
if enc.Bits(v) <= v.DataBytes(l)*8 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and execute plan.
|
||||||
|
p, err := coding.NewPlan(v, l, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cc, err := p.Encode(enc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pick appropriate mask.
|
||||||
|
|
||||||
|
return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Code is a square pixel grid.
|
||||||
|
// It implements image.Image and direct PNG encoding.
|
||||||
|
type Code struct {
|
||||||
|
Bitmap []byte // 1 is black, 0 is white
|
||||||
|
Size int // number of pixels on a side
|
||||||
|
Stride int // number of bytes per row
|
||||||
|
Scale int // number of image pixels per QR pixel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black returns true if the pixel at (x,y) is black.
|
||||||
|
func (c *Code) Black(x, y int) bool {
|
||||||
|
return 0 <= x && x < c.Size && 0 <= y && y < c.Size &&
|
||||||
|
c.Bitmap[y*c.Stride+x/8]&(1<<uint(7-x&7)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image returns an Image displaying the code.
|
||||||
|
func (c *Code) Image() image.Image {
|
||||||
|
return &codeImage{c}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// codeImage implements image.Image
|
||||||
|
type codeImage struct {
|
||||||
|
*Code
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
whiteColor color.Color = color.Gray{0xFF}
|
||||||
|
blackColor color.Color = color.Gray{0x00}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *codeImage) Bounds() image.Rectangle {
|
||||||
|
d := (c.Size + 8) * c.Scale
|
||||||
|
return image.Rect(0, 0, d, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codeImage) At(x, y int) color.Color {
|
||||||
|
if c.Black(x, y) {
|
||||||
|
return blackColor
|
||||||
|
}
|
||||||
|
return whiteColor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codeImage) ColorModel() color.Model {
|
||||||
|
return color.GrayModel
|
||||||
|
}
|