1 /* 2 * Copyright 2013, Stephan Aßmus <superstippi@gmx.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include "MarkupParser.h" 7 8 #include <new> 9 10 #include <math.h> 11 12 #include <utf8_functions.h> 13 14 15 MarkupParser::MarkupParser() 16 : 17 fNormalStyle(), 18 fBoldStyle(), 19 fItalicStyle(), 20 fBoldItalicStyle(), 21 fHeadingStyle(), 22 23 fParagraphStyle(), 24 fHeadingParagraphStyle(), 25 fBulletStyle(), 26 27 fCurrentCharacterStyle(&fNormalStyle), 28 fCurrentParagraphStyle(&fParagraphStyle), 29 fSpanStartOffset(0) 30 { 31 _InitStyles(); 32 } 33 34 35 MarkupParser::MarkupParser(const CharacterStyle& characterStyle, 36 const ParagraphStyle& paragraphStyle) 37 : 38 fNormalStyle(characterStyle), 39 fBoldStyle(), 40 fItalicStyle(), 41 fBoldItalicStyle(), 42 fHeadingStyle(), 43 44 fParagraphStyle(paragraphStyle), 45 fHeadingParagraphStyle(), 46 fBulletStyle(), 47 48 fCurrentCharacterStyle(&fNormalStyle), 49 fCurrentParagraphStyle(&fParagraphStyle), 50 fSpanStartOffset(0) 51 { 52 _InitStyles(); 53 } 54 55 56 void 57 MarkupParser::SetStyles(const CharacterStyle& characterStyle, 58 const ParagraphStyle& paragraphStyle) 59 { 60 fNormalStyle = characterStyle; 61 fParagraphStyle = paragraphStyle; 62 _InitStyles(); 63 } 64 65 66 TextDocumentRef 67 MarkupParser::CreateDocumentFromMarkup(const BString& text) 68 { 69 TextDocumentRef document(new(std::nothrow) TextDocument(), true); 70 if (!document.IsSet()) 71 return document; 72 73 AppendMarkup(document, text); 74 75 return document; 76 } 77 78 79 void 80 MarkupParser::AppendMarkup(const TextDocumentRef& document, const BString& text) 81 { 82 fTextDocument.SetTo(document); 83 84 fCurrentCharacterStyle = &fNormalStyle; 85 fCurrentParagraphStyle = &fParagraphStyle; 86 87 fCurrentParagraph = Paragraph(*fCurrentParagraphStyle); 88 fSpanStartOffset = 0; 89 90 _ParseText(text); 91 92 fTextDocument.Unset(); 93 } 94 95 96 // #pragma mark - private 97 98 99 void 100 MarkupParser::_InitStyles() 101 { 102 fBoldStyle = fNormalStyle; 103 fBoldStyle.SetBold(true); 104 105 fItalicStyle = fNormalStyle; 106 fItalicStyle.SetItalic(true); 107 108 fBoldItalicStyle = fNormalStyle; 109 fBoldItalicStyle.SetBold(true); 110 fBoldItalicStyle.SetItalic(true); 111 112 float fontSize = fNormalStyle.Font().Size(); 113 114 fHeadingStyle = fNormalStyle; 115 fHeadingStyle.SetFontSize(ceilf(fontSize * 1.15f)); 116 fHeadingStyle.SetBold(true); 117 118 fHeadingParagraphStyle = fParagraphStyle; 119 fHeadingParagraphStyle.SetSpacingTop(ceilf(fontSize * 0.8f)); 120 fHeadingParagraphStyle.SetSpacingBottom(ceilf(fontSize * 0.5f)); 121 fHeadingParagraphStyle.SetJustify(false); 122 123 fBulletStyle = fParagraphStyle; 124 fBulletStyle.SetBullet(Bullet("•", fontSize)); 125 fBulletStyle.SetLineInset(ceilf(fontSize * 0.8f)); 126 } 127 128 129 void 130 MarkupParser::_ParseText(const BString& text) 131 { 132 int32 start = 0; 133 int32 offset = 0; 134 135 int32 charCount = text.CountChars(); 136 const char* c = text.String(); 137 138 while (offset <= charCount) { 139 uint32 nextChar = UTF8ToCharCode(&c); 140 141 switch (nextChar) { 142 // Requires two line-breaks to start a new paragraph, unles the current 143 // paragraph is already considered a bullet list item. Doesn't work well 144 // with current set of packages. 145 // case '\n': 146 // _CopySpan(text, start, offset); 147 // if (offset + 1 < charCount && c[0] == '\n') { 148 // _FinishParagraph(); 149 // offset += 1; 150 // c += 1; 151 // } else if (fCurrentParagraph.Style() == fBulletStyle) { 152 // _FinishParagraph(); 153 // } 154 // start = offset + 1; 155 // break; 156 157 case '\n': 158 _CopySpan(text, start, offset); 159 if (offset > 0 && c[-1] != ' ') 160 _FinishParagraph(offset >= charCount); 161 start = offset + 1; 162 break; 163 164 case '\0': 165 _CopySpan(text, start, offset); 166 _FinishParagraph(true); 167 start = offset + 1; 168 break; 169 170 case '\'': 171 if (offset + 2 < charCount && c[0] == '\'') { 172 int32 tickCount = 2; 173 if (c[1] == '\'') 174 tickCount = 3; 175 176 // Copy previous span using current style, excluding the 177 // ticks. 178 _CopySpan(text, start, offset); 179 180 if (tickCount == 2) 181 _ToggleStyle(fItalicStyle); 182 else if (tickCount == 3) 183 _ToggleStyle(fBoldStyle); 184 185 // Don't include the ticks in the next span. 186 offset += tickCount - 1; 187 start = offset + 1; 188 c += tickCount - 1; 189 } 190 break; 191 192 case '=': 193 // Detect headings 194 if (offset == start 195 && fCurrentParagraph.IsEmpty() 196 && offset + 2 < charCount 197 && c[0] == '=' && c[1] == ' ') { 198 199 fCurrentParagraph.SetStyle(fHeadingParagraphStyle); 200 fCurrentCharacterStyle = &fHeadingStyle; 201 202 offset += 2; 203 c += 2; 204 205 start = offset + 1; 206 } else if (offset > start 207 && offset + 2 < charCount 208 && c[0] == '=' && c[1] == '\n') { 209 210 _CopySpan(text, start, offset - 1); 211 212 offset += 2; 213 c += 2; 214 215 _FinishParagraph(offset >= charCount); 216 217 start = offset + 1; 218 } 219 break; 220 221 case ' ': 222 // Detect bullets at line starts (preceeding space) 223 if (offset == start 224 && fCurrentParagraph.IsEmpty() 225 && offset + 2 < charCount 226 && (c[0] == '*' || c[0] == '-') && c[1] == ' ') { 227 228 fCurrentParagraph.SetStyle(fBulletStyle); 229 230 offset += 2; 231 c += 2; 232 233 start = offset + 1; 234 } 235 break; 236 237 case '*': 238 case '-': 239 // Detect bullets at line starts (no preceeding space) 240 if (offset == start 241 && fCurrentParagraph.IsEmpty() 242 && offset + 1 < charCount 243 && c[0] == ' ') { 244 245 fCurrentParagraph.SetStyle(fBulletStyle); 246 247 offset += 1; 248 c += 1; 249 250 start = offset + 1; 251 } 252 break; 253 254 default: 255 break; 256 257 } 258 offset++; 259 } 260 } 261 262 263 void 264 MarkupParser::_CopySpan(const BString& text, int32& start, int32 end) 265 { 266 if (start >= end) 267 return; 268 269 BString subString; 270 text.CopyCharsInto(subString, start, end - start); 271 fCurrentParagraph.Append(TextSpan(subString, *fCurrentCharacterStyle)); 272 273 start = end; 274 } 275 276 277 void 278 MarkupParser::_ToggleStyle(const CharacterStyle& style) 279 { 280 if (fCurrentCharacterStyle == &style) 281 fCurrentCharacterStyle = &fNormalStyle; 282 else 283 fCurrentCharacterStyle = &style; 284 } 285 286 287 void 288 MarkupParser::_FinishParagraph(bool isLast) 289 { 290 if (!isLast) 291 fCurrentParagraph.Append(TextSpan("\n", *fCurrentCharacterStyle)); 292 293 if (fCurrentParagraph.IsEmpty()) { 294 // Append empty span 295 fCurrentParagraph.Append(TextSpan("", fNormalStyle)); 296 } 297 298 fTextDocument->Append(fCurrentParagraph); 299 fCurrentParagraph.Clear(); 300 fCurrentParagraph.SetStyle(fParagraphStyle); 301 fCurrentCharacterStyle = &fNormalStyle; 302 } 303