InternetCiter.cpp (10297B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "InternetCiter.h" 7 8 #include "mozilla/Casting.h" 9 #include "mozilla/intl/Segmenter.h" 10 #include "HTMLEditUtils.h" 11 #include "nsAString.h" 12 #include "nsCOMPtr.h" 13 #include "nsCRT.h" 14 #include "nsDebug.h" 15 #include "nsDependentSubstring.h" 16 #include "nsError.h" 17 #include "nsServiceManagerUtils.h" 18 #include "nsString.h" 19 #include "nsStringIterator.h" 20 21 namespace mozilla { 22 23 /** 24 * Mail citations using the Internet style: > This is a citation. 25 */ 26 27 void InternetCiter::GetCiteString(const nsAString& aInString, 28 nsAString& aOutString) { 29 aOutString.Truncate(); 30 char16_t uch = HTMLEditUtils::kNewLine; 31 32 // Strip trailing new lines which will otherwise turn up 33 // as ugly quoted empty lines. 34 nsReadingIterator<char16_t> beginIter, endIter; 35 aInString.BeginReading(beginIter); 36 aInString.EndReading(endIter); 37 while (beginIter != endIter && (*endIter == HTMLEditUtils::kCarriageReturn || 38 *endIter == HTMLEditUtils::kNewLine)) { 39 --endIter; 40 } 41 42 // Loop over the string: 43 while (beginIter != endIter) { 44 if (uch == HTMLEditUtils::kNewLine) { 45 aOutString.Append(HTMLEditUtils::kGreaterThan); 46 // No space between >: this is ">>> " style quoting, for 47 // compatibility with RFC 2646 and format=flowed. 48 if (*beginIter != HTMLEditUtils::kGreaterThan) { 49 aOutString.Append(HTMLEditUtils::kSpace); 50 } 51 } 52 53 uch = *beginIter; 54 ++beginIter; 55 56 aOutString += uch; 57 } 58 59 if (uch != HTMLEditUtils::kNewLine) { 60 aOutString += HTMLEditUtils::kNewLine; 61 } 62 } 63 64 static void AddCite(nsAString& aOutString, int32_t citeLevel) { 65 for (int32_t i = 0; i < citeLevel; ++i) { 66 aOutString.Append(HTMLEditUtils::kGreaterThan); 67 } 68 if (citeLevel > 0) { 69 aOutString.Append(HTMLEditUtils::kSpace); 70 } 71 } 72 73 static inline void BreakLine(nsAString& aOutString, uint32_t& outStringCol, 74 uint32_t citeLevel) { 75 aOutString.Append(HTMLEditUtils::kNewLine); 76 if (citeLevel > 0) { 77 AddCite(aOutString, citeLevel); 78 outStringCol = citeLevel + 1; 79 } else { 80 outStringCol = 0; 81 } 82 } 83 84 static inline bool IsSpace(char16_t c) { 85 return (nsCRT::IsAsciiSpace(c) || (c == HTMLEditUtils::kNewLine) || 86 (c == HTMLEditUtils::kCarriageReturn) || (c == HTMLEditUtils::kNBSP)); 87 } 88 89 void InternetCiter::Rewrap(const nsAString& aInString, uint32_t aWrapCol, 90 uint32_t aFirstLineOffset, bool aRespectNewlines, 91 nsAString& aOutString) { 92 // There shouldn't be returns in this string, only dom newlines. 93 // Check to make sure: 94 #ifdef DEBUG 95 int32_t crPosition = aInString.FindChar(HTMLEditUtils::kCarriageReturn); 96 NS_ASSERTION(crPosition < 0, "Rewrap: CR in string gotten from DOM!\n"); 97 #endif /* DEBUG */ 98 99 aOutString.Truncate(); 100 101 // Loop over lines in the input string, rewrapping each one. 102 uint32_t posInString = 0; 103 uint32_t outStringCol = 0; 104 uint32_t citeLevel = 0; 105 const nsPromiseFlatString& tString = PromiseFlatString(aInString); 106 const uint32_t length = tString.Length(); 107 while (posInString < length) { 108 // Get the new cite level here since we're at the beginning of a line 109 uint32_t newCiteLevel = 0; 110 while (posInString < length && 111 tString[posInString] == HTMLEditUtils::kGreaterThan) { 112 ++newCiteLevel; 113 ++posInString; 114 while (posInString < length && 115 tString[posInString] == HTMLEditUtils::kSpace) { 116 ++posInString; 117 } 118 } 119 if (posInString >= length) { 120 break; 121 } 122 123 // Special case: if this is a blank line, maintain a blank line 124 // (retain the original paragraph breaks) 125 if (tString[posInString] == HTMLEditUtils::kNewLine && 126 !aOutString.IsEmpty()) { 127 if (aOutString.Last() != HTMLEditUtils::kNewLine) { 128 aOutString.Append(HTMLEditUtils::kNewLine); 129 } 130 AddCite(aOutString, newCiteLevel); 131 aOutString.Append(HTMLEditUtils::kNewLine); 132 133 ++posInString; 134 outStringCol = 0; 135 continue; 136 } 137 138 // If the cite level has changed, then start a new line with the 139 // new cite level (but if we're at the beginning of the string, 140 // don't bother). 141 if (newCiteLevel != citeLevel && posInString > newCiteLevel + 1 && 142 outStringCol) { 143 BreakLine(aOutString, outStringCol, 0); 144 } 145 citeLevel = newCiteLevel; 146 147 // Prepend the quote level to the out string if appropriate 148 if (!outStringCol) { 149 AddCite(aOutString, citeLevel); 150 outStringCol = citeLevel + (citeLevel ? 1 : 0); 151 } 152 // If it's not a cite, and we're not at the beginning of a line in 153 // the output string, add a space to separate new text from the 154 // previous text. 155 else if (outStringCol > citeLevel) { 156 aOutString.Append(HTMLEditUtils::kSpace); 157 ++outStringCol; 158 } 159 160 // find the next newline -- don't want to go farther than that 161 int32_t nextNewline = 162 tString.FindChar(HTMLEditUtils::kNewLine, posInString); 163 if (nextNewline < 0) { 164 nextNewline = length; 165 } 166 167 // For now, don't wrap unquoted lines at all. 168 // This is because the plaintext edit window has already wrapped them 169 // by the time we get them for rewrap, yet when we call the line 170 // breaker, it will refuse to break backwards, and we'll end up 171 // with a line that's too long and gets displayed as a lone word 172 // on a line by itself. Need special logic to detect this case 173 // and break it ourselves without resorting to the line breaker. 174 if (!citeLevel) { 175 aOutString.Append( 176 Substring(tString, posInString, nextNewline - posInString)); 177 outStringCol += nextNewline - posInString; 178 if (nextNewline != (int32_t)length) { 179 aOutString.Append(HTMLEditUtils::kNewLine); 180 outStringCol = 0; 181 } 182 posInString = nextNewline + 1; 183 continue; 184 } 185 186 // Otherwise we have to use the line breaker and loop 187 // over this line of the input string to get all of it: 188 while ((int32_t)posInString < nextNewline) { 189 // Skip over initial spaces: 190 while ((int32_t)posInString < nextNewline && 191 nsCRT::IsAsciiSpace(tString[posInString])) { 192 ++posInString; 193 } 194 195 // If this is a short line, just append it and continue: 196 if (outStringCol + nextNewline - posInString <= 197 aWrapCol - citeLevel - 1) { 198 // If this short line is the final one in the in string, 199 // then we need to include the final newline, if any: 200 if (nextNewline + 1 == (int32_t)length && 201 tString[nextNewline - 1] == HTMLEditUtils::kNewLine) { 202 ++nextNewline; 203 } 204 // Trim trailing spaces: 205 int32_t lastRealChar = nextNewline; 206 while ((uint32_t)lastRealChar > posInString && 207 nsCRT::IsAsciiSpace(tString[lastRealChar - 1])) { 208 --lastRealChar; 209 } 210 211 aOutString += 212 Substring(tString, posInString, lastRealChar - posInString); 213 outStringCol += lastRealChar - posInString; 214 posInString = nextNewline + 1; 215 continue; 216 } 217 218 int32_t eol = posInString + aWrapCol - citeLevel - outStringCol; 219 // eol is the prospective end of line. 220 // If it's already less than our current position, 221 // then our line is already too long, so break now. 222 if (eol <= (int32_t)posInString) { 223 BreakLine(aOutString, outStringCol, citeLevel); 224 continue; // continue inner loop, with outStringCol now at bol 225 } 226 227 MOZ_ASSERT(eol >= 0 && eol - posInString > 0); 228 229 uint32_t breakPt = 0; 230 Maybe<uint32_t> nextBreakPt; 231 intl::LineBreakIteratorUtf16 lineBreakIter(Span<const char16_t>( 232 tString.get() + posInString, length - posInString)); 233 while (true) { 234 nextBreakPt = lineBreakIter.Next(); 235 if (!nextBreakPt || 236 *nextBreakPt > AssertedCast<uint32_t>(eol) - posInString) { 237 break; 238 } 239 breakPt = *nextBreakPt; 240 } 241 242 if (breakPt == 0) { 243 // If we couldn't find a breakpoint within the eol upper bound, and 244 // we're not starting a new line, then end this line and loop around 245 // again: 246 if (outStringCol > citeLevel + 1) { 247 BreakLine(aOutString, outStringCol, citeLevel); 248 continue; // continue inner loop, with outStringCol now at bol 249 } 250 251 MOZ_ASSERT(nextBreakPt.isSome(), 252 "Next() always treats end-of-text as a break"); 253 breakPt = *nextBreakPt; 254 } 255 256 // Special case: maybe we should have wrapped last time. 257 // If the first breakpoint here makes the current line too long, 258 // then if we already have text on the current line, 259 // break and loop around again. 260 // If we're at the beginning of the current line, though, 261 // don't force a break since the long word might be a url 262 // and breaking it would make it unclickable on the other end. 263 const int SLOP = 6; 264 if (outStringCol + breakPt > aWrapCol + SLOP && 265 outStringCol > citeLevel + 1) { 266 BreakLine(aOutString, outStringCol, citeLevel); 267 continue; 268 } 269 270 nsAutoString sub(Substring(tString, posInString, breakPt)); 271 // skip newlines or white-space at the end of the string 272 int32_t subend = sub.Length(); 273 while (subend > 0 && IsSpace(sub[subend - 1])) { 274 --subend; 275 } 276 sub.Left(sub, subend); 277 aOutString += sub; 278 outStringCol += sub.Length(); 279 // Advance past the white-space which caused the wrap: 280 posInString += breakPt; 281 while (posInString < length && IsSpace(tString[posInString])) { 282 ++posInString; 283 } 284 285 // Add a newline and the quote level to the out string 286 if (posInString < length) { // not for the last line, though 287 BreakLine(aOutString, outStringCol, citeLevel); 288 } 289 } // end inner loop within one line of aInString 290 } // end outer loop over lines of aInString 291 } 292 293 } // namespace mozilla