1134545d2d8429615552931ef50a15ba2994d03f8Insun Kang/*
2134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * Copyright 2018 The Android Open Source Project
3134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
4134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * Licensed under the Apache License, Version 2.0 (the "License");
5134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * you may not use this file except in compliance with the License.
6134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * You may obtain a copy of the License at
7134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
8134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *      http://www.apache.org/licenses/LICENSE-2.0
9134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
10134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * Unless required by applicable law or agreed to in writing, software
11134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * distributed under the License is distributed on an "AS IS" BASIS,
12134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * See the License for the specific language governing permissions and
14134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * limitations under the License.
15134545d2d8429615552931ef50a15ba2994d03f8Insun Kang */
16134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
17134545d2d8429615552931ef50a15ba2994d03f8Insun Kangpackage androidx.media.subtitle;
18134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
19134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.text.Spannable;
20134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.text.SpannableStringBuilder;
21134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.text.TextPaint;
22134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.text.style.CharacterStyle;
23134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.text.style.StyleSpan;
24134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.text.style.UnderlineSpan;
25134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.text.style.UpdateAppearance;
26134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.util.Log;
27134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport android.view.accessibility.CaptioningManager.CaptionStyle;
28134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
29134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport java.util.ArrayList;
30134545d2d8429615552931ef50a15ba2994d03f8Insun Kangimport java.util.Arrays;
31134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
32134545d2d8429615552931ef50a15ba2994d03f8Insun Kang/**
33134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * CCParser processes CEA-608 closed caption data.
34134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
35134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * It calls back into OnDisplayChangedListener upon
36134545d2d8429615552931ef50a15ba2994d03f8Insun Kang * display change with styled text for rendering.
37134545d2d8429615552931ef50a15ba2994d03f8Insun Kang *
38134545d2d8429615552931ef50a15ba2994d03f8Insun Kang */
39134545d2d8429615552931ef50a15ba2994d03f8Insun Kangclass Cea608CCParser {
40134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public static final int MAX_ROWS = 15;
41134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public static final int MAX_COLS = 32;
42134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
43134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final String TAG = "Cea608CCParser";
44134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
45134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
46134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int INVALID = -1;
47134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
48134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    // EIA-CEA-608: Table 70 - Control Codes
49134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int RCL = 0x20;
50134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int BS  = 0x21;
51134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int AOF = 0x22;
52134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int AON = 0x23;
53134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int DER = 0x24;
54134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int RU2 = 0x25;
55134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int RU3 = 0x26;
56134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int RU4 = 0x27;
57134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int FON = 0x28;
58134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int RDC = 0x29;
59134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int TR  = 0x2a;
60134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int RTD = 0x2b;
61134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int EDM = 0x2c;
62134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int CR  = 0x2d;
63134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int ENM = 0x2e;
64134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int EOC = 0x2f;
65134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
66134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    // Transparent Space
67134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final char TS = '\u00A0';
68134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
69134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    // Captioning Modes
70134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int MODE_UNKNOWN = 0;
71134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int MODE_PAINT_ON = 1;
72134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int MODE_ROLL_UP = 2;
73134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int MODE_POP_ON = 3;
74134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static final int MODE_TEXT = 4;
75134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
76134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private final DisplayListener mListener;
77134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
78134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private int mMode = MODE_PAINT_ON;
79134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private int mRollUpSize = 4;
80134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private int mPrevCtrlCode = INVALID;
81134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
82134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private CCMemory mDisplay = new CCMemory();
83134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private CCMemory mNonDisplay = new CCMemory();
84134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private CCMemory mTextMem = new CCMemory();
85134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
86134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    Cea608CCParser(DisplayListener listener) {
87134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mListener = listener;
88134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
89134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
90134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public void parse(byte[] data) {
91134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        CCData[] ccData = CCData.fromByteArray(data);
92134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
93134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        for (int i = 0; i < ccData.length; i++) {
94134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (DEBUG) {
95134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                Log.d(TAG, ccData[i].toString());
96134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
97134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
98134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (handleCtrlCode(ccData[i])
99134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    || handleTabOffsets(ccData[i])
100134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    || handlePACCode(ccData[i])
101134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    || handleMidRowCode(ccData[i])) {
102134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                continue;
103134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
104134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
105134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            handleDisplayableChars(ccData[i]);
106134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
107134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
108134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
109134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    interface DisplayListener {
110134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void onDisplayChanged(SpannableStringBuilder[] styledTexts);
111134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        CaptionStyle getCaptionStyle();
112134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
113134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
114134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private CCMemory getMemory() {
115134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        // get the CC memory to operate on for current mode
116134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        switch (mMode) {
117134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case MODE_POP_ON:
118134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return mNonDisplay;
119134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case MODE_TEXT:
120134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // TODO(chz): support only caption mode for now,
121134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // in text mode, dump everything to text mem.
122134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return mTextMem;
123134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case MODE_PAINT_ON:
124134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case MODE_ROLL_UP:
125134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return mDisplay;
126134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            default:
127134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                Log.w(TAG, "unrecoginized mode: " + mMode);
128134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
129134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return mDisplay;
130134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
131134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
132134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private boolean handleDisplayableChars(CCData ccData) {
133134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (!ccData.isDisplayableChar()) {
134134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return false;
135134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
136134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
137134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        // Extended char includes 1 automatic backspace
138134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (ccData.isExtendedChar()) {
139134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            getMemory().bs();
140134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
141134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
142134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        getMemory().writeText(ccData.getDisplayText());
143134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
144134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mMode == MODE_PAINT_ON || mMode == MODE_ROLL_UP) {
145134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            updateDisplay();
146134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
147134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
148134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return true;
149134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
150134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
151134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private boolean handleMidRowCode(CCData ccData) {
152134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        StyleCode m = ccData.getMidRow();
153134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (m != null) {
154134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            getMemory().writeMidRowCode(m);
155134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return true;
156134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
157134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return false;
158134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
159134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
160134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private boolean handlePACCode(CCData ccData) {
161134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        PAC pac = ccData.getPAC();
162134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
163134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (pac != null) {
164134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mMode == MODE_ROLL_UP) {
165134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                getMemory().moveBaselineTo(pac.getRow(), mRollUpSize);
166134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
167134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            getMemory().writePAC(pac);
168134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return true;
169134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
170134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
171134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return false;
172134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
173134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
174134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private boolean handleTabOffsets(CCData ccData) {
175134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int tabs = ccData.getTabOffset();
176134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
177134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (tabs > 0) {
178134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            getMemory().tab(tabs);
179134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return true;
180134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
181134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
182134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return false;
183134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
184134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
185134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private boolean handleCtrlCode(CCData ccData) {
186134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int ctrlCode = ccData.getCtrlCode();
187134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
188134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mPrevCtrlCode != INVALID && mPrevCtrlCode == ctrlCode) {
189134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // discard double ctrl codes (but if there's a 3rd one, we still take that)
190134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mPrevCtrlCode = INVALID;
191134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return true;
192134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
193134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
194134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        switch(ctrlCode) {
195134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case RCL:
196134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // select pop-on style
197134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mMode = MODE_POP_ON;
198134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
199134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case BS:
200134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                getMemory().bs();
201134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
202134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case DER:
203134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                getMemory().der();
204134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
205134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case RU2:
206134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case RU3:
207134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case RU4:
208134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mRollUpSize = (ctrlCode - 0x23);
209134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // erase memory if currently in other style
210134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mMode != MODE_ROLL_UP) {
211134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mDisplay.erase();
212134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mNonDisplay.erase();
213134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
214134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // select roll-up style
215134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mMode = MODE_ROLL_UP;
216134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
217134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case FON:
218134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                Log.i(TAG, "Flash On");
219134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
220134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case RDC:
221134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // select paint-on style
222134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mMode = MODE_PAINT_ON;
223134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
224134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case TR:
225134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mMode = MODE_TEXT;
226134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mTextMem.erase();
227134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
228134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case RTD:
229134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mMode = MODE_TEXT;
230134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
231134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case EDM:
232134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // erase display memory
233134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mDisplay.erase();
234134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                updateDisplay();
235134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
236134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case CR:
237134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mMode == MODE_ROLL_UP) {
238134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    getMemory().rollUp(mRollUpSize);
239134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                } else {
240134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    getMemory().cr();
241134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
242134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mMode == MODE_ROLL_UP) {
243134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    updateDisplay();
244134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
245134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
246134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case ENM:
247134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // erase non-display memory
248134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mNonDisplay.erase();
249134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
250134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case EOC:
251134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // swap display/non-display memory
252134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                swapMemory();
253134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // switch to pop-on style
254134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mMode = MODE_POP_ON;
255134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                updateDisplay();
256134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                break;
257134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            case INVALID:
258134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            default:
259134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mPrevCtrlCode = INVALID;
260134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return false;
261134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
262134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
263134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mPrevCtrlCode = ctrlCode;
264134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
265134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        // handled
266134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        return true;
267134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
268134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
269134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private void updateDisplay() {
270134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        if (mListener != null) {
271134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            CaptionStyle captionStyle = mListener.getCaptionStyle();
272134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mListener.onDisplayChanged(mDisplay.getStyledText(captionStyle));
273134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
274134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
275134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
276134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private void swapMemory() {
277134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        CCMemory temp = mDisplay;
278134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mDisplay = mNonDisplay;
279134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        mNonDisplay = temp;
280134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
281134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
282134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static class StyleCode {
283134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_WHITE = 0;
284134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_GREEN = 1;
285134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_BLUE = 2;
286134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_CYAN = 3;
287134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_RED = 4;
288134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_YELLOW = 5;
289134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_MAGENTA = 6;
290134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int COLOR_INVALID = 7;
291134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
292134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int STYLE_ITALICS   = 0x00000001;
293134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final int STYLE_UNDERLINE = 0x00000002;
294134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
295134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static final String[] sColorMap = {
296134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "WHITE", "GREEN", "BLUE", "CYAN", "RED", "YELLOW", "MAGENTA", "INVALID"
297134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        };
298134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
299134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        final int mStyle;
300134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        final int mColor;
301134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
302134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static StyleCode fromByte(byte data2) {
303134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int style = 0;
304134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int color = (data2 >> 1) & 0x7;
305134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
306134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((data2 & 0x1) != 0) {
307134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                style |= STYLE_UNDERLINE;
308134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
309134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
310134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (color == COLOR_INVALID) {
311134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // WHITE ITALICS
312134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                color = COLOR_WHITE;
313134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                style |= STYLE_ITALICS;
314134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
315134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
316134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return new StyleCode(style, color);
317134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
318134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
319134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        StyleCode(int style, int color) {
320134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mStyle = style;
321134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mColor = color;
322134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
323134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
324134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        boolean isItalics() {
325134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return (mStyle & STYLE_ITALICS) != 0;
326134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
327134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
328134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        boolean isUnderline() {
329134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return (mStyle & STYLE_UNDERLINE) != 0;
330134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
331134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
332134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int getColor() {
333134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mColor;
334134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
335134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
336134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        @Override
337134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public String toString() {
338134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            StringBuilder str = new StringBuilder();
339134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            str.append("{");
340134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            str.append(sColorMap[mColor]);
341134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mStyle & STYLE_ITALICS) != 0) {
342134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                str.append(", ITALICS");
343134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
344134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mStyle & STYLE_UNDERLINE) != 0) {
345134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                str.append(", UNDERLINE");
346134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
347134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            str.append("}");
348134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
349134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return str.toString();
350134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
351134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
352134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
353134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static class PAC extends StyleCode {
354134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        final int mRow;
355134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        final int mCol;
356134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
357134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static PAC fromBytes(byte data1, byte data2) {
358134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int[] rowTable = {11, 1, 3, 12, 14, 5, 7, 9};
359134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int row = rowTable[data1 & 0x07] + ((data2 & 0x20) >> 5);
360134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int style = 0;
361134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((data2 & 1) != 0) {
362134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                style |= STYLE_UNDERLINE;
363134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
364134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((data2 & 0x10) != 0) {
365134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // indent code
366134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                int indent = (data2 >> 1) & 0x7;
367134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return new PAC(row, indent * 4, style, COLOR_WHITE);
368134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else {
369134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // style code
370134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                int color = (data2 >> 1) & 0x7;
371134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
372134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (color == COLOR_INVALID) {
373134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // WHITE ITALICS
374134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    color = COLOR_WHITE;
375134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    style |= STYLE_ITALICS;
376134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
377134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return new PAC(row, -1, style, color);
378134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
379134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
380134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
381134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        PAC(int row, int col, int style, int color) {
382134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            super(style, color);
383134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mRow = row;
384134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mCol = col;
385134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
386134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
387134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        boolean isIndentPAC() {
388134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return (mCol >= 0);
389134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
390134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
391134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int getRow() {
392134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mRow;
393134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
394134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
395134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int getCol() {
396134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mCol;
397134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
398134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
399134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        @Override
400134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public String toString() {
401134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return String.format("{%d, %d}, %s",
402134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mRow, mCol, super.toString());
403134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
404134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
405134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
406134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /**
407134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * Mutable version of BackgroundSpan to facilitate text rendering with edge styles.
408134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
409134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    public static class MutableBackgroundColorSpan extends CharacterStyle
410134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            implements UpdateAppearance {
411134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private int mColor;
412134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
413134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        MutableBackgroundColorSpan(int color) {
414134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mColor = color;
415134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
416134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
417134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public void setBackgroundColor(int color) {
418134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mColor = color;
419134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
420134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
421134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public int getBackgroundColor() {
422134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mColor;
423134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
424134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
425134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        @Override
426134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public void updateDrawState(TextPaint ds) {
427134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            ds.bgColor = mColor;
428134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
429134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
430134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
431134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /* CCLineBuilder keeps track of displayable chars, as well as
432134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * MidRow styles and PACs, for a single line of CC memory.
433134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     *
434134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * It generates styled text via getStyledText() method.
435134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
436134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static class CCLineBuilder {
437134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final StringBuilder mDisplayChars;
438134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final StyleCode[] mMidRowStyles;
439134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final StyleCode[] mPACStyles;
440134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
441134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        CCLineBuilder(String str) {
442134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mDisplayChars = new StringBuilder(str);
443134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mMidRowStyles = new StyleCode[mDisplayChars.length()];
444134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mPACStyles = new StyleCode[mDisplayChars.length()];
445134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
446134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
447134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void setCharAt(int index, char ch) {
448134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mDisplayChars.setCharAt(index, ch);
449134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mMidRowStyles[index] = null;
450134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
451134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
452134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void setMidRowAt(int index, StyleCode m) {
453134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mDisplayChars.setCharAt(index, ' ');
454134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mMidRowStyles[index] = m;
455134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
456134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
457134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void setPACAt(int index, PAC pac) {
458134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mPACStyles[index] = pac;
459134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
460134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
461134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        char charAt(int index) {
462134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mDisplayChars.charAt(index);
463134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
464134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
465134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int length() {
466134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mDisplayChars.length();
467134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
468134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
469134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void applyStyleSpan(
470134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                SpannableStringBuilder styledText,
471134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                StyleCode s, int start, int end) {
472134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (s.isItalics()) {
473134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                styledText.setSpan(
474134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        new StyleSpan(android.graphics.Typeface.ITALIC),
475134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
476134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
477134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (s.isUnderline()) {
478134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                styledText.setSpan(
479134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        new UnderlineSpan(),
480134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
481134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
482134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
483134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
484134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        SpannableStringBuilder getStyledText(CaptionStyle captionStyle) {
485134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            SpannableStringBuilder styledText = new SpannableStringBuilder(mDisplayChars);
486134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int start = -1, next = 0;
487134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int styleStart = -1;
488134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            StyleCode curStyle = null;
489134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            while (next < mDisplayChars.length()) {
490134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                StyleCode newStyle = null;
491134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mMidRowStyles[next] != null) {
492134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // apply mid-row style change
493134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    newStyle = mMidRowStyles[next];
494134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                } else if (mPACStyles[next] != null && (styleStart < 0 || start < 0)) {
495134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // apply PAC style change, only if:
496134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // 1. no style set, or
497134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // 2. style set, but prev char is none-displayable
498134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    newStyle = mPACStyles[next];
499134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
500134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (newStyle != null) {
501134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    curStyle = newStyle;
502134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    if (styleStart >= 0 && start >= 0) {
503134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        applyStyleSpan(styledText, newStyle, styleStart, next);
504134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
505134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    styleStart = next;
506134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
507134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
508134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mDisplayChars.charAt(next) != TS) {
509134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    if (start < 0) {
510134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        start = next;
511134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
512134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                } else if (start >= 0) {
513134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    int expandedStart = mDisplayChars.charAt(start) == ' ' ? start : start - 1;
514134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    int expandedEnd = mDisplayChars.charAt(next - 1) == ' ' ? next : next + 1;
515134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    styledText.setSpan(
516134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            new MutableBackgroundColorSpan(captionStyle.backgroundColor),
517134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            expandedStart, expandedEnd,
518134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
519134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    if (styleStart >= 0) {
520134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        applyStyleSpan(styledText, curStyle, styleStart, expandedEnd);
521134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
522134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    start = -1;
523134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
524134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                next++;
525134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
526134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
527134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return styledText;
528134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
529134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
530134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
531134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /*
532134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * CCMemory models a console-style display.
533134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
534134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static class CCMemory {
535134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final String mBlankLine;
536134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final CCLineBuilder[] mLines = new CCLineBuilder[MAX_ROWS + 2];
537134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private int mRow;
538134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private int mCol;
539134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
540134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        CCMemory() {
541134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            char[] blank = new char[MAX_COLS + 2];
542134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            Arrays.fill(blank, TS);
543134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mBlankLine = new String(blank);
544134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
545134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
546134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void erase() {
547134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // erase all lines
548134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (int i = 0; i < mLines.length; i++) {
549134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[i] = null;
550134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
551134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mRow = MAX_ROWS;
552134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mCol = 1;
553134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
554134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
555134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void der() {
556134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mLines[mRow] != null) {
557134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                for (int i = 0; i < mCol; i++) {
558134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    if (mLines[mRow].charAt(i) != TS) {
559134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        for (int j = mCol; j < mLines[mRow].length(); j++) {
560134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                            mLines[j].setCharAt(j, TS);
561134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        }
562134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        return;
563134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    }
564134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
565134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[mRow] = null;
566134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
567134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
568134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
569134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void tab(int tabs) {
570134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            moveCursorByCol(tabs);
571134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
572134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
573134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void bs() {
574134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            moveCursorByCol(-1);
575134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mLines[mRow] != null) {
576134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[mRow].setCharAt(mCol, TS);
577134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mCol == MAX_COLS - 1) {
578134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // Spec recommendation:
579134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // if cursor was at col 32, move cursor
580134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    // back to col 31 and erase both col 31&32
581134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mLines[mRow].setCharAt(MAX_COLS, TS);
582134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
583134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
584134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
585134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
586134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void cr() {
587134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            moveCursorTo(mRow + 1, 1);
588134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
589134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
590134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void rollUp(int windowSize) {
591134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int i;
592134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (i = 0; i <= mRow - windowSize; i++) {
593134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[i] = null;
594134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
595134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int startRow = mRow - windowSize + 1;
596134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (startRow < 1) {
597134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                startRow = 1;
598134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
599134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (i = startRow; i < mRow; i++) {
600134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[i] = mLines[i + 1];
601134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
602134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (i = mRow; i < mLines.length; i++) {
603134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // clear base row
604134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[i] = null;
605134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
606134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // default to col 1, in case PAC is not sent
607134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mCol = 1;
608134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
609134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
610134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void writeText(String text) {
611134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (int i = 0; i < text.length(); i++) {
612134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                getLineBuffer(mRow).setCharAt(mCol, text.charAt(i));
613134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                moveCursorByCol(1);
614134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
615134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
616134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
617134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void writeMidRowCode(StyleCode m) {
618134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            getLineBuffer(mRow).setMidRowAt(mCol, m);
619134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            moveCursorByCol(1);
620134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
621134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
622134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        void writePAC(PAC pac) {
623134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (pac.isIndentPAC()) {
624134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                moveCursorTo(pac.getRow(), pac.getCol());
625134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else {
626134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                moveCursorTo(pac.getRow(), 1);
627134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
628134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            getLineBuffer(mRow).setPACAt(mCol, pac);
629134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
630134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
631134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        SpannableStringBuilder[] getStyledText(CaptionStyle captionStyle) {
632134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            ArrayList<SpannableStringBuilder> rows = new ArrayList<>(MAX_ROWS);
633134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (int i = 1; i <= MAX_ROWS; i++) {
634134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                rows.add(mLines[i] != null ? mLines[i].getStyledText(captionStyle) : null);
635134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
636134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return rows.toArray(new SpannableStringBuilder[MAX_ROWS]);
637134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
638134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
639134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private static int clamp(int x, int min, int max) {
640134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return x < min ? min : (x > max ? max : x);
641134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
642134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
643134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private void moveCursorTo(int row, int col) {
644134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mRow = clamp(row, 1, MAX_ROWS);
645134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mCol = clamp(col, 1, MAX_COLS);
646134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
647134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
648134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private void moveCursorToRow(int row) {
649134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mRow = clamp(row, 1, MAX_ROWS);
650134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
651134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
652134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private void moveCursorByCol(int col) {
653134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mCol = clamp(mCol + col, 1, MAX_COLS);
654134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
655134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
656134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private void moveBaselineTo(int baseRow, int windowSize) {
657134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mRow == baseRow) {
658134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return;
659134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
660134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int actualWindowSize = windowSize;
661134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (baseRow < actualWindowSize) {
662134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                actualWindowSize = baseRow;
663134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
664134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mRow < actualWindowSize) {
665134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                actualWindowSize = mRow;
666134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
667134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
668134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int i;
669134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (baseRow < mRow) {
670134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // copy from bottom to top row
671134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                for (i = actualWindowSize - 1; i >= 0; i--) {
672134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mLines[baseRow - i] = mLines[mRow - i];
673134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
674134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else {
675134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // copy from top to bottom row
676134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                for (i = 0; i < actualWindowSize; i++) {
677134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    mLines[baseRow - i] = mLines[mRow - i];
678134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
679134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
680134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // clear rest of the rows
681134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (i = 0; i <= baseRow - windowSize; i++) {
682134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[i] = null;
683134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
684134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (i = baseRow + 1; i < mLines.length; i++) {
685134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[i] = null;
686134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
687134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
688134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
689134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private CCLineBuilder getLineBuffer(int row) {
690134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mLines[row] == null) {
691134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                mLines[row] = new CCLineBuilder(mBlankLine);
692134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
693134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mLines[row];
694134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
695134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
696134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
697134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    /*
698134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * CCData parses the raw CC byte pair into displayable chars,
699134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     * misc control codes, Mid-Row or Preamble Address Codes.
700134545d2d8429615552931ef50a15ba2994d03f8Insun Kang     */
701134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    private static class CCData {
702134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final byte mType;
703134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final byte mData1;
704134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private final byte mData2;
705134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
706134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private static final String[] sCtrlCodeMap = {
707134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "RCL", "BS" , "AOF", "AON",
708134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "DER", "RU2", "RU3", "RU4",
709134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "FON", "RDC", "TR" , "RTD",
710134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "EDM", "CR" , "ENM", "EOC",
711134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        };
712134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
713134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private static final String[] sSpecialCharMap = {
714134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00AE",
715134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00B0",
716134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00BD",
717134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00BF",
718134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2122",
719134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00A2",
720134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00A3",
721134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u266A", // Eighth note
722134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00E0",
723134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00A0", // Transparent space
724134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00E8",
725134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00E2",
726134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00EA",
727134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00EE",
728134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00F4",
729134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00FB",
730134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        };
731134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
732134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private static final String[] sSpanishCharMap = {
733134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // Spanish and misc chars
734134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C1", // A
735134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C9", // E
736134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00D3", // I
737134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00DA", // O
738134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00DC", // U
739134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00FC", // u
740134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2018", // opening single quote
741134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00A1", // inverted exclamation mark
742134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "*",
743134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "'",
744134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2014", // em dash
745134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00A9", // Copyright
746134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2120", // Servicemark
747134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2022", // round bullet
748134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u201C", // opening double quote
749134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u201D", // closing double quote
750134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // French
751134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C0",
752134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C2",
753134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C7",
754134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C8",
755134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00CA",
756134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00CB",
757134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00EB",
758134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00CE",
759134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00CF",
760134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00EF",
761134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00D4",
762134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00D9",
763134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00F9",
764134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00DB",
765134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00AB",
766134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00BB"
767134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        };
768134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
769134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private static final String[] sProtugueseCharMap = {
770134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // Portuguese
771134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C3",
772134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00E3",
773134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00CD",
774134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00CC",
775134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00EC",
776134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00D2",
777134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00F2",
778134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00D5",
779134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00F5",
780134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "{",
781134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "}",
782134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\\",
783134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "^",
784134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "_",
785134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "|",
786134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "~",
787134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // German and misc chars
788134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C4",
789134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00E4",
790134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00D6",
791134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00F6",
792134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00DF",
793134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00A5",
794134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00A4",
795134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2502", // vertical bar
796134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00C5",
797134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00E5",
798134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00D8",
799134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u00F8",
800134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u250C", // top-left corner
801134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2510", // top-right corner
802134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2514", // lower-left corner
803134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            "\u2518", // lower-right corner
804134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        };
805134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
806134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        static CCData[] fromByteArray(byte[] data) {
807134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            CCData[] ccData = new CCData[data.length / 3];
808134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
809134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            for (int i = 0; i < ccData.length; i++) {
810134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                ccData[i] = new CCData(
811134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        data[i * 3],
812134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        data[i * 3 + 1],
813134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        data[i * 3 + 2]);
814134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
815134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
816134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return ccData;
817134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
818134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
819134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        CCData(byte type, byte data1, byte data2) {
820134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mType = type;
821134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mData1 = data1;
822134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            mData2 = data2;
823134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
824134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
825134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int getCtrlCode() {
826134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mData1 == 0x14 || mData1 == 0x1c)
827134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && mData2 >= 0x20 && mData2 <= 0x2f) {
828134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return mData2;
829134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
830134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return INVALID;
831134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
832134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
833134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        StyleCode getMidRow() {
834134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // only support standard Mid-row codes, ignore
835134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // optional background/foreground mid-row codes
836134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mData1 == 0x11 || mData1 == 0x19)
837134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && mData2 >= 0x20 && mData2 <= 0x2f) {
838134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return StyleCode.fromByte(mData2);
839134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
840134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return null;
841134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
842134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
843134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        PAC getPAC() {
844134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mData1 & 0x70) == 0x10
845134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && (mData2 & 0x40) == 0x40
846134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && ((mData1 & 0x07) != 0 || (mData2 & 0x20) == 0)) {
847134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return PAC.fromBytes(mData1, mData2);
848134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
849134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return null;
850134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
851134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
852134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        int getTabOffset() {
853134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mData1 == 0x17 || mData1 == 0x1f)
854134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && mData2 >= 0x21 && mData2 <= 0x23) {
855134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return mData2 & 0x3;
856134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
857134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return 0;
858134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
859134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
860134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        boolean isDisplayableChar() {
861134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return isBasicChar() || isSpecialChar() || isExtendedChar();
862134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
863134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
864134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        String getDisplayText() {
865134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            String str = getBasicChars();
866134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
867134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (str == null) {
868134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                str =  getSpecialChar();
869134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
870134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (str == null) {
871134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    str = getExtendedChar();
872134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
873134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
874134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
875134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return str;
876134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
877134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
878134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private String ctrlCodeToString(int ctrlCode) {
879134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return sCtrlCodeMap[ctrlCode - 0x20];
880134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
881134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
882134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private boolean isBasicChar() {
883134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return mData1 >= 0x20 && mData1 <= 0x7f;
884134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
885134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
886134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private boolean isSpecialChar() {
887134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return ((mData1 == 0x11 || mData1 == 0x19)
888134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && mData2 >= 0x30 && mData2 <= 0x3f);
889134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
890134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
891134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private boolean isExtendedChar() {
892134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return ((mData1 == 0x12 || mData1 == 0x1A
893134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    || mData1 == 0x13 || mData1 == 0x1B)
894134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && mData2 >= 0x20 && mData2 <= 0x3f);
895134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
896134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
897134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private char getBasicChar(byte data) {
898134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            char c;
899134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            // replace the non-ASCII ones
900134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            switch (data) {
901134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x2A: c = '\u00E1'; break;
902134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x5C: c = '\u00E9'; break;
903134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x5E: c = '\u00ED'; break;
904134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x5F: c = '\u00F3'; break;
905134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x60: c = '\u00FA'; break;
906134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x7B: c = '\u00E7'; break;
907134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x7C: c = '\u00F7'; break;
908134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x7D: c = '\u00D1'; break;
909134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x7E: c = '\u00F1'; break;
910134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                case 0x7F: c = '\u2588'; break; // Full block
911134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                default: c = (char) data; break;
912134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
913134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return c;
914134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
915134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
916134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private String getBasicChars() {
917134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mData1 >= 0x20 && mData1 <= 0x7f) {
918134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                StringBuilder builder = new StringBuilder(2);
919134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                builder.append(getBasicChar(mData1));
920134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                if (mData2 >= 0x20 && mData2 <= 0x7f) {
921134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    builder.append(getBasicChar(mData2));
922134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                }
923134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return builder.toString();
924134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
925134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
926134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return null;
927134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
928134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
929134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private String getSpecialChar() {
930134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mData1 == 0x11 || mData1 == 0x19)
931134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                    && mData2 >= 0x30 && mData2 <= 0x3f) {
932134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return sSpecialCharMap[mData2 - 0x30];
933134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
934134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
935134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return null;
936134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
937134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
938134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        private String getExtendedChar() {
939134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if ((mData1 == 0x12 || mData1 == 0x1A) && mData2 >= 0x20 && mData2 <= 0x3f) {
940134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // 1 Spanish/French char
941134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return sSpanishCharMap[mData2 - 0x20];
942134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            } else if ((mData1 == 0x13 || mData1 == 0x1B) && mData2 >= 0x20 && mData2 <= 0x3f) {
943134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // 1 Portuguese/German/Danish char
944134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return sProtugueseCharMap[mData2 - 0x20];
945134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
946134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
947134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return null;
948134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
949134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
950134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        @Override
951134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        public String toString() {
952134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            String str;
953134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
954134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (mData1 < 0x10 && mData2 < 0x10) {
955134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                // Null Pad, ignore
956134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return String.format("[%d]Null: %02x %02x", mType, mData1, mData2);
957134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
958134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
959134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int ctrlCode = getCtrlCode();
960134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (ctrlCode != INVALID) {
961134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return String.format("[%d]%s", mType, ctrlCodeToString(ctrlCode));
962134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
963134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
964134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            int tabOffset = getTabOffset();
965134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (tabOffset > 0) {
966134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return String.format("[%d]Tab%d", mType, tabOffset);
967134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
968134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
969134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            PAC pac = getPAC();
970134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (pac != null) {
971134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return String.format("[%d]PAC: %s", mType, pac.toString());
972134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
973134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
974134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            StyleCode m = getMidRow();
975134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (m != null) {
976134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return String.format("[%d]Mid-row: %s", mType, m.toString());
977134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
978134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
979134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            if (isDisplayableChar()) {
980134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                return String.format("[%d]Displayable: %s (%02x %02x)",
981134545d2d8429615552931ef50a15ba2994d03f8Insun Kang                        mType, getDisplayText(), mData1, mData2);
982134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            }
983134545d2d8429615552931ef50a15ba2994d03f8Insun Kang
984134545d2d8429615552931ef50a15ba2994d03f8Insun Kang            return String.format("[%d]Invalid: %02x %02x", mType, mData1, mData2);
985134545d2d8429615552931ef50a15ba2994d03f8Insun Kang        }
986134545d2d8429615552931ef50a15ba2994d03f8Insun Kang    }
987134545d2d8429615552931ef50a15ba2994d03f8Insun Kang}
988