YTSFlix_Go/vendor/github.com/asticode/go-astisub/teletext.go
2018-11-04 15:58:15 +01:00

1005 lines
36 KiB
Go

package astisub
import (
"context"
"io"
"math/bits"
"sort"
"strings"
"time"
"github.com/asticode/go-astilog"
"github.com/asticode/go-astitools/bits"
"github.com/asticode/go-astitools/ptr"
"github.com/asticode/go-astits"
"github.com/pkg/errors"
)
// Errors
var (
ErrNoValidTeletextPID = errors.New("astisub: no valid teletext PID")
)
type teletextCharset [96][]byte
type teletextNationalSubset [13][]byte
// Chapter: 15.2 | Page: 109 | Link: http://www.etsi.org/deliver/etsi_i_ets/300700_300799/300706/01_60/ets_300706e01p.pdf
// It is indexed by triplet1 then by national option subset code
var teletextCharsets = map[uint8]map[uint8]struct {
g0 *teletextCharset
g2 *teletextCharset
national *teletextNationalSubset
}{
0: {
0: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetEnglish},
1: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetFrench},
2: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetSwedishFinnishHungarian},
3: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetCzechSlovak},
4: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetGerman},
5: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetPortugueseSpanish},
6: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetItalian},
7: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
},
1: {
0: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetPolish},
1: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetFrench},
2: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetSwedishFinnishHungarian},
3: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetCzechSlovak},
4: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetGerman},
5: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
6: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetItalian},
7: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
},
2: {
0: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetEnglish},
1: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetFrench},
2: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetSwedishFinnishHungarian},
3: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetCzechSlovak},
4: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetGerman},
5: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetPortugueseSpanish},
6: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetItalian},
7: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
},
3: {
0: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
1: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
2: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
3: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
4: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
5: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetSerbianCroatianSlovenian},
6: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin},
7: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetRomanian},
},
4: {
0: {g0: teletextCharsetG0CyrillicOption1, g2: teletextCharsetG2Cyrillic},
1: {g0: teletextCharsetG0CyrillicOption2, g2: teletextCharsetG2Cyrillic},
2: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetEstonian},
3: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetCzechSlovak},
4: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetGerman},
5: {g0: teletextCharsetG0CyrillicOption3, g2: teletextCharsetG2Cyrillic},
6: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetLettishLithuanian},
},
6: {
3: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Latin, national: teletextNationalSubsetTurkish},
7: {g0: teletextCharsetG0Greek, g2: teletextCharsetG2Greek},
},
8: {
0: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Arabic, national: teletextNationalSubsetEnglish},
1: {g0: teletextCharsetG0Latin, g2: teletextCharsetG2Arabic, national: teletextNationalSubsetFrench},
7: {g0: teletextCharsetG0Arabic, g2: teletextCharsetG2Arabic},
},
10: {
5: {g0: teletextCharsetG0Hebrew, g2: teletextCharsetG2Arabic},
7: {g0: teletextCharsetG0Arabic, g2: teletextCharsetG2Arabic},
},
}
// Teletext G0 charsets
var (
teletextCharsetG0CyrillicOption1 = &teletextCharset{
[]byte{0x20}, []byte{0x21}, []byte{0x22}, []byte{0x23}, []byte{0x24}, []byte{0x25}, []byte{0xd1, 0x8b},
[]byte{0x27}, []byte{0x28}, []byte{0x29}, []byte{0x2a}, []byte{0x2b}, []byte{0x2c}, []byte{0x2d},
[]byte{0x2e}, []byte{0x2f}, []byte{0x30}, []byte{0x31}, []byte{0xe3, 0x88, 0x80}, []byte{0x33}, []byte{0x34},
[]byte{0x35}, []byte{0x36}, []byte{0x37}, []byte{0x38}, []byte{0x39}, []byte{0x3a}, []byte{0x3b},
[]byte{0x3c}, []byte{0x3d}, []byte{0x3e}, []byte{0x3f}, []byte{0xd0, 0xa7}, []byte{0xd0, 0x90},
[]byte{0xd0, 0x91}, []byte{0xd0, 0xa6}, []byte{0xd0, 0x94}, []byte{0xd0, 0x95}, []byte{0xd0, 0xa4},
[]byte{0xd0, 0x93}, []byte{0xd0, 0xa5}, []byte{0xd0, 0x98}, []byte{0xd0, 0x88}, []byte{0xd0, 0x9a},
[]byte{0xd0, 0x9b}, []byte{0xd0, 0x9c}, []byte{0xd0, 0x9d}, []byte{0xd0, 0x9e}, []byte{0xd0, 0x9f},
[]byte{0xd0, 0x8c}, []byte{0xd0, 0xa0}, []byte{0xd0, 0xa1}, []byte{0xd0, 0xa2}, []byte{0xd0, 0xa3},
[]byte{0xd0, 0x92}, []byte{0xd0, 0x83}, []byte{0xd0, 0x89}, []byte{0xd0, 0x8a}, []byte{0xd0, 0x97},
[]byte{0xd0, 0x8b}, []byte{0xd0, 0x96}, []byte{0xd0, 0x82}, []byte{0xd0, 0xa8}, []byte{0xd0, 0x8f},
[]byte{0xd1, 0x87}, []byte{0xd0, 0xb0}, []byte{0xd0, 0xb1}, []byte{0xd1, 0x86}, []byte{0xd0, 0xb4},
[]byte{0xd0, 0xb5}, []byte{0xd1, 0x84}, []byte{0xd0, 0xb3}, []byte{0xd1, 0x85}, []byte{0xd0, 0xb8},
[]byte{0xd0, 0xa8}, []byte{0xd0, 0xba}, []byte{0xd0, 0xbb}, []byte{0xd0, 0xbc}, []byte{0xd0, 0xbd},
[]byte{0xd0, 0xbe}, []byte{0xd0, 0xbf}, []byte{0xd0, 0xac}, []byte{0xd1, 0x80}, []byte{0xd1, 0x81},
[]byte{0xd1, 0x82}, []byte{0xd1, 0x83}, []byte{0xd0, 0xb2}, []byte{0xd0, 0xa3}, []byte{0xd0, 0xa9},
[]byte{0xd0, 0xaa}, []byte{0xd0, 0xb7}, []byte{0xd0, 0xab}, []byte{0xd0, 0xb6}, []byte{0xd0, 0xa2},
[]byte{0xd1, 0x88}, []byte{0xd0, 0xaf},
}
teletextCharsetG0CyrillicOption2 = &teletextCharset{
[]byte{0x20}, []byte{0x21}, []byte{0x22}, []byte{0x23}, []byte{0x24}, []byte{0x25}, []byte{0xd1, 0x8b},
[]byte{0x27}, []byte{0x28}, []byte{0x29}, []byte{0x2a}, []byte{0x2b}, []byte{0x2c}, []byte{0x2d},
[]byte{0x2e}, []byte{0x2f}, []byte{0x30}, []byte{0x31}, []byte{0x32}, []byte{0x33}, []byte{0x34},
[]byte{0x35}, []byte{0x36}, []byte{0x37}, []byte{0x38}, []byte{0x39}, []byte{0x3a}, []byte{0x3b},
[]byte{0x3c}, []byte{0x3d}, []byte{0x3e}, []byte{0x3f}, []byte{0xd0, 0xae}, []byte{0xd0, 0x90},
[]byte{0xd0, 0x91}, []byte{0xd0, 0xa6}, []byte{0xd0, 0x94}, []byte{0xd0, 0x95}, []byte{0xd0, 0xa4},
[]byte{0xd0, 0x93}, []byte{0xd0, 0xa5}, []byte{0xd0, 0x98}, []byte{0xd0, 0x99}, []byte{0xd0, 0x9a},
[]byte{0xd0, 0x9b}, []byte{0xd0, 0x9c}, []byte{0xd0, 0x9d}, []byte{0xd0, 0x9e}, []byte{0xd0, 0x9f},
[]byte{0xd0, 0xaf}, []byte{0xd0, 0xa0}, []byte{0xd0, 0xa1}, []byte{0xd0, 0xa2}, []byte{0xd0, 0xa3},
[]byte{0xd0, 0x96}, []byte{0xd0, 0x92}, []byte{0xd0, 0xac}, []byte{0xd0, 0xaa}, []byte{0xd0, 0x97},
[]byte{0xd0, 0xa8}, []byte{0xd0, 0xad}, []byte{0xd0, 0xa9}, []byte{0xd0, 0xa7}, []byte{0xd0, 0xab},
[]byte{0xd1, 0x8e}, []byte{0xd0, 0xb0}, []byte{0xd0, 0xb1}, []byte{0xd1, 0x86}, []byte{0xd0, 0xb4},
[]byte{0xd0, 0xb5}, []byte{0xd1, 0x84}, []byte{0xd0, 0xb3}, []byte{0xd1, 0x85}, []byte{0xd0, 0xb8},
[]byte{0xd0, 0xb9}, []byte{0xd0, 0xba}, []byte{0xd0, 0xbb}, []byte{0xd0, 0xbc}, []byte{0xd0, 0xbd},
[]byte{0xd0, 0xbe}, []byte{0xd0, 0xbf}, []byte{0xd1, 0x8f}, []byte{0xd1, 0x80}, []byte{0xd1, 0x81},
[]byte{0xd1, 0x82}, []byte{0xd1, 0x83}, []byte{0xd0, 0xb6}, []byte{0xd0, 0xb2}, []byte{0xd1, 0x8c},
[]byte{0xd1, 0x8a}, []byte{0xd0, 0xb7}, []byte{0xd1, 0x88}, []byte{0xd1, 0x8d}, []byte{0xd1, 0x89},
[]byte{0xd1, 0x87}, []byte{0xd1, 0x8b},
}
teletextCharsetG0CyrillicOption3 = &teletextCharset{
[]byte{0x20}, []byte{0x21}, []byte{0x22}, []byte{0x23}, []byte{0x24}, []byte{0x25}, []byte{0xc3, 0xaf},
[]byte{0x27}, []byte{0x28}, []byte{0x29}, []byte{0x2a}, []byte{0x2b}, []byte{0x2c}, []byte{0x2d},
[]byte{0x2e}, []byte{0x2f}, []byte{0x30}, []byte{0x31}, []byte{0x32}, []byte{0x33}, []byte{0x34},
[]byte{0x35}, []byte{0x36}, []byte{0x37}, []byte{0x38}, []byte{0x39}, []byte{0x3a}, []byte{0x3b},
[]byte{0x3c}, []byte{0x3d}, []byte{0x3e}, []byte{0x3f}, []byte{0xd0, 0xae}, []byte{0xd0, 0x90},
[]byte{0xd0, 0x91}, []byte{0xd0, 0xa6}, []byte{0xd0, 0x94}, []byte{0xd0, 0x95}, []byte{0xd0, 0xa4},
[]byte{0xd0, 0x93}, []byte{0xd0, 0xa5}, []byte{0xd0, 0x98}, []byte{0xd0, 0x99}, []byte{0xd0, 0x9a},
[]byte{0xd0, 0x9b}, []byte{0xd0, 0x9c}, []byte{0xd0, 0x9d}, []byte{0xd0, 0x9e}, []byte{0xd0, 0x9f},
[]byte{0xd0, 0xaf}, []byte{0xd0, 0xa0}, []byte{0xd0, 0xa1}, []byte{0xd0, 0xa2}, []byte{0xd0, 0xa3},
[]byte{0xd0, 0x96}, []byte{0xd0, 0x92}, []byte{0xd0, 0xac}, []byte{0x49}, []byte{0xd0, 0x97},
[]byte{0xd0, 0xa8}, []byte{0xd0, 0xad}, []byte{0xd0, 0xa9}, []byte{0xd0, 0xa7}, []byte{0xc3, 0x8f},
[]byte{0xd1, 0x8e}, []byte{0xd0, 0xb0}, []byte{0xd0, 0xb1}, []byte{0xd1, 0x86}, []byte{0xd0, 0xb4},
[]byte{0xd0, 0xb5}, []byte{0xd1, 0x84}, []byte{0xd0, 0xb3}, []byte{0xd1, 0x85}, []byte{0xd0, 0xb8},
[]byte{0xd0, 0xb9}, []byte{0xd0, 0xba}, []byte{0xd0, 0xbb}, []byte{0xd0, 0xbc}, []byte{0xd0, 0xbd},
[]byte{0xd0, 0xbe}, []byte{0xd0, 0xbf}, []byte{0xd1, 0x8f}, []byte{0xd1, 0x80}, []byte{0xd1, 0x81},
[]byte{0xd1, 0x82}, []byte{0xd1, 0x83}, []byte{0xd0, 0xb6}, []byte{0xd0, 0xb2}, []byte{0xd1, 0x8c},
[]byte{0x69}, []byte{0xd0, 0xb7}, []byte{0xd1, 0x88}, []byte{0xd1, 0x8d}, []byte{0xd1, 0x89},
[]byte{0xd1, 0x87}, []byte{0xc3, 0xbf},
}
teletextCharsetG0Greek = &teletextCharset{
[]byte{0x20}, []byte{0x21}, []byte{0x22}, []byte{0x23}, []byte{0x24}, []byte{0x25}, []byte{0x26},
[]byte{0x27}, []byte{0x28}, []byte{0x29}, []byte{0x2a}, []byte{0x2b}, []byte{0x2c}, []byte{0x2d},
[]byte{0x2e}, []byte{0x2f}, []byte{0x30}, []byte{0x31}, []byte{0x32}, []byte{0x33}, []byte{0x34},
[]byte{0x35}, []byte{0x36}, []byte{0x37}, []byte{0x38}, []byte{0x39}, []byte{0x3a}, []byte{0x3b},
[]byte{0x3c}, []byte{0x3d}, []byte{0x3e}, []byte{0x3f}, []byte{0xce, 0x90}, []byte{0xce, 0x91},
[]byte{0xce, 0x92}, []byte{0xce, 0x93}, []byte{0xce, 0x94}, []byte{0xce, 0x95}, []byte{0xce, 0x96},
[]byte{0xce, 0x97}, []byte{0xce, 0x98}, []byte{0xce, 0x99}, []byte{0xce, 0x9a}, []byte{0xce, 0x9b},
[]byte{0xce, 0x9c}, []byte{0xce, 0x9d}, []byte{0xce, 0x9e}, []byte{0xce, 0x9f}, []byte{0xce, 0xa0},
[]byte{0xce, 0xa1}, []byte{0xce, 0xa2}, []byte{0xce, 0xa3}, []byte{0xce, 0xa4}, []byte{0xce, 0xa5},
[]byte{0xce, 0xa6}, []byte{0xce, 0xa7}, []byte{0xce, 0xa8}, []byte{0xce, 0xa9}, []byte{0xce, 0xaa},
[]byte{0xce, 0xab}, []byte{0xce, 0xac}, []byte{0xce, 0xad}, []byte{0xce, 0xae}, []byte{0xce, 0xaf},
[]byte{0xce, 0xb0}, []byte{0xce, 0xb1}, []byte{0xce, 0xb2}, []byte{0xce, 0xb3}, []byte{0xce, 0xb4},
[]byte{0xce, 0xb5}, []byte{0xce, 0xb6}, []byte{0xce, 0xb7}, []byte{0xce, 0xb8}, []byte{0xce, 0xb9},
[]byte{0xce, 0xba}, []byte{0xce, 0xbb}, []byte{0xce, 0xbc}, []byte{0xce, 0xbd}, []byte{0xce, 0xbe},
[]byte{0xce, 0xbf}, []byte{0xcf, 0x80}, []byte{0xcf, 0x81}, []byte{0xcf, 0x82}, []byte{0xcf, 0x83},
[]byte{0xcf, 0x84}, []byte{0xcf, 0x85}, []byte{0xcf, 0x86}, []byte{0xcf, 0x87}, []byte{0xcf, 0x88},
[]byte{0xcf, 0x89}, []byte{0xcf, 0x8a}, []byte{0xcf, 0x8b}, []byte{0xcf, 0x8c}, []byte{0xcf, 0x8d},
[]byte{0xcf, 0x8e}, []byte{0xcf, 0x8f},
}
teletextCharsetG0Latin = &teletextCharset{
[]byte{0x20}, []byte{0x21}, []byte{0x22}, []byte{0xc2, 0xa3}, []byte{0x24}, []byte{0x25}, []byte{0x26},
[]byte{0x27}, []byte{0x28}, []byte{0x29}, []byte{0x2a}, []byte{0x2b}, []byte{0x2c}, []byte{0x2d},
[]byte{0x2e}, []byte{0x2f}, []byte{0x30}, []byte{0x31}, []byte{0x32}, []byte{0x33}, []byte{0x34},
[]byte{0x35}, []byte{0x36}, []byte{0x37}, []byte{0x38}, []byte{0x39}, []byte{0x3a}, []byte{0x3b},
[]byte{0x3c}, []byte{0x3d}, []byte{0x3e}, []byte{0x3f}, []byte{0x40}, []byte{0x41}, []byte{0x42},
[]byte{0x43}, []byte{0x44}, []byte{0x45}, []byte{0x46}, []byte{0x47}, []byte{0x48}, []byte{0x49},
[]byte{0x4a}, []byte{0x4b}, []byte{0x4c}, []byte{0x4d}, []byte{0x4e}, []byte{0x4f}, []byte{0x50},
[]byte{0x51}, []byte{0x52}, []byte{0x53}, []byte{0x54}, []byte{0x55}, []byte{0x56}, []byte{0x57},
[]byte{0x58}, []byte{0x59}, []byte{0x5a}, []byte{0xc2, 0xab}, []byte{0xc2, 0xbd}, []byte{0xc2, 0xbb},
[]byte{0x5e}, []byte{0x23}, []byte{0x2d}, []byte{0x61}, []byte{0x62}, []byte{0x63}, []byte{0x64},
[]byte{0x65}, []byte{0x66}, []byte{0x67}, []byte{0x68}, []byte{0x69}, []byte{0x6a}, []byte{0x6b},
[]byte{0x6c}, []byte{0x6d}, []byte{0x6e}, []byte{0x6f}, []byte{0x70}, []byte{0x71}, []byte{0x72},
[]byte{0x73}, []byte{0x74}, []byte{0x75}, []byte{0x76}, []byte{0x77}, []byte{0x78}, []byte{0x79},
[]byte{0x7a}, []byte{0xc2, 0xbc}, []byte{0xc2, 0xa6}, []byte{0xc2, 0xbe}, []byte{0xc3, 0xb7}, []byte{0x7f},
}
// TODO Add
teletextCharsetG0Arabic = teletextCharsetG0Latin
teletextCharsetG0Hebrew = teletextCharsetG0Latin
)
// Teletext G2 charsets
var (
teletextCharsetG2Latin = &teletextCharset{
[]byte{0x20}, []byte{0xc2, 0xa1}, []byte{0xc2, 0xa2}, []byte{0xc2, 0xa3}, []byte{0x24},
[]byte{0xc2, 0xa5}, []byte{0x23}, []byte{0xc2, 0xa7}, []byte{0xc2, 0xa4}, []byte{0xe2, 0x80, 0x98},
[]byte{0xe2, 0x80, 0x9c}, []byte{0xc2, 0xab}, []byte{0xe2, 0x86, 0x90}, []byte{0xe2, 0x86, 0x91},
[]byte{0xe2, 0x86, 0x92}, []byte{0xe2, 0x86, 0x93}, []byte{0xc2, 0xb0}, []byte{0xc2, 0xb1},
[]byte{0xc2, 0xb2}, []byte{0xc2, 0xb3}, []byte{0xc3, 0x97}, []byte{0xc2, 0xb5}, []byte{0xc2, 0xb6},
[]byte{0xc2, 0xb7}, []byte{0xc3, 0xb7}, []byte{0xe2, 0x80, 0x99}, []byte{0xe2, 0x80, 0x9d},
[]byte{0xc2, 0xbb}, []byte{0xc2, 0xbc}, []byte{0xc2, 0xbd}, []byte{0xc2, 0xbe}, []byte{0xc2, 0xbf},
[]byte{0x20}, []byte{0xcc, 0x80}, []byte{0xcc, 0x81}, []byte{0xcc, 0x82}, []byte{0xcc, 0x83},
[]byte{0xcc, 0x84}, []byte{0xcc, 0x86}, []byte{0xcc, 0x87}, []byte{0xcc, 0x88}, []byte{0x00},
[]byte{0xcc, 0x8a}, []byte{0xcc, 0xa7}, []byte{0x5f}, []byte{0xcc, 0x8b}, []byte{0xcc, 0xa8},
[]byte{0xcc, 0x8c}, []byte{0xe2, 0x80, 0x95}, []byte{0xc2, 0xb9}, []byte{0xc2, 0xae}, []byte{0xc2, 0xa9},
[]byte{0xe2, 0x84, 0xa2}, []byte{0xe2, 0x99, 0xaa}, []byte{0xe2, 0x82, 0xac}, []byte{0xe2, 0x80, 0xb0},
[]byte{0xce, 0xb1}, []byte{0x00}, []byte{0x00}, []byte{0x00}, []byte{0xe2, 0x85, 0x9b},
[]byte{0xe2, 0x85, 0x9c}, []byte{0xe2, 0x85, 0x9d}, []byte{0xe2, 0x85, 0x9e}, []byte{0xce, 0xa9},
[]byte{0xc3, 0x86}, []byte{0xc4, 0x90}, []byte{0xc2, 0xaa}, []byte{0xc4, 0xa6}, []byte{0x00},
[]byte{0xc4, 0xb2}, []byte{0xc4, 0xbf}, []byte{0xc5, 0x81}, []byte{0xc3, 0x98}, []byte{0xc5, 0x92},
[]byte{0xc2, 0xba}, []byte{0xc3, 0x9e}, []byte{0xc5, 0xa6}, []byte{0xc5, 0x8a}, []byte{0xc5, 0x89},
[]byte{0xc4, 0xb8}, []byte{0xc3, 0xa6}, []byte{0xc4, 0x91}, []byte{0xc3, 0xb0}, []byte{0xc4, 0xa7},
[]byte{0xc4, 0xb1}, []byte{0xc4, 0xb3}, []byte{0xc5, 0x80}, []byte{0xc5, 0x82}, []byte{0xc3, 0xb8},
[]byte{0xc5, 0x93}, []byte{0xc3, 0x9f}, []byte{0xc3, 0xbe}, []byte{0xc5, 0xa7}, []byte{0xc5, 0x8b},
[]byte{0x20},
}
// TODO Add
teletextCharsetG2Arabic = teletextCharsetG2Latin
teletextCharsetG2Cyrillic = teletextCharsetG2Latin
teletextCharsetG2Greek = teletextCharsetG2Latin
)
var teletextNationalSubsetCharactersPositionInG0 = [13]uint8{0x03, 0x04, 0x20, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x5b, 0x5c, 0x5d, 0x5e}
// Teletext national subsets
var (
teletextNationalSubsetCzechSlovak = &teletextNationalSubset{
[]byte{0x23}, []byte{0xc5, 0xaf}, []byte{0xc4, 0x8d}, []byte{0xc5, 0xa5}, []byte{0xc5, 0xbe},
[]byte{0xc3, 0xbd}, []byte{0xc3, 0xad}, []byte{0xc5, 0x99}, []byte{0xc3, 0xa9}, []byte{0xc3, 0xa1},
[]byte{0xc4, 0x9b}, []byte{0xc3, 0xba}, []byte{0xc5, 0xa1},
}
teletextNationalSubsetEnglish = &teletextNationalSubset{
[]byte{0xc2, 0xa3}, []byte{0x24}, []byte{0x40}, []byte{0xc2, 0xab}, []byte{0xc2, 0xbd}, []byte{0xc2, 0xbb},
[]byte{0x5e}, []byte{0x23}, []byte{0x2d}, []byte{0xc2, 0xbc}, []byte{0xc2, 0xa6}, []byte{0xc2, 0xbe},
[]byte{0xc3, 0xb7},
}
teletextNationalSubsetEstonian = &teletextNationalSubset{
[]byte{0x23}, []byte{0xc3, 0xb5}, []byte{0xc5, 0xa0}, []byte{0xc3, 0x84}, []byte{0xc3, 0x96},
[]byte{0xc5, 0xbe}, []byte{0xc3, 0x9c}, []byte{0xc3, 0x95}, []byte{0xc5, 0xa1}, []byte{0xc3, 0xa4},
[]byte{0xc3, 0xb6}, []byte{0xc5, 0xbe}, []byte{0xc3, 0xbc},
}
teletextNationalSubsetFrench = &teletextNationalSubset{
[]byte{0xc3, 0xa9}, []byte{0xc3, 0xaf}, []byte{0xc3, 0xa0}, []byte{0xc3, 0xab}, []byte{0xc3, 0xaa},
[]byte{0xc3, 0xb9}, []byte{0xc3, 0xae}, []byte{0x23}, []byte{0xc3, 0xa8}, []byte{0xc3, 0xa2},
[]byte{0xc3, 0xb4}, []byte{0xc3, 0xbb}, []byte{0xc3, 0xa7},
}
teletextNationalSubsetGerman = &teletextNationalSubset{
[]byte{0x23}, []byte{0x24}, []byte{0xc2, 0xa7}, []byte{0xc3, 0x84}, []byte{0xc3, 0x96}, []byte{0xc3, 0x9c},
[]byte{0x5e}, []byte{0x5f}, []byte{0xc2, 0xb0}, []byte{0xc3, 0xa4}, []byte{0xc3, 0xb6}, []byte{0xc3, 0xbc},
[]byte{0xc3, 0x9f},
}
teletextNationalSubsetItalian = &teletextNationalSubset{
[]byte{0xc2, 0xa3}, []byte{0x24}, []byte{0xc3, 0xa9}, []byte{0xc2, 0xb0}, []byte{0xc3, 0xa7},
[]byte{0xc2, 0xbb}, []byte{0x5e}, []byte{0x23}, []byte{0xc3, 0xb9}, []byte{0xc3, 0xa0}, []byte{0xc3, 0xb2},
[]byte{0xc3, 0xa8}, []byte{0xc3, 0xac},
}
teletextNationalSubsetLettishLithuanian = &teletextNationalSubset{
[]byte{0x23}, []byte{0x24}, []byte{0xc5, 0xa0}, []byte{0xc4, 0x97}, []byte{0xc4, 0x99}, []byte{0xc5, 0xbd},
[]byte{0xc4, 0x8d}, []byte{0xc5, 0xab}, []byte{0xc5, 0xa1}, []byte{0xc4, 0x85}, []byte{0xc5, 0xb3},
[]byte{0xc5, 0xbe}, []byte{0xc4, 0xaf},
}
teletextNationalSubsetPolish = &teletextNationalSubset{
[]byte{0x23}, []byte{0xc5, 0x84}, []byte{0xc4, 0x85}, []byte{0xc5, 0xbb}, []byte{0xc5, 0x9a},
[]byte{0xc5, 0x81}, []byte{0xc4, 0x87}, []byte{0xc3, 0xb3}, []byte{0xc4, 0x99}, []byte{0xc5, 0xbc},
[]byte{0xc5, 0x9b}, []byte{0xc5, 0x82}, []byte{0xc5, 0xba},
}
teletextNationalSubsetPortugueseSpanish = &teletextNationalSubset{
[]byte{0xc3, 0xa7}, []byte{0x24}, []byte{0xc2, 0xa1}, []byte{0xc3, 0xa1}, []byte{0xc3, 0xa9},
[]byte{0xc3, 0xad}, []byte{0xc3, 0xb3}, []byte{0xc3, 0xba}, []byte{0xc2, 0xbf}, []byte{0xc3, 0xbc},
[]byte{0xc3, 0xb1}, []byte{0xc3, 0xa8}, []byte{0xc3, 0xa0},
}
teletextNationalSubsetRomanian = &teletextNationalSubset{
[]byte{0x23}, []byte{0xc2, 0xa4}, []byte{0xc5, 0xa2}, []byte{0xc3, 0x82}, []byte{0xc5, 0x9e},
[]byte{0xc4, 0x82}, []byte{0xc3, 0x8e}, []byte{0xc4, 0xb1}, []byte{0xc5, 0xa3}, []byte{0xc3, 0xa2},
[]byte{0xc5, 0x9f}, []byte{0xc4, 0x83}, []byte{0xc3, 0xae},
}
teletextNationalSubsetSerbianCroatianSlovenian = &teletextNationalSubset{
[]byte{0x23}, []byte{0xc3, 0x8b}, []byte{0xc4, 0x8c}, []byte{0xc4, 0x86}, []byte{0xc5, 0xbd},
[]byte{0xc4, 0x90}, []byte{0xc5, 0xa0}, []byte{0xc3, 0xab}, []byte{0xc4, 0x8d}, []byte{0xc4, 0x87},
[]byte{0xc5, 0xbe}, []byte{0xc4, 0x91}, []byte{0xc5, 0xa1},
}
teletextNationalSubsetSwedishFinnishHungarian = &teletextNationalSubset{
[]byte{0x23}, []byte{0xc2, 0xa4}, []byte{0xc3, 0x89}, []byte{0xc3, 0x84}, []byte{0xc3, 0x96},
[]byte{0xc3, 0x85}, []byte{0xc3, 0x9c}, []byte{0x5f}, []byte{0xc3, 0xa9}, []byte{0xc3, 0xa4},
[]byte{0xc3, 0xb6}, []byte{0xc3, 0xa5}, []byte{0xc3, 0xbc},
}
teletextNationalSubsetTurkish = &teletextNationalSubset{
[]byte{0x54}, []byte{0xc4, 0x9f}, []byte{0xc4, 0xb0}, []byte{0xc5, 0x9e}, []byte{0xc3, 0x96},
[]byte{0xc3, 0x87}, []byte{0xc3, 0x9c}, []byte{0xc4, 0x9e}, []byte{0xc4, 0xb1}, []byte{0xc5, 0x9f},
[]byte{0xc3, 0xb6}, []byte{0xc3, 0xa7}, []byte{0xc3, 0xbc},
}
)
// Teletext PES data types
const (
teletextPESDataTypeEBU = "EBU"
teletextPESDataTypeUnknown = "unknown"
)
func teletextPESDataType(dataIdentifier uint8) string {
switch {
case dataIdentifier >= 0x10 && dataIdentifier <= 0x1f:
return teletextPESDataTypeEBU
}
return teletextPESDataTypeUnknown
}
// Teletext PES data unit ids
const (
teletextPESDataUnitIDEBUNonSubtitleData = 0x2
teletextPESDataUnitIDEBUSubtitleData = 0x3
teletextPESDataUnitIDStuffing = 0xff
)
// TeletextOptions represents teletext options
type TeletextOptions struct {
Page int
PID int
}
// ReadFromTeletext parses a teletext content
// http://www.etsi.org/deliver/etsi_en/300400_300499/300472/01.03.01_60/en_300472v010301p.pdf
// http://www.etsi.org/deliver/etsi_i_ets/300700_300799/300706/01_60/ets_300706e01p.pdf
// TODO Update README
// TODO Add tests
func ReadFromTeletext(r io.Reader, o TeletextOptions) (s *Subtitles, err error) {
// Init
s = &Subtitles{}
var dmx = astits.New(context.Background(), r)
// Get the teletext PID
var pid uint16
if pid, err = teletextPID(dmx, o); err != nil {
if err != ErrNoValidTeletextPID {
err = errors.Wrap(err, "astisub: getting teletext PID failed")
}
return
}
// Create character decoder
cd := newTeletextCharacterDecoder()
// Create page buffer
b := newTeletextPageBuffer(o.Page, cd)
// Loop in data
var firstTime, lastTime time.Time
var d *astits.Data
var ps []*teletextPage
for {
// Fetch next data
if d, err = dmx.NextData(); err != nil {
if err == astits.ErrNoMorePackets {
err = nil
break
}
err = errors.Wrap(err, "astisub: fetching next data failed")
return
}
// We only parse PES data
if d.PES == nil {
continue
}
// This data is not of interest to us
if d.PID != pid || d.PES.Header.StreamID != astits.StreamIDPrivateStream1 {
continue
}
// Get time
t := teletextDataTime(d)
if t.IsZero() {
continue
}
// First and last time
if firstTime.IsZero() || firstTime.After(t) {
firstTime = t
}
if lastTime.IsZero() || lastTime.Before(t) {
lastTime = t
}
// Append pages
ps = append(ps, b.process(d.PES, t)...)
}
// Dump buffer
ps = append(ps, b.dump(lastTime)...)
// Parse pages
for _, p := range ps {
p.parse(s, cd, firstTime)
}
return
}
// TODO Add tests
func teletextDataTime(d *astits.Data) time.Time {
if d.PES.Header != nil && d.PES.Header.OptionalHeader != nil && d.PES.Header.OptionalHeader.PTS != nil {
return d.PES.Header.OptionalHeader.PTS.Time()
} else if d.FirstPacket != nil && d.FirstPacket.AdaptationField != nil && d.FirstPacket.AdaptationField.PCR != nil {
return d.FirstPacket.AdaptationField.PCR.Time()
}
return time.Time{}
}
// If the PID teletext option is not indicated, it will walk through the ts data until it reaches a PMT packet to
// detect the first valid teletext PID
// TODO Add tests
func teletextPID(dmx *astits.Demuxer, o TeletextOptions) (pid uint16, err error) {
// PID is in the options
if o.PID > 0 {
pid = uint16(o.PID)
return
}
// Loop in data
var d *astits.Data
for {
// Fetch next data
if d, err = dmx.NextData(); err != nil {
if err == astits.ErrNoMorePackets {
err = ErrNoValidTeletextPID
return
}
err = errors.Wrap(err, "astisub: fetching next data failed")
return
}
// PMT data
if d.PMT != nil {
// Retrieve valid teletext PIDs
var pids []uint16
for _, s := range d.PMT.ElementaryStreams {
for _, dsc := range s.ElementaryStreamDescriptors {
if dsc.Tag == astits.DescriptorTagTeletext || dsc.Tag == astits.DescriptorTagVBITeletext {
pids = append(pids, s.ElementaryPID)
}
}
}
// No valid teletext PIDs
if len(pids) == 0 {
err = ErrNoValidTeletextPID
return
}
// Set pid
pid = pids[0]
astilog.Debugf("astisub: no teletext pid specified, using pid %d", pid)
// Rewind
if _, err = dmx.Rewind(); err != nil {
err = errors.Wrap(err, "astisub: rewinding failed")
return
}
return
}
}
return
}
type teletextPageBuffer struct {
cd *teletextCharacterDecoder
currentPage *teletextPage
donePages []*teletextPage
magazineNumber uint8
pageNumber int
receiving bool
}
func newTeletextPageBuffer(page int, cd *teletextCharacterDecoder) *teletextPageBuffer {
return &teletextPageBuffer{
cd: cd,
magazineNumber: uint8(page / 100),
pageNumber: page % 100,
}
}
// TODO Add tests
func (b *teletextPageBuffer) dump(lastTime time.Time) (ps []*teletextPage) {
if b.currentPage != nil {
b.currentPage.end = lastTime
ps = []*teletextPage{b.currentPage}
}
return
}
// TODO Add tests
func (b *teletextPageBuffer) process(d *astits.PESData, t time.Time) (ps []*teletextPage) {
// Data identifier
var offset int
dataIdentifier := uint8(d.Data[offset])
offset += 1
// Check data type
if teletextPESDataType(dataIdentifier) != teletextPESDataTypeEBU {
return
}
// Loop through data units
for offset < len(d.Data) {
// ID
id := uint8(d.Data[offset])
offset += 1
// Length
length := uint8(d.Data[offset])
offset += 1
// Offset end
offsetEnd := offset + int(length)
if offsetEnd > len(d.Data) {
break
}
// Parse data unit
b.parseDataUnit(d.Data[offset:offsetEnd], id, t)
// Seek to end of data unit
offset = offsetEnd
}
// Dump buffer
ps = b.donePages
b.donePages = []*teletextPage(nil)
return ps
}
// TODO Add tests
func (b *teletextPageBuffer) parseDataUnit(i []byte, id uint8, t time.Time) {
// Check id
if id != teletextPESDataUnitIDEBUSubtitleData {
return
}
// Field parity: i[0]&0x20 > 0
// Line offset: uint8(i[0] & 0x1f)
// Framing code
framingCode := uint8(i[1])
// Check framing code
if framingCode != 0xe4 {
return
}
// Magazine number and packet number
h1, ok := astibits.Hamming84Decode(i[2])
if !ok {
return
}
h2, ok := astibits.Hamming84Decode(i[3])
if !ok {
return
}
h := h2<<4 | h1
magazineNumber := h & 0x7
if magazineNumber == 0 {
magazineNumber = 8
}
packetNumber := h >> 3
// Parse packet
b.parsePacket(i[4:], magazineNumber, packetNumber, t)
return
}
// TODO Add tests
func (b *teletextPageBuffer) parsePacket(i []byte, magazineNumber, packetNumber uint8, t time.Time) {
if packetNumber == 0 {
b.parsePacketHeader(i, magazineNumber, t)
} else if b.receiving && magazineNumber == b.magazineNumber && (packetNumber >= 1 && packetNumber <= 25) {
b.parsePacketData(i, packetNumber)
} else {
// Designation code
designationCode, ok := astibits.Hamming84Decode(i[0])
if !ok {
return
}
// Parse packet
if b.receiving && magazineNumber == b.magazineNumber && packetNumber == 26 {
// TODO Implement
} else if b.receiving && magazineNumber == b.magazineNumber && packetNumber == 28 {
b.parsePacket28And29(i[1:], packetNumber, designationCode)
} else if magazineNumber == b.magazineNumber && packetNumber == 29 {
b.parsePacket28And29(i[1:], packetNumber, designationCode)
} else if magazineNumber == 8 && packetNumber == 30 {
b.parsePacket30(i, designationCode)
}
}
}
// TODO Add tests
func (b *teletextPageBuffer) parsePacketHeader(i []byte, magazineNumber uint8, t time.Time) (transmissionDone bool) {
// Page number units
pageNumberUnits, ok := astibits.Hamming84Decode(i[0])
if !ok {
return
}
// Page number tens
pageNumberTens, ok := astibits.Hamming84Decode(i[1])
if !ok {
return
}
pageNumber := int(pageNumberTens)*10 + int(pageNumberUnits)
// 0xff is a reserved page number value
if pageNumberTens == 0xf && pageNumberUnits == 0xf {
return
}
// Update magazine and page number
if b.magazineNumber == 0 && b.pageNumber == 0 {
// C6
controlBits, ok := astibits.Hamming84Decode(i[5])
if !ok {
return
}
subtitleFlag := controlBits&0x8 > 0
// This is a subtitle page
if subtitleFlag {
b.magazineNumber = magazineNumber
b.pageNumber = pageNumber
astilog.Debugf("astisub: no teletext page specified, using page %d%.2d", b.magazineNumber, b.pageNumber)
}
}
// C11 --> C14
controlBits, ok := astibits.Hamming84Decode(i[7])
if !ok {
return
}
magazineSerial := controlBits&0x1 > 0
charsetCode := controlBits >> 1
// Page transmission is done
if b.receiving && ((magazineSerial && pageNumber != b.pageNumber) ||
(!magazineSerial && pageNumber != b.pageNumber && magazineNumber == b.magazineNumber)) {
b.receiving = false
return
}
// Invalid magazine or page number
if pageNumber != b.pageNumber || magazineNumber != b.magazineNumber {
return
}
// Now that we know when the previous page ends we can add it to the done slice
if b.currentPage != nil {
b.currentPage.end = t
b.donePages = append(b.donePages, b.currentPage)
}
// Reset
b.receiving = true
b.currentPage = newTeletextPage(charsetCode, t)
return
}
// TODO Add tests
func (b *teletextPageBuffer) parsePacketData(i []byte, packetNumber uint8) {
// Make sure the map is initialized
if _, ok := b.currentPage.data[packetNumber]; !ok {
b.currentPage.data[packetNumber] = make([]byte, 40)
}
// Loop through input
b.currentPage.rows = append(b.currentPage.rows, int(packetNumber))
for idx := uint8(0); idx < 40; idx++ {
v, ok := astibits.Parity(bits.Reverse8(i[idx]))
if !ok {
v = 0
}
b.currentPage.data[packetNumber][idx] = v
}
}
// TODO Add tests
func (b *teletextPageBuffer) parsePacket28And29(i []byte, packetNumber, designationCode uint8) {
// Invalid designation code
if designationCode != 0 && designationCode != 4 {
return
}
// Triplet 1
// TODO Implement hamming 24/18
triplet1, ok := astibits.Hamming2418Decode(uint32(i[2]<<16) | uint32(i[1])<<8 | uint32(i[0]))
if !ok {
return
}
// We only process x/28 format 1
if packetNumber == 28 && triplet1&0xf > 0 {
return
}
// Update character decoder
if packetNumber == 28 {
b.cd.setTripletX28(triplet1)
} else {
b.cd.setTripletM29(triplet1)
}
return
}
// TODO Add tests
func (b *teletextPageBuffer) parsePacket30(i []byte, designationCode uint8) {
// Switch on designation code to determine format
switch designationCode {
case 0, 1:
b.parsePacket30Format1(i)
case 2, 3:
b.parsePacket30Format2(i)
}
}
func (b *teletextPageBuffer) parsePacket30Format1(i []byte) {
// TODO Implement
}
func (b *teletextPageBuffer) parsePacket30Format2(i []byte) {
// TODO Implement
}
type teletextCharacterDecoder struct {
c teletextCharset
lastPageCharsetCode *uint8
tripletM29 *uint32
tripletX28 *uint32
}
func newTeletextCharacterDecoder() *teletextCharacterDecoder {
return &teletextCharacterDecoder{}
}
// TODO Add tests
func (d *teletextCharacterDecoder) setTripletM29(i uint32) {
if *d.tripletM29 != i {
d.tripletM29 = astiptr.UInt32(i)
d.updateCharset(d.lastPageCharsetCode, true)
}
}
// TODO Add tests
func (d *teletextCharacterDecoder) setTripletX28(i uint32) {
if *d.tripletX28 != i {
d.tripletX28 = astiptr.UInt32(i)
d.updateCharset(d.lastPageCharsetCode, true)
}
}
// TODO Add tests
func (d *teletextCharacterDecoder) decode(i byte) []byte {
if i < 0x20 {
return []byte{}
}
return d.c[i-0x20]
}
// TODO Add tests
func (d *teletextCharacterDecoder) updateCharset(pageCharsetCode *uint8, force bool) {
// Charset is up to date
if d.lastPageCharsetCode != nil && *pageCharsetCode == *d.lastPageCharsetCode && !force {
return
}
d.lastPageCharsetCode = pageCharsetCode
// Get triplet
var triplet uint32
if d.tripletX28 != nil {
triplet = *d.tripletX28
} else if d.tripletM29 != nil {
triplet = *d.tripletM29
}
// Get charsets
d.c = *teletextCharsetG0Latin
var nationalOptionSubset *teletextNationalSubset
if v1, ok := teletextCharsets[uint8((triplet&0x3f80)>>10)]; ok {
if v2, ok := v1[*pageCharsetCode]; ok {
d.c = *v2.g0
nationalOptionSubset = v2.national
}
}
// Update g0 with national option subset
if nationalOptionSubset != nil {
for k, v := range nationalOptionSubset {
d.c[teletextNationalSubsetCharactersPositionInG0[k]] = v
}
}
return
}
type teletextPage struct {
charsetCode uint8
data map[uint8][]byte
end time.Time
rows []int
start time.Time
}
func newTeletextPage(charsetCode uint8, start time.Time) *teletextPage {
return &teletextPage{
charsetCode: charsetCode,
data: make(map[uint8][]byte),
start: start,
}
}
func (p *teletextPage) parse(s *Subtitles, d *teletextCharacterDecoder, firstTime time.Time) {
// Update charset
d.updateCharset(astiptr.UInt8(p.charsetCode), false)
// No data
if len(p.data) == 0 {
return
}
// Order rows
sort.Ints(p.rows)
// Create item
i := &Item{
EndAt: p.end.Sub(firstTime),
StartAt: p.start.Sub(firstTime),
}
// Loop through rows
for _, idxRow := range p.rows {
parseTeletextRow(i, d, nil, p.data[uint8(idxRow)])
}
// Append item
s.Items = append(s.Items, i)
}
type decoder interface {
decode(i byte) []byte
}
type styler interface {
hasBeenSet() bool
hasChanged(s *StyleAttributes) bool
parseSpacingAttribute(i byte)
propagateStyleAttributes(s *StyleAttributes)
update(sa *StyleAttributes)
}
func parseTeletextRow(i *Item, d decoder, fs func() styler, row []byte) {
// Loop through columns
var l = Line{}
var li = LineItem{InlineStyle: &StyleAttributes{}}
var started bool
var s styler
for _, v := range row {
// Create specific styler
if fs != nil {
s = fs()
}
// Get spacing attributes
var color *Color
var doubleHeight, doubleSize, doubleWidth *bool
switch v {
case 0x0:
color = ColorBlack
case 0x1:
color = ColorRed
case 0x2:
color = ColorGreen
case 0x3:
color = ColorYellow
case 0x4:
color = ColorBlue
case 0x5:
color = ColorMagenta
case 0x6:
color = ColorCyan
case 0x7:
color = ColorWhite
case 0xa:
started = false
case 0xb:
started = true
case 0xc:
doubleHeight = astiptr.Bool(false)
doubleSize = astiptr.Bool(false)
doubleWidth = astiptr.Bool(false)
case 0xd:
doubleHeight = astiptr.Bool(true)
case 0xe:
doubleWidth = astiptr.Bool(true)
case 0xf:
doubleSize = astiptr.Bool(true)
default:
if s != nil {
s.parseSpacingAttribute(v)
}
}
// Style has been set
if color != nil || doubleHeight != nil || doubleSize != nil || doubleWidth != nil || (s != nil && s.hasBeenSet()) {
// Style has changed
if color != li.InlineStyle.TeletextColor || doubleHeight != li.InlineStyle.TeletextDoubleHeight ||
doubleSize != li.InlineStyle.TeletextDoubleSize || doubleWidth != li.InlineStyle.TeletextDoubleWidth ||
(s != nil && s.hasChanged(li.InlineStyle)) {
// Line has started
if started {
// Append line item
appendTeletextLineItem(&l, li, s)
// Create new line item
sa := &StyleAttributes{}
*sa = *li.InlineStyle
li = LineItem{InlineStyle: sa}
}
// Update style attributes
if color != nil && color != li.InlineStyle.TeletextColor {
li.InlineStyle.TeletextColor = color
}
if doubleHeight != nil && doubleHeight != li.InlineStyle.TeletextDoubleHeight {
li.InlineStyle.TeletextDoubleHeight = doubleHeight
}
if doubleSize != nil && doubleSize != li.InlineStyle.TeletextDoubleSize {
li.InlineStyle.TeletextDoubleSize = doubleSize
}
if doubleWidth != nil && doubleWidth != li.InlineStyle.TeletextDoubleWidth {
li.InlineStyle.TeletextDoubleWidth = doubleWidth
}
if s != nil {
s.update(li.InlineStyle)
}
}
} else if started {
// Append text
li.Text += string(d.decode(v))
}
}
// Append line item
appendTeletextLineItem(&l, li, s)
// Append line
if len(l.Items) > 0 {
i.Lines = append(i.Lines, l)
}
}
func appendTeletextLineItem(l *Line, li LineItem, s styler) {
// There's some text
if len(strings.TrimSpace(li.Text)) > 0 {
// Make sure inline style exists
if li.InlineStyle == nil {
li.InlineStyle = &StyleAttributes{}
}
// Get number of spaces before
li.InlineStyle.TeletextSpacesBefore = astiptr.Int(0)
for _, c := range li.Text {
if c == ' ' {
*li.InlineStyle.TeletextSpacesBefore++
} else {
break
}
}
// Get number of spaces after
li.InlineStyle.TeletextSpacesAfter = astiptr.Int(0)
for idx := len(li.Text) - 1; idx >= 0; idx-- {
if li.Text[idx] == ' ' {
*li.InlineStyle.TeletextSpacesAfter++
} else {
break
}
}
// Propagate style attributes
li.InlineStyle.propagateTeletextAttributes()
if s != nil {
s.propagateStyleAttributes(li.InlineStyle)
}
// Append line item
li.Text = strings.TrimSpace(li.Text)
l.Items = append(l.Items, li)
}
}