package charmap import ( "bytes" "errors" "io" "git.sdf.org/CRThaze/vtTools/encoding/codetable" mnemonic "git.sdf.org/CRThaze/vtTools/encoding/decMSCMnemonics" equiv "git.sdf.org/CRThaze/vtTools/equivalence" "golang.org/x/text/encoding" "golang.org/x/text/transform" ) var ( ErrUnboundCodepage = errors.New("Codepage is not bound to a codespace") ) type transformer interface { transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) } type Codepage struct { charSet CharacterSet codeSpace *codetable.GraphicCodeSpace } func NewCodepage(set CharacterSet, graphicTable *codetable.GraphicCodeSpace) Codepage { return Codepage{ charSet: set, codeSpace: graphicTable, } } type DynamicCodepage struct { Codepage } func NewDynamicCodepage(set CharacterSet) DynamicCodepage { e := DynamicCodepage{ Codepage{charSet: set}, } return e } func (p *DynamicCodepage) Bind(graphicTable *codetable.GraphicCodeSpace) { if graphicTable != nil { p.codeSpace = graphicTable return } p.codeSpace = nil } func (p *DynamicCodepage) IsBoundTo(graphicTable *codetable.GraphicCodeSpace) bool { if p.codeSpace != nil && p.codeSpace == graphicTable { return true } return false } func (p *DynamicCodepage) BoundTo() *codetable.GraphicCodeSpace { return p.codeSpace } type ExtendedCodepage struct { base Codepage supplemental Codepage } func NewExtendedCodepage(sevenBitSet, eighthBitSet CharacterSet) ExtendedCodepage { return ExtendedCodepage{ base: Codepage{ charSet: sevenBitSet, codeSpace: &codetable.GLTable, }, supplemental: Codepage{ charSet: eighthBitSet, codeSpace: &codetable.GRTable, }, } } func (p Codepage) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { srcBuf := bytes.NewBuffer(src) for r, sbn, e := srcBuf.ReadRune(); e != io.EOF; r, sbn, e = srcBuf.ReadRune() { transBytes := make([]byte, 1, 1) if e != nil { err = e return } else if r == '\uFFFD' && sbn == 1 { transBytes[0] = mnemonic.SUB // MSC/ASCII Substituion character. } else { transBytes[0] = p.SafeTrans(r) } nSrc += sbn for i, b := range transBytes { nextIndex := nDst + i if len(dst) < nextIndex+1 { err = transform.ErrShortDst return } dst[nDst+i] = b nDst++ } } return } func (p ExtendedCodepage) transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { srcBuf := bytes.NewBuffer(src) for r, sbn, e := srcBuf.ReadRune(); e != io.EOF; r, sbn, e = srcBuf.ReadRune() { transBytes := make([]byte, 1, 1) if e != nil { err = e return } else if r == '\uFFFD' && sbn == 1 { transBytes[0] = mnemonic.SUB // MSC/ASCII Substituion character. } else { if tb, e := p.base.Trans(equiv.Normalize(r)); e == nil { transBytes[0] = tb } else { transBytes[0] = p.supplemental.SafeTrans(r) } } nSrc += sbn for i, b := range transBytes { nextIndex := nDst + i if len(dst) < nextIndex+1 { err = transform.ErrShortDst return } dst[nDst+i] = b nDst++ } } return } type vtEncEncoder struct { transform.NopResetter charmap transformer } func (en vtEncEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { return en.charmap.transform(dst, src, atEOF) } type vtEncDecoder struct { transform.NopResetter charmap Codepage } func (d vtEncDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { // TODO: Implement decode by looking up rune in charmap. return 0, 0, nil } func (p Codepage) NewDecoder() *encoding.Decoder { return &encoding.Decoder{ Transformer: vtEncDecoder{}, } } func (p Codepage) NewEncoder() *encoding.Encoder { return &encoding.Encoder{ Transformer: vtEncEncoder{}, } } func (p ExtendedCodepage) NewDecoder() *encoding.Decoder { return &encoding.Decoder{ Transformer: vtEncDecoder{}, } } func (p ExtendedCodepage) NewEncoder() *encoding.Encoder { return &encoding.Encoder{ Transformer: vtEncEncoder{}, } } func (p Codepage) Trans(r rune) (byte, error) { if p.codeSpace == nil { return 0, ErrUnboundCodepage } if b, ok := decMSCControlFull[r]; ok { return b, nil } else if xy := p.charSet.lookup.Get(r); xy != nil { // Discarding the error since we are confident // the coordinate will not be out of range. b, _ = p.codeSpace.Get(xy[0], xy[1]) return b, nil } return 0, errors.New("No translation for rune.") } func (p Codepage) MustTrans(r rune) byte { if b, err := p.Trans(r); err == nil { return b } return mnemonic.SUB } func (p Codepage) SafeTrans(r rune) byte { return p.MustTrans(equiv.Normalize(r)) } // // TransDynamic first calls Trans. If Trans returns an error, then it attempts to find the rune // // anywhere within the GraphicRepetoir. If it is able to find the rune, it adds the necessary // // escape sequences to temporarily load the needed character set to properly display the matching // // symbol. If none can be found, it retuns the same fall-back code-point as MustTrans. // func (e vtEncEncoder) TransDynamic(r rune) []byte { // if s, err := vt.Trans(r); err == nil { // return s // } // if vt.graphicRegistry[2].register.designated { // if vt.graphicRegistry[2].register.set.lookup.Has(r) { // if bs, err := vt.SingleShiftRune(2, r); err == nil { // return bs // } else { // panic(err) // } // } // } // if vt.graphicRegistry[3].register.designated { // if vt.graphicRegistry[3].register.set.lookup.Has(r) { // if bs, err := vt.SingleShiftRune(3, r); err == nil { // return bs // } else { // panic(err) // } // } // } // for i, set := range vt.graphicRepetoire { // if set == nil || !set.lookup.Has(r) { // continue // } // // TODO: Don't leave this in the register. // if err := vt.DesignateCharSet(i, 3); err != nil { // panic(err) // } // if bs, err := vt.SingleShiftRune(3, r); err == nil { // return bs // } else { // panic(err) // } // } // return UNKNOWN[:] // } // // SafeTrans uses an equivalency table for the given rune to determine a similiar rune that is // // present in the GraphicRepetoir. It then calls TransDynamic with that rune. // func (e vtEncEncoder) SafeTransDynamic(r rune) []byte { // return vt.TransDynamic(requiem.Equivalence.Normalize(r)) // }