private static void SetStyle(Attr attr, SmiStatus status) { attr.b = status.b > 0; attr.i = status.i > 0; attr.u = status.u > 0; attr.fs = (status.fs.Count > 0) ? status.fs[status.fs.Count - 1] : 0; attr.fn = (status.fn.Count > 0) ? status.fn[status.fn.Count - 1] : ""; attr.fc = (status.fc.Count > 0) ? status.fc[status.fc.Count - 1] : ""; attr.fade = (status.fade.Count > 0) ? status.fade[status.fade.Count - 1] : 0; attr.typing = (status.typing.Count > 0) ? status.typing[status.typing.Count - 1] : null; }
public Ass FromAttr(List <Attr> attrs) { string text = ""; List <string[]> lastAttrs = new List <string[]>(); Attr last = new Attr(); foreach (Attr attr in attrs) { if (!last.b && attr.b) { text += "{\\b1}"; } else if (last.b && !attr.b) { text += "{\\b}"; } if (!last.i && attr.i) { text += "{\\i1}"; } else if (last.i && !attr.i) { text += "{\\i}"; } if (!last.u && attr.u) { text += "{\\u1}"; } else if (last.u && !attr.u) { text += "{\\u}"; } if (!last.fn.Equals(attr.fn)) { text += "{\\fn" + attr.fn + "}"; } if (!last.fc.Equals(attr.fc)) { text += "{\\c" + ColorFromAttr(attr.fc) + "}"; } text += attr.text; last = attr; } this.text = text.Replace("}{", "").Replace("\n", "\\N"); return(this); }
public Attr(Attr old) { text = ""; b = old.b; i = old.i; u = old.u; fs = old.fs; fn = old.fn; fc = old.fc; fade = old.fade; typing = old.typing; }
public List <Attr> ToAttr() { List <Attr> result = new List <Attr>(); int index = 0; int pos = 0; Attr last = new Attr(); result.Add(last); while ((pos = text.IndexOf('{', index)) >= 0) { last.text += text.Substring(index, pos - index).Replace("\\N", "\n"); int endPos = text.IndexOf('}', pos); string attrString = text.Substring(pos + 1, endPos - pos - 1); int mode = -1; int tagStart = 0, tagEnd = 0; string tag = null; for (pos = 0; pos < attrString.Length; pos++) { switch (mode) { case -1: // 태그 시작 전 while (pos < attrString.Length && attrString[pos] != '\\') { pos++; } mode = 0; tagStart = pos + 1; pos--; break; case 0: // 태그 for (tagEnd = tagStart; tagEnd < attrString.Length; tagEnd++) { if (attrString[tagEnd] == '\\') { break; } else if (attrString[tagEnd] == '(') { mode++; pos = tagEnd + 1; break; } } if (mode > 0) { break; } if (tagStart == tagEnd) { break; } tag = attrString.Substring(tagStart, tagEnd - tagStart); if (tag.StartsWith("c") || tag.StartsWith("1c")) { if (last.text.Length > 0) { result.Add((last = new Attr(last))); } if (tag.StartsWith("c")) { tag = "1" + tag; } if (tag.Length >= 11 && tag[2] == '&' && tag[3] == 'H' && tag[10] == '&') { last.fc = ColorToAttr(tag.Substring(2, 9)); } else { last.fc = ""; } } else if (tag.StartsWith("fn")) { if (last.text.Length > 0) { result.Add((last = new Attr(last))); } last.fn = (tag.Length > 2) ? tag.Substring(2) : ""; } else if (tag.StartsWith("b")) { if (last.text.Length > 0) { result.Add((last = new Attr(last))); } last.b = (tag.Length >= 2 && tag[1] == '1'); } else if (tag.StartsWith("u")) { if (last.text.Length > 0) { result.Add((last = new Attr(last))); } last.u = (tag.Length >= 2 && tag[1] == '1'); } else if (tag.StartsWith("i")) { if (last.text.Length > 0) { result.Add((last = new Attr(last))); } last.i = (tag.Length >= 2 && tag[1] == '1'); } mode = 0; pos = tagEnd; tagStart = tagEnd + 1; break; default: // 괄호 for (; pos < attrString.Length; pos++) { if (attrString[pos] == ')') { mode--; break; } } break; } } index = endPos + 1; } last.text += text.Substring(index).Replace("\n", "\\n"); return(result); }
public static void Normalize(List <Smi> smis) { int startIndex = -1; for (int i = 1; i < smis.Count; i++) { if (smis[i].syncType == SyncType.inner) { if (startIndex < 0) { startIndex = i - 1; } } else { if (startIndex >= 0) { int endIndex = i; if (smis[startIndex].syncType == smis[endIndex].syncType) { int startSync = smis[startIndex].start; int endSync = smis[endIndex].start; int count = endIndex - startIndex; for (int j = 1; j < count; j++) { smis[startIndex + j].start = ((count - j) * startSync + j * endSync) / count; } } startIndex = -1; } } } for (int i = 0; i < smis.Count - 1; i++) { Smi smi = smis[i]; if (smi.syncType != smis[i + 1].syncType) { // 전후 싱크 타입이 맞을 때만 안전함 continue; } string lower = smi.text.ToLower(); if (lower.IndexOf(" fade=") > 0) { List <Color> fadeColors = new List <Color>(); List <Attr> attrs = smi.ToAttr(); for (int j = 0; j < attrs.Count; j++) { if (attrs[j].fade != 0) { string color = (attrs[j].fc.Length == 6) ? attrs[j].fc : "ffffff"; fadeColors.Add(new Color(j, attrs[j].fade > 0, color)); attrs[j].fade = 0; } } if (fadeColors.Count == 0) { continue; } int start = smi.start, end = smis[i + 1].start; int frames = (int)Math.Round((end - start) * 24 / 1001.0); foreach (Color color in fadeColors) { Attr attr = attrs[color.index]; if (color.isIn) { attr.fc = color.Get(1, 2 * frames); } else { attr.fc = color.Get(2 * frames - 1, 2 * frames); } } smi.FromAttr(attrs); for (int j = 1; j < frames; j++) { foreach (Color color in fadeColors) { Attr attr = attrs[color.index]; if (color.isIn) { attr.fc = color.Get(1 + 2 * j, 2 * frames); } else { attr.fc = color.Get(2 * frames - (1 + 2 * j), 2 * frames); } } smis.Insert(i + j, new Smi() { start = (start * (frames - j) + end * j) / frames, //syncType = 2, syncType = SyncType.inner }.FromAttr(attrs)); } i += frames - 1; } else if (lower.IndexOf(" typing=") > 0) { // 타이핑은 한 싱크에 하나만 가능 int attrIndex = -1; Attr attr = null; List <Attr> attrs = smi.ToAttr(); bool isLastAttr = false; for (int j = 0; j < attrs.Count; j++) { if (attrs[j].typing != null) { string color = (attrs[j].fc.Length == 6) ? attrs[j].fc : "ffffff"; attr = attrs[(attrIndex = j)]; string remains = ""; for (int k = j + 1; k < attrs.Count; k++) { remains += attrs[k].text; } isLastAttr = remains.Length == 0 || remains.StartsWith("\n"); if (!isLastAttr) { int length = 0; for (int k = j + 1; k < attrs.Count; k++) { length += attrs[k].text.Length; } isLastAttr = (length == 0); } break; } } if (attr == null) { continue; } List <string> types = Typing.ToType(attr.text, attr.typing.mode, attr.typing.cursor); float width = GetLineWidth(attr.text); int start = smi.start, end = smis[i + 1].start; int count = types.Count - attr.typing.end - attr.typing.start; if (count < 1) { continue; } int typingStart = attr.typing.start; attr.typing = null; smis.RemoveAt(i); for (int j = 0; j < count; j++) { string text = types[j + typingStart]; attr.text = Width.GetAppend(GetLineWidth(text), width) + (isLastAttr ? "" : ""); List <Attr> tAttrs = new List <Attr>(); tAttrs.AddRange(attrs.GetRange(0, attrIndex)); tAttrs.AddRange(new Smi() { text = text }.ToAttr()); tAttrs.Add(attr); if (!isLastAttr) { tAttrs.AddRange(attrs.GetRange(attrIndex + 1, attrs.Count - attrIndex - 1)); } smis.Insert(i + j, new Smi() { start = (start * (count - j) + end * (j)) / count, syncType = j == 0 ? smi.syncType : SyncType.inner }.FromAttr(tAttrs)); } i += count - 1; } } }
public Smi FromAttr(List <Attr> attrs) { string text = ""; List <string[]> lastAttrs = new List <string[]>(); Attr last = new Attr(); foreach (Attr attr in attrs) { if (!last.b && attr.b) { text += "<B>"; } else if (last.b && !attr.b) { text += "</B>"; } if (!last.i && attr.i) { text += "<I>"; } else if (last.i && !attr.i) { text += "</I>"; } if (!last.u && attr.u) { text += "<U>"; } else if (last.u && !attr.u) { text += "</U>"; } if (last.fs != attr.fs || !last.fn.Equals(attr.fn) || !last.fc.Equals(attr.fc) || !last.fade.Equals(attr.fade) || (last.typing == null && attr.typing != null) || (last.typing != null && attr.typing == null) ) { // 기존에 속성이 있었을 때만 닫는 태그 if (last.fs > 0 || !last.fn.Equals("") || !last.fc.Equals("") || last.fade > 0 || last.typing != null) { text += "</FONT>"; } // 신규 속성이 있을 때만 여는 태그 if (attr.fs > 0 || !attr.fn.Equals("") || !attr.fc.Equals("") || attr.fade > 0 || attr.typing != null) { text += "<FONT"; if (attr.fs > 0) { text += " size=\"" + attr.fs + "\""; } if (!attr.fn.Equals("")) { text += " face=\"" + attr.fn + "\""; } if (!attr.fc.Equals("")) { text += " color=\"" + ColorFromAttr(attr.fc) + "\""; } if (attr.fade != 0) { text += " fade=\"" + (attr.fade > 0 ? "in" : "out") + "\""; } if (attr.typing != null) { text += " typing=\"" + attr.typing.mode.ToString() + "(" + attr.typing.start + "," + attr.typing.end + ") " + attr.typing.cursor.ToString() + "\""; } text += ">"; } } text += WebUtility.HtmlEncode(attr.text); last = attr; } this.text = text.Replace("\n", "<br>"); return(this); }
public static List <Attr> ToAttr(string text) { List <Attr> result = new List <Attr>(); int index = 0; int pos = 0; SmiStatus status = new SmiStatus(); Attr last = new Attr(); result.Add(last); while ((pos = text.IndexOf('<', index)) >= 0) { #region 태그명 int tagNameLength = 0; while (pos + 1 + tagNameLength < text.Length && text[pos + 1 + tagNameLength] != ' ' && text[pos + 1 + tagNameLength] != '>' && text[pos + 1 + tagNameLength] != '\r' && text[pos + 1 + tagNameLength] != '\n' && text[pos + 1 + tagNameLength] != '\t' ) { tagNameLength++; } string tagName = text.Substring(pos + 1, tagNameLength).ToUpper(); int attrPos = pos + 1 + tagNameLength; #endregion #region 태그 속성 List <string[]> attrs = new List <string[]>(); if (text[attrPos] == '>') { attrPos++; } else { if (tagName[0].Equals('/')) // 종료 태그 { attrPos = text.IndexOf('>', attrPos) + 1; } else { int mode = 0; int attrNameStart = attrPos, attrValueStart = 0; string name = ""; while (mode >= 0) { switch (mode) { case 0: // 속성 이름 { for (; mode == 0 && attrPos < text.Length; attrPos++) { switch (text[attrPos]) { case '>': if (attrPos - attrNameStart > 0) { // 이름만 있는 속성 attrs.Add(new string[] { text.Substring(attrNameStart, attrPos - attrNameStart).ToLower(), null }); } mode = -1; break; case ' ': case '\t': case '\r': case '\n': if (attrPos - attrNameStart > 0) { // 이름만 있는 속성 attrs.Add(new string[] { text.Substring(attrNameStart, attrPos - attrNameStart).ToLower(), null }); } attrNameStart = attrPos + 1; break; case '=': // 이름 끝, 값 시작 name = text.Substring(attrNameStart, attrPos - attrNameStart).ToLower(); mode = 1; attrValueStart = attrPos + 1; break; } } break; } case 1: // 속성 값 { for (; mode == 1 && attrPos < text.Length; attrPos++) { switch (text[attrPos]) { case '>': // 값 끝 attrs.Add(new string[] { name, text.Substring(attrNameStart, attrPos - attrNameStart).ToLower() }); mode = -1; attrPos++; break; case ' ': case '\t': case '\r': case '\n': // 값 끝 attrs.Add(new string[] { name, text.Substring(attrValueStart, attrPos - attrValueStart).ToLower() }); mode = 0; attrNameStart = attrPos + 1; break; case '\'': if (attrPos == attrValueStart) { mode = 2; attrValueStart++; } break; case '"': if (attrPos == attrValueStart) { mode = 3; attrValueStart++; } break; } } break; } case 2: // 속성 값 (작은 따옴표) { for (; mode == 2 && attrPos < text.Length; attrPos++) { switch (text[attrPos]) { case '\\': attrPos++; break; case '\'': // 값 끝 attrs.Add(new string[] { name, text.Substring(attrValueStart, attrPos - attrValueStart).ToLower() }); mode = 0; attrNameStart = attrPos + 1; break; } } break; } case 3: // 속성 값 (큰 따옴표) { for (; mode == 3 && attrPos < text.Length; attrPos++) { switch (text[attrPos]) { case '\\': attrPos++; break; case '"': // 값 끝 attrs.Add(new string[] { name, text.Substring(attrValueStart, attrPos - attrValueStart).ToLower() }); mode = 0; attrNameStart = attrPos + 1; break; } } break; } } } } } #endregion if (tagName[0].Equals('/')) // 종료 태그 { tagName = tagName.Substring(1); switch (tagName) { case "B": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetB(false)); break; case "I": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetI(false)); break; case "U": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetU(false)); break; case "FONT": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetFont(null)); break; default: break; } } else { switch (tagName) { case "B": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetB(true)); break; case "I": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetI(true)); break; case "U": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetU(true)); break; case "FONT": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")); if (last.text.Length > 0) { result.Add((last = new Attr())); } SetStyle(last, status.SetFont(attrs)); break; case "BR": last.text += WebUtility.HtmlDecode(text.Substring(index, pos - index).Replace("\n", "")) + "\n"; break; default: last.text += WebUtility.HtmlDecode(text.Substring(index, attrPos - index).Replace("\n", "")); break; } } index = attrPos; } last.text += WebUtility.HtmlDecode(text.Substring(index)); return(result); }