Consumes data to minimize memory usage
| 464 | } |
| 465 | // Consumes data to minimize memory usage |
| 466 | void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fontId, const uint16_t viewportWidth, |
| 467 | const std::function<void(std::shared_ptr<TextBlock>)>& processLine, |
| 468 | const bool includeLastLine) { |
| 469 | if (words.empty()) { |
| 470 | return; |
| 471 | } |
| 472 | |
| 473 | // Per-paragraph RTL auto-detection: only when CSS/HTML didn't explicitly set direction. |
| 474 | // Explicit dir="ltr" must be respected and not overridden by content heuristic. |
| 475 | if (!blockStyle.directionDefined && hasRtlWord) { |
| 476 | // Check the first few words for RTL letter codepoints (no heap allocation). |
| 477 | const size_t wordsToScan = std::min(words.size(), RTL_PARAGRAPH_PROBE_WORDS); |
| 478 | for (size_t i = 0; i < wordsToScan; ++i) { |
| 479 | if (BidiUtils::startsWithRtl(words[i].c_str(), BidiUtils::RTL_PARAGRAPH_PROBE_DEPTH)) { |
| 480 | blockStyle.isRtl = true; |
| 481 | break; |
| 482 | } |
| 483 | } |
| 484 | } |
| 485 | |
| 486 | isNaturalAlign = |
| 487 | blockStyle.alignment == CssTextAlign::Justify || |
| 488 | (blockStyle.isRtl ? blockStyle.alignment == CssTextAlign::Right : blockStyle.alignment == CssTextAlign::Left); |
| 489 | |
| 490 | // Ensure SD card font glyph metrics are loaded before measuring word widths. |
| 491 | // For flash-based fonts isSdCardFont() returns false and this block is skipped |
| 492 | // entirely — no heap allocation. For SD card fonts this reads glyph metadata |
| 493 | // (advanceX only, no bitmaps) for all unique codepoints in this paragraph so |
| 494 | // that calculateWordWidths() can measure text without on-demand SD I/O. |
| 495 | if (renderer.isSdCardFont(fontId)) { |
| 496 | // Style mask: only ask the SD font to load advances for styles actually |
| 497 | // used in this paragraph. Style index is the low two bits (regular/bold/ |
| 498 | // italic/bold-italic); the underline bit is irrelevant to advance metrics. |
| 499 | uint8_t styleMask = 0; |
| 500 | for (auto s : wordStyles) { |
| 501 | styleMask |= static_cast<uint8_t>(1u << (static_cast<uint8_t>(s) & 0x03)); |
| 502 | } |
| 503 | if (styleMask == 0) styleMask = 0x01; // defensive: regular only |
| 504 | renderer.ensureSdCardFontReady(fontId, words, hyphenationEnabled, styleMask); |
| 505 | } |
| 506 | |
| 507 | const int pageWidth = viewportWidth; |
| 508 | auto wordWidths = calculateWordWidths(renderer, fontId); |
| 509 | |
| 510 | std::vector<size_t> lineBreakIndices; |
| 511 | if (hyphenationEnabled) { |
| 512 | // Use greedy layout that can split words mid-loop when a hyphenated prefix fits. |
| 513 | lineBreakIndices = |
| 514 | computeHyphenatedLineBreaks(renderer, fontId, pageWidth, wordWidths, wordContinues, wordNoSpaceBefore); |
| 515 | } else { |
| 516 | lineBreakIndices = computeLineBreaks(renderer, fontId, pageWidth, wordWidths, wordContinues, wordNoSpaceBefore); |
| 517 | } |
| 518 | const size_t lineCount = includeLastLine ? lineBreakIndices.size() : lineBreakIndices.size() - 1; |
| 519 | |
| 520 | for (size_t i = 0; i < lineCount; ++i) { |
| 521 | extractLine(i, pageWidth, wordWidths, wordContinues, wordNoSpaceBefore, lineBreakIndices, processLine, renderer, |
| 522 | fontId); |
| 523 | } |
no test coverage detected