This commit is contained in:
foreversmart 2016-01-13 23:24:27 +08:00
commit f7361c568c
28 changed files with 2642 additions and 61 deletions

View File

@ -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$$

View File

@ -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)

View File

@ -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)

View File

@ -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.)

View File

@ -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章

View 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

View File

@ -0,0 +1,3 @@
# 索引
TODO

View 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
```

View File

@ -1,3 +1,69 @@
## 5.10. Recover捕獲異常 ## 5.10. Recover捕獲異常
TODO 通常來説不應該對panic異常做任何處理但有時也許我們可以從異常中恢複至少我們可以在程序崩潰前做一些操作。舉個例子當web服務器遇到不可預料的嚴重問題時在崩潰前應該將所有的連接關閉如果不做任何處理會使得客戶端一直處於等待狀態。如果web服務器還在開發階段服務器甚至可以將異常信息反饋到客戶端幫助調試。
如果在deferred函數中調用了內置函數recover併且定義該defer語句的函數發生了panic異常recover會使程序從panic中恢複併返迴panic value。導致panic異常的函數不會繼續運行但能正常返迴。在未發生panic時調用recoverrecover會返迴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語句但能返迴一個非零值的函數。

View File

@ -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和它的LenLess和Swap方法 爲了對序列進行排序我們需要定義一個實現了這三個方法的類型然後對這個類型的一個實例應用sort.Sort函數。思考對一個字符串切片進行排序這可能是最簡單的例子了。下面是這個新的類型StringSlice和它的LenLess和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 authors musical tastes.每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作sort函數會交換很多對元素所以如果每個元素都是指針會更快而不是全部Track類型指針是一個機器字碼長度而Track類型可能是八個或更多。 下面的變量tracks包好了一個播放列表。One of the authors apologizes for the other authors 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那樣定義一個新的帶有必鬚LenLess和Swap方法的切片類型。 爲了能按照Artist字段對播放列表進行排序我們會像對StringSlice那樣定義一個新的帶有必鬚LenLess和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)次比較操作檢査一個序列是否已經有序至少需要n1次比較。sort包中的IsSorted函數幫我們做這樣的檢査。像sort.Sort一樣它也使用sort.Interface對這個序列和它的排序函數進行抽象但是它從不會調用Swap方法這段代碼示范了IntsAreSorted和Ints函數和IntSlice類型的使用 盡管對長度爲n的序列排序需要 O(n log n)次比較操作檢査一個序列是否已經有序至少需要n1次比較。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.10sort.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上的元素相等。

View File

@ -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個字節)更大的類型時事情就變得更麻煩了比如interfacestring或者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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

33
mkqrcode.go Normal file
View 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)
}

View File

@ -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語言快樂地編程。

View File

@ -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
View 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")
}

View 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,
)
}
}

View 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
}
}
}

View 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)
}
}

View 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
}

View 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
View 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
View 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
View 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
}