-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathUIManagerTextLayout.cpp
More file actions
331 lines (298 loc) · 12.5 KB
/
UIManagerTextLayout.cpp
File metadata and controls
331 lines (298 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#include "UIManager.h"
#include "EpubParser.h"
extern EpubParser epubParser;
extern SDHandler sdHandler;
extern LibraryManager libraryManager;
extern SettingsManager settingsManager;
void UIManager::processTextBlock(
const String &text, int startX, int startY, int width, int height,
std::function<void(const String &, int, int, int)> onPrintLine,
std::function<void(const String &, int, int, int, int)> onRenderImage,
std::function<void(size_t)> onPageBreak,
int startIdx)
{
int cursorX = startX;
int cursorY = startY;
int normalLineHeight = NORMAL_LINE_HEIGHT;
int headerLineHeight = HEADER_LINE_HEIGHT;
int remainingHeight = height;
String currentLine = "";
String word = "";
bool inHeader = false;
bool pendingFontReset = false;
// Track how many raw characters from 'text' have been consumed relative to startIdx.
// This aligns sectionStarts with EpubParser::extractTextFromElement targetStart.
size_t consumedFromInput = 0;
// Serial.println("ProcessTextBlock Input txt: "+String(text));
auto flushCurrentLine = [&](int idx)
{
int lineHeight = inHeader ? headerLineHeight : normalLineHeight;
if (!currentLine.isEmpty())
{
if (remainingHeight < lineHeight)
{
// Notify with raw characters consumed so far from the source string
onPageBreak(consumedFromInput);
// Early return: stop processing further characters after page break
return true; // signal break occurred
}
consumedFromInput = idx;
onPrintLine(currentLine, cursorX, cursorY, lineHeight);
cursorY += lineHeight;
remainingHeight -= lineHeight;
currentLine = "";
}
return false; // no break
};
for (size_t i = startIdx; i < text.length(); i++)
{
// Normalize consumedFromInput at the start of each iteration
// consumedFromInput = i - startIdx;
if (text.substring(i, i + 10) == "<img src=\"")
{
// Before handling the image, ensure any pending text is flushed
if (flushCurrentLine(i))
return; // a break occurred while flushing text
if (!word.isEmpty())
{
currentLine += word + " ";
word = "";
if (flushCurrentLine(i))
return;
}
// Parse image tag and resolve path
int closingQuote = text.indexOf("\"", i + 10);
if (closingQuote == -1)
continue;
String imgPath = text.substring(i + 10, closingQuote);
int tagEndIndex = closingQuote + 2; // position after closing quote and trailing character(s)
String fullImgPath = "/library/" + libraryManager.getCurrentBook() + "/" + pagePath + imgPath;
fullImgPath = sdHandler.normalizePath(fullImgPath);
int imgW, imgH;
if (!imageScaler.getScaledDimensions(fullImgPath.c_str(), width, height, imgW, imgH))
{
Serial.println("Failed to get image dimensions for: " + fullImgPath);
i = tagEndIndex;
continue;
}
// If image is taller than the page height, ensure it's rendered starting on a fresh page.
if (imgH > height)
{
// If we're not at the top of the page, break now and start next page from the image tag
if (cursorY != startY)
{
onPageBreak((size_t)(i - startIdx));
return;
}
// We are at the top: render the oversized image anyway
onRenderImage(fullImgPath, cursorX, cursorY, imgW, imgH);
cursorY += imgH;
remainingHeight = 0; // no more space on this page
consumedFromInput = (size_t)(tagEndIndex - startIdx); // advance past the image tag for next section
onPageBreak(consumedFromInput);
return;
}
// Image fits within a page but not the remaining space: break to next page starting at image
if (remainingHeight < imgH)
{
onPageBreak((size_t)(i - startIdx));
return;
}
// Render image on current page
onRenderImage(fullImgPath, cursorX, cursorY, imgW, imgH);
cursorY += imgH;
remainingHeight -= imgH;
// Advance parser index to after the image tag and continue
i = tagEndIndex;
continue;
}
if (text.substring(i, i + 3) == "<h>")
{
flushCurrentLine(i);
inHeader = true;
setFont(FONT_PRIM, FONT_SIZE_LARGE);
i += 2;
// consumedFromInput = i - startIdx;
continue;
}
if (text.substring(i, i + 4) == "</h>")
{
pendingFontReset = true;
i += 3;
// consumedFromInput = i - startIdx;
continue;
}
char c = text[i];
int lineHeight = inHeader ? headerLineHeight : normalLineHeight;
if (c == ' ' || c == '\n')
{
int16_t x1, y1;
uint16_t wordWidth, h;
display->getTextBounds(currentLine + word, 0, 0, &x1, &y1, &wordWidth, &h);
if (wordWidth > width)
{
if (flushCurrentLine(i - word.length()))
return;
}
currentLine += word + " ";
word = "";
if (c == '\n')
{
if (flushCurrentLine(i))
return;
}
}
else
{
// Serial.println("Consumed character: "+String(c));
word += c;
// consumedFromInput = i + 1 - startIdx; // include this character
}
if (pendingFontReset && word.isEmpty() && currentLine.isEmpty())
{
if (remainingHeight < headerLineHeight)
{
Serial.printf("[processTextBlock] PageBreak on header reset: consumed=%u i=%u startIdx=%u\n", (unsigned)consumedFromInput, (unsigned)i, (unsigned)startIdx);
onPageBreak(consumedFromInput);
return; // early exit
}
cursorY += headerLineHeight;
remainingHeight -= headerLineHeight;
setFont(FONT_PRIM, FONT_SIZE_DEFAULT);
inHeader = false;
pendingFontReset = false;
}
}
if (!word.isEmpty())
currentLine += word + " ";
flushCurrentLine(text.length()); // last line; ignore break signal here intentionally
// At end, we don't force page break; caller can interpret final page char count if needed
}
TextRenderResult UIManager::renderTextBlockSection(
const String &text, int startX, int startY, int width, int height, int pageNum, int startIdx)
{
setFont(FONT_PRIM, FONT_SIZE_DEFAULT);
int currentPage = 0;
bool done = false;
display->setTextColor(settingsManager.getFgColor());
bool printing = false;
String printedText = "";
int textStartIdx = -1;
int textEndIdx = -1;
TextRenderResult result;
processTextBlock(
text, startX, startY, width, height,
[&](const String &line, int x, int y, int lineHeight)
{
// if (currentPage == pageNum) {
if (!done)
{
if (textStartIdx == -1)
textStartIdx = startIdx;
display->setCursor(x, y);
display->print(line);
printing = true;
printedText += line + "\n";
}
// Serial.println(line);
startIdx += line.length() + 1; // +1 for space or newline separation
},
[&](const String &path, int x, int y, int w, int h)
{
// if (currentPage == pageNum) {
if (!done)
{
renderImage(path, x, y);
printing = true;
}
// Images don't count towards text index increments
},
[&](size_t consumedFromInput)
{
// Page break occurred; we stop rendering further lines.
done = true;
// result.endIndex += startIdx;
result.endIndex += consumedFromInput;
// Could record printedCharsBeforeBreak if needed (e.g., result.pageCharCount)
},
startIdx // pass startIdx down into processTextBlock
);
textEndIdx = startIdx;
result.printedText = printedText;
result.startIndex = textStartIdx;
// previousSectionIndex = currentSectionIndex;
// currentSectionIndex = result.startIndex;
nextSectionIndex = result.endIndex + 1;
// Serial.println("previousSectionIndex: " + String(previousSectionIndex));
// Serial.println("currentSectionIndex: " + String(currentSectionIndex));
// Serial.println("nextSectionIndex: " + String(nextSectionIndex));
// Serial.println("printed text: " + String(printedText));
return result;
}
int UIManager::sectionsForTextBlock(int startX, int startY, int width, int height)
{
// Streaming implementation: use epubParser.getPageContent(book,page,start,len) to walk the entire page
// and simulate layout with processTextBlock over each accumulated slice. We avoid building the full page string.
sectionStarts.clear();
// Try to load cached sections from storage
std::vector<size_t> cachedSections;
if (libraryManager.loadCurrentPageSections(cachedSections) && !cachedSections.empty())
{
sectionStarts = cachedSections;
return (int)sectionStarts.size();
}
// Ensure font matches renderTextBlockSection for consistent measurements
setFont(FONT_PRIM, FONT_SIZE_DEFAULT);
// Ensure pagePath is set so image dimensions resolve correctly during layout
pagePath = libraryManager.getCurrentPagePath();
int currentPage = libraryManager.getCurrentPage();
size_t globalStart = 0; // start offset for next slice request
const size_t SLICE_LEN = 2048; // tune slice size
String accumulated = ""; // unused with direct slice processing
sectionStarts.push_back(0); // first section
// We will feed chunks into processTextBlock, but we need a way to stop mid-buffer where a page break occurs.
// Strategy: run processTextBlock completely on accumulated, then discard consumed part.
// For simplicity, we will reset after each chunk; page indices approximate raw character positions.
// NOTE: This assumes tags (<h>, <img>) are included; their characters count in raw offsets.
bool end = false;
while (!end)
{
String slice = epubParser.getPageContent(libraryManager.getCurrentBook(), currentPage, sectionStarts.back(), SLICE_LEN);
if (slice.isEmpty())
{
end = true;
break;
}
// accumulated += slice; // append new slice
// Serial.println(slice);
// Layout this accumulated block
processTextBlock(
slice, startX, startY, width, height,
[&](const String &line, int x, int y, int lineHeight)
{
// Rendering callback not used for index calculation here
// Serial.println(line);
},
[&](const String &path, int x, int y, int w, int h)
{
// Image callback not used for index calculation here
// TODO: There is still a small bug sometime loosing a section when there are in page images
},
[&](size_t consumedFromInput)
{
// consumedFromInput is the number of characters from this slice/page before break.
// Serial.println("consumedFromInput: " + String(consumedFromInput));
sectionStarts.push_back(sectionStarts.back() + consumedFromInput);
});
// Advance start for next slice based on latest sectionStarts
globalStart = sectionStarts.back();
// If less than requested slice length, we've reached end
if (slice.length() < SLICE_LEN)
end = true;
// Accumulated buffer fully processed; clear to avoid re-processing
// accumulated = ""; // we rely solely on processedChars continuing from previous
}
// Persist computed sections for future loads
libraryManager.saveCurrentPageSections(sectionStarts);
return (int)sectionStarts.size();
}