1
zero-epwing-go/zig/zig.go
2020-12-31 19:52:25 -08:00

387 lines
8.8 KiB
Go

package zig
import (
"fmt"
"hash/crc32"
"unsafe"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/japanese"
)
/*
#cgo linux LDFLAGS: -L"./eb/eb/.libs" -l:libeb.a -lz
#cgo linux CFLAGS: -I"./eb/"
#include "zig.h"
*/
import "C"
//export hookCallback
func hookCallback(ebBook *C.EB_Book, ebAppendix *C.EB_Appendix, container *C.void, ebHookCode C.EB_Hook_Code, argc C.int, argv *C.uint) C.EB_Error_Code {
return 0
}
type blockType int
const (
blockTypeHeading blockType = iota
blockTypeText
)
type BookEntry struct {
Heading string
Text string
}
type BookSubbook struct {
Title string
Copyright string
Entries []BookEntry
}
type Book struct {
DiscCode string
CharCode string
Subbooks []BookSubbook
}
type Context struct {
buffer []byte
decoder *encoding.Decoder
hookset *C.EB_Hookset
book *C.EB_Book
}
func (c *Context) initialize() error {
if errEb := C.eb_initialize_library(); errEb != C.EB_SUCCESS {
return fmt.Errorf("eb_initialize_library failed with code %d", errEb)
}
c.book = (*C.EB_Book)(C.calloc(1, C.ulong(unsafe.Sizeof(C.EB_Book{}))))
C.eb_initialize_book(c.book)
c.hookset = (*C.EB_Hookset)(C.calloc(1, C.ulong(unsafe.Sizeof(C.EB_Hookset{}))))
C.eb_initialize_hookset(c.hookset)
if err := c.installHooks(); err != nil {
return err
}
c.buffer = make([]byte, 22)
c.decoder = japanese.EUCJP.NewDecoder()
return nil
}
func (c *Context) shutdown() {
C.eb_finalize_hookset(c.hookset)
C.free(unsafe.Pointer(c.hookset))
C.eb_finalize_book(c.book)
C.free(unsafe.Pointer(c.book))
C.eb_finalize_library()
}
func (c *Context) installHooks() error {
hookCodes := []C.EB_Hook_Code{
C.EB_HOOK_BEGIN_CANDIDATE,
C.EB_HOOK_BEGIN_CLICKABLE_AREA,
C.EB_HOOK_BEGIN_COLOR_BMP,
C.EB_HOOK_BEGIN_COLOR_JPEG,
C.EB_HOOK_BEGIN_DECORATION,
C.EB_HOOK_BEGIN_EBXAC_GAIJI,
C.EB_HOOK_BEGIN_EMPHASIS,
C.EB_HOOK_BEGIN_GRAPHIC_REFERENCE,
C.EB_HOOK_BEGIN_GRAY_GRAPHIC,
C.EB_HOOK_BEGIN_IMAGE_PAGE,
C.EB_HOOK_BEGIN_IN_COLOR_BMP,
C.EB_HOOK_BEGIN_IN_COLOR_JPEG,
C.EB_HOOK_BEGIN_KEYWORD,
C.EB_HOOK_BEGIN_MONO_GRAPHIC,
C.EB_HOOK_BEGIN_MPEG,
C.EB_HOOK_BEGIN_NARROW,
C.EB_HOOK_BEGIN_NO_NEWLINE,
C.EB_HOOK_BEGIN_REFERENCE,
C.EB_HOOK_BEGIN_SUBSCRIPT,
C.EB_HOOK_BEGIN_SUPERSCRIPT,
C.EB_HOOK_BEGIN_UNICODE,
C.EB_HOOK_BEGIN_WAVE,
C.EB_HOOK_END_CANDIDATE_GROUP,
C.EB_HOOK_END_CANDIDATE_LEAF,
C.EB_HOOK_END_CLICKABLE_AREA,
C.EB_HOOK_END_COLOR_GRAPHIC,
C.EB_HOOK_END_DECORATION,
C.EB_HOOK_END_EBXAC_GAIJI,
C.EB_HOOK_END_EMPHASIS,
C.EB_HOOK_END_GRAPHIC_REFERENCE,
C.EB_HOOK_END_GRAY_GRAPHIC,
C.EB_HOOK_END_IMAGE_PAGE,
C.EB_HOOK_END_IN_COLOR_GRAPHIC,
C.EB_HOOK_END_KEYWORD,
C.EB_HOOK_END_MONO_GRAPHIC,
C.EB_HOOK_END_MPEG,
C.EB_HOOK_END_NARROW,
C.EB_HOOK_END_NO_NEWLINE,
C.EB_HOOK_END_REFERENCE,
C.EB_HOOK_END_SUBSCRIPT,
C.EB_HOOK_END_SUPERSCRIPT,
C.EB_HOOK_END_UNICODE,
C.EB_HOOK_END_WAVE,
C.EB_HOOK_GRAPHIC_REFERENCE,
C.EB_HOOK_NEWLINE,
C.EB_HOOK_SET_INDENT,
}
for _, hookCode := range hookCodes {
if errEb := C.installHook(c.hookset, hookCode); errEb != C.EB_SUCCESS {
return fmt.Errorf("eb_set_hook failed with code %d", errEb)
}
}
return nil
}
func (c *Context) loadInternal(path string) (*Book, error) {
pathC := C.CString(path)
defer C.free(unsafe.Pointer(pathC))
if errEb := C.eb_bind(c.book, pathC); errEb != C.EB_SUCCESS {
return nil, fmt.Errorf("eb_bind failed with code %d", errEb)
}
var (
book Book
err error
)
if book.CharCode, err = c.loadCharCode(); err != nil {
return nil, err
}
if book.DiscCode, err = c.loadDiscCode(); err != nil {
return nil, err
}
if book.Subbooks, err = c.loadSubbooks(); err != nil {
return nil, err
}
return &book, nil
}
func (c *Context) loadCharCode() (string, error) {
var charCode C.EB_Character_Code
if errEb := C.eb_character_code(c.book, &charCode); errEb != C.EB_SUCCESS {
return "", fmt.Errorf("eb_character_code failed with code %d", errEb)
}
switch charCode {
case C.EB_CHARCODE_ISO8859_1:
return "iso8859-1", nil
case C.EB_CHARCODE_JISX0208:
return "jisx0208", nil
case C.EB_CHARCODE_JISX0208_GB2312:
return "jisx0208/gb2312", nil
default:
return "invalid", nil
}
}
func (c *Context) loadDiscCode() (string, error) {
var discCode C.EB_Disc_Code
if errEb := C.eb_disc_type(c.book, &discCode); errEb != C.EB_SUCCESS {
return "", fmt.Errorf("eb_disc_type failed with code %d", errEb)
}
switch discCode {
case C.EB_DISC_EB:
return "eb", nil
case C.EB_DISC_EPWING:
return "epwing", nil
default:
return "invalid", nil
}
}
func (c *Context) loadSubbooks() ([]BookSubbook, error) {
var (
subbookCodes [C.EB_MAX_SUBBOOKS]C.EB_Subbook_Code
subbookCount C.int
)
if errEb := C.eb_subbook_list(c.book, &subbookCodes[0], &subbookCount); errEb != C.EB_SUCCESS {
return nil, fmt.Errorf("eb_subbook_list failed with code %d", errEb)
}
var subbooks []BookSubbook
for i := 0; i < int(subbookCount); i++ {
subbook, err := c.loadSubbook(subbookCodes[i])
if err != nil {
return nil, err
}
subbooks = append(subbooks, *subbook)
}
return subbooks, nil
}
func (c *Context) loadSubbook(subbookCode C.EB_Subbook_Code) (*BookSubbook, error) {
if errEb := C.eb_set_subbook(c.book, subbookCode); errEb != C.EB_SUCCESS {
return nil, fmt.Errorf("eb_set_subbook failed with code %d", errEb)
}
var (
subbook BookSubbook
err error
)
if subbook.Title, err = c.loadTitle(); err != nil {
return nil, err
}
if subbook.Copyright, err = c.loadCopyright(); err != nil {
return nil, err
}
blocksSeen := make(map[uint32]bool)
if errEb := C.eb_search_all_alphabet(c.book); errEb == C.EB_SUCCESS {
entries, err := c.loadEntries(blocksSeen)
if err != nil {
return nil, err
}
subbook.Entries = append(subbook.Entries, entries...)
}
if errEb := C.eb_search_all_kana(c.book); errEb == C.EB_SUCCESS {
entries, err := c.loadEntries(blocksSeen)
if err != nil {
return nil, err
}
subbook.Entries = append(subbook.Entries, entries...)
}
if errEb := C.eb_search_all_asis(c.book); errEb == C.EB_SUCCESS {
entries, err := c.loadEntries(blocksSeen)
if err != nil {
return nil, err
}
subbook.Entries = append(subbook.Entries, entries...)
}
return &subbook, nil
}
func (c *Context) loadEntries(blocksSeen map[uint32]bool) ([]BookEntry, error) {
var entries []BookEntry
for {
var (
hits [256]C.EB_Hit
hitCount C.int
)
if errEb := C.eb_hit_list(c.book, (C.int)(len(hits)), &hits[0], &hitCount); errEb != C.EB_SUCCESS {
return nil, fmt.Errorf("eb_hit_list failed with code %d", errEb)
}
for _, hit := range hits[:hitCount] {
var (
entry BookEntry
err error
)
if entry.Heading, err = c.loadContent(hit.heading, blockTypeHeading); err != nil {
return nil, err
}
if entry.Text, err = c.loadContent(hit.text, blockTypeHeading); err != nil {
return nil, err
}
hasher := crc32.NewIEEE()
hasher.Write([]byte(entry.Heading))
hasher.Write([]byte(entry.Text))
sum := hasher.Sum32()
if seen, _ := blocksSeen[sum]; !seen {
entries = append(entries, entry)
blocksSeen[sum] = true
}
}
if hitCount == 0 {
return entries, nil
}
}
}
func (c *Context) loadTitle() (string, error) {
var data [C.EB_MAX_TITLE_LENGTH + 1]C.char
if errEb := C.eb_subbook_title(c.book, &data[0]); errEb != C.EB_SUCCESS {
return "", fmt.Errorf("eb_subbook_title failed with code %d", errEb)
}
return c.decoder.String(C.GoString(&data[0]))
}
func (c *Context) loadCopyright() (string, error) {
if C.eb_have_copyright(c.book) == 0 {
return "", nil
}
var position C.EB_Position
if errEb := C.eb_copyright(c.book, &position); errEb != C.EB_SUCCESS {
return "", fmt.Errorf("eb_copyright failed with code %d", errEb)
}
return c.loadContent(position, blockTypeText)
}
func (c *Context) loadContent(position C.EB_Position, blockType blockType) (string, error) {
for {
var (
data = (*C.char)(unsafe.Pointer(&c.buffer[0]))
dataSize = (C.ulong)(len(c.buffer))
dataUsed C.long
)
if errEb := C.eb_seek_text(c.book, &position); errEb != C.EB_SUCCESS {
return "", fmt.Errorf("eb_seek_text failed with code %d", errEb)
}
switch blockType {
case blockTypeHeading:
if errEb := C.eb_read_heading(c.book, nil, c.hookset, nil, dataSize, data, &dataUsed); errEb != C.EB_SUCCESS {
return "", fmt.Errorf("eb_read_heading failed with code %d", errEb)
}
case blockTypeText:
if errEb := C.eb_read_text(c.book, nil, c.hookset, nil, dataSize, data, &dataUsed); errEb != C.EB_SUCCESS {
return "", fmt.Errorf("eb_read_text failed with code %d", errEb)
}
default:
panic("invalid block type")
}
if dataUsed+8 >= (C.long)(dataSize) {
c.buffer = make([]byte, dataSize*2)
} else {
return c.decoder.String(C.GoString(data))
}
}
}
func Load(path string) (*Book, error) {
var context Context
if err := context.initialize(); err != nil {
return nil, err
}
defer context.shutdown()
return context.loadInternal(path)
}