* Get the position of a character in the layout. * @param ch Character to get the position of. Must be an iterator of the string passed to the constructor. * @return Upper left corner of the character relative to the start of the string. * @note Will only work right for single-line strings. */
| 235 | * @note Will only work right for single-line strings. |
| 236 | */ |
| 237 | ParagraphLayouter::Position Layouter::GetCharPosition(std::string_view::const_iterator ch) const |
| 238 | { |
| 239 | const auto &line = this->front(); |
| 240 | |
| 241 | /* Pointer to the end-of-string marker? Return total line width. */ |
| 242 | if (ch == this->string.end()) { |
| 243 | Point p = {_current_text_dir == TD_LTR ? line->GetWidth() : 0, 0}; |
| 244 | return p; |
| 245 | } |
| 246 | |
| 247 | /* Initial position, returned if character not found. */ |
| 248 | const ParagraphLayouter::Position initial_position = Point{_current_text_dir == TD_LTR ? 0 : line->GetWidth(), 0}; |
| 249 | |
| 250 | /* Find the code point index which corresponds to the char |
| 251 | * pointer into our UTF-8 source string. */ |
| 252 | size_t index = 0; |
| 253 | { |
| 254 | Utf8View view(this->string); |
| 255 | const size_t offset = ch - this->string.begin(); |
| 256 | const auto pos = view.GetIterAtByte(offset); |
| 257 | |
| 258 | /* We couldn't find the code point index. */ |
| 259 | if (pos.GetByteOffset() != offset) return initial_position; |
| 260 | |
| 261 | for (auto it = view.begin(); it < pos; ++it) { |
| 262 | char32_t c = *it; |
| 263 | if (!IsConsumedFormattingCode(c)) index += line->GetInternalCharLength(c); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | const ParagraphLayouter::Position *position = &initial_position; |
| 268 | |
| 269 | /* Scan all runs until we've found our code point index. */ |
| 270 | size_t best_index = SIZE_MAX; |
| 271 | for (int run_index = 0; run_index < line->CountRuns(); run_index++) { |
| 272 | const ParagraphLayouter::VisualRun &run = line->GetVisualRun(run_index); |
| 273 | const auto &positions = run.GetPositions(); |
| 274 | const auto &charmap = run.GetGlyphToCharMap(); |
| 275 | |
| 276 | auto itp = positions.begin(); |
| 277 | for (auto it = charmap.begin(); it != charmap.end(); ++it, ++itp) { |
| 278 | const size_t cur_index = static_cast<size_t>(*it); |
| 279 | /* Found exact character match? */ |
| 280 | if (cur_index == index) return *itp; |
| 281 | |
| 282 | /* If the character we are looking for has been combined with other characters to form a ligature then |
| 283 | * we may not be able to find an exact match. We don't actually know if our character is part of a |
| 284 | * ligature. In this case we will aim to select the first character of the ligature instead, so the best |
| 285 | * index is the index nearest to but lower than the desired index. */ |
| 286 | if (cur_index < index && (best_index < cur_index || best_index == SIZE_MAX)) { |
| 287 | best_index = cur_index; |
| 288 | position = &*itp; |
| 289 | } |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | /* At the end of the run but still didn't find our character so probably a trailing ligature, use the last found position. */ |
| 294 | return *position; |
no test coverage detected