Embedded SDK
Embedded SDK
ux_layout_pages.c
Go to the documentation of this file.
1 
2 /*******************************************************************************
3  * Ledger Nano S - Secure firmware
4  * (c) 2022 Ledger
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  ********************************************************************************/
18 #include "os_helpers.h"
19 #include "os_math.h"
20 #include "os_pic.h"
21 #include "os_print.h"
22 #include "os_utils.h"
23 #include "ux.h"
24 #include <string.h>
25 #include "os.h"
26 
27 #ifdef HAVE_UX_FLOW
28 #if defined(HAVE_INDEXED_STRINGS)
29 
30 #include "ux_layout_common.h"
31 
32 #ifdef HAVE_BAGL
33 
34 #if (defined HAVE_BOLOS || defined(BUILD_PNG))
35 
36 // clang-format off
37 static const bagl_element_t ux_layout_pages_elements[] = {
38 #if (BAGL_WIDTH==128 && BAGL_HEIGHT==64)
39 // Default Layout for LNX/LNS+:
40 // Coordinates for Layout:
41 // N => X=6 Y=35
42 // P => X=57 Y=26
43 // NN => X=6 Y=27,43
44 // PN => X=57,6 Y=17,44
45 // NP => X=6,57 Y=26,33
46 // P/N => X=41 Y=35
47 // P/NN => X=41 Y=27,43
48 // NNN => X=6 Y=19,35,51
49 // PNN => X=57,6,6 Y=10,36,52
50 // NNP => X=6,6,57 Y=19,34,41
51 // NNNN => X=6 Y=14,28,42,56
52 // PNNN => X=57,6,6,6 Y=5,29,43,56
53 // NNNP => X=6,6,57 Y=14,27,41,45
54 
55  // erase
56  {{BAGL_RECTANGLE , 0x00, 0, 0, 128, 64, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, .text=NULL},
57  // Left & Right arrows, when applicable:
58  {{BAGL_ICON , 0x01, 2, 28, 4, 7, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=(const char*)&C_icon_left},
59  {{BAGL_ICON , 0x02, 122, 28, 4, 7, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=(const char*)&C_icon_right},
60  // 14x14 Icon on the left side, vertically centered
61  {{BAGL_ICON , 0x0F, 16, 24, 16, 16, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=NULL},
62  // Up to 4 lines of text (can be mixed with one centered 14x14 Icon):
63  {{BAGL_LABELINE , 0x10, 6, 15, 116, 32, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
64  {{BAGL_LABELINE , 0x11, 6, 29, 116, 32, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
65  {{BAGL_LABELINE , 0x12, 6, 43, 116, 32, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
66  {{BAGL_LABELINE , 0x13, 6, 57, 116, 32, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
67 
68 #elif (BAGL_WIDTH==128 && BAGL_HEIGHT==32)
69 // Default Layout for LNS:
70 // Coordinates for Layout:
71 // N => X=6 Y=19
72 // P => X=57 Y=9
73 // NN => X=6 Y=12,26
74 // PN => X=57,6 Y=2,27
75 // NP => X=6,57 Y=11,16
76 // P/N => X=41 Y=19
77 // P/NN => X=41 Y=12,26
78 
79  // erase
80  {{BAGL_RECTANGLE , 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, .text=NULL},
81  // Left & Right arrows, when applicable:
82  {{BAGL_ICON , 0x01, 2, 12, 4, 7, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=(const char*)&C_icon_left},
83  {{BAGL_ICON , 0x02, 122, 12, 4, 7, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=(const char*)&C_icon_right},
84  // 14x14 Icon on the left side, vertically centered
85  {{BAGL_ICON , 0x0F, 14, 8, 16, 16, 0, 0, 0 , 0xFFFFFF, 0x000000, 0, 0 }, .text=NULL},
86  // Up to 2 lines of text (can be mixed with one centered 14x14 Icon):
87  {{BAGL_LABELINE , 0x10, 6, 12, 116, 32, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
88  {{BAGL_LABELINE , 0x11, 6, 26, 116, 32, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, .text=NULL},
89 #endif // (BAGL_WIDTH==128 && BAGL_HEIGHT==64)
90 };
91 // clang-format on
92 
93 // X Coordinate & width of text when there is an icon centered on the side:
94 #define BAGL_ICON_TEXT_XCOORD 38
95 #define BAGL_ICON_TEXT_WIDTH (BAGL_WIDTH - BAGL_ICON_TEXT_XCOORD)
96 
97 // =============================================================================
98 // This function will parse text and build arrays of txt ptr & lengths for each
99 // line, taking in account specific contents like string buffer, side icons etc.
100 // It returns a ptr to the side icon if there is one on the page (on any line).
101 const bagl_icon_details_t *update_lines_length(const char *text,
102  const char *lines[],
103  unsigned short lengths[])
104 {
105  const bagl_icon_details_t *icon_details = NULL;
106 
107  // Override text parameter by a string buffer addr, eventually:
108  // (this is necessary when a string buffer is displayed over several pages)
109  const char *string_buffer = get_string_buffer(G_ux.layout_paging.string_buffer_id);
110 
111  // Parse all lines on this page to see if there is something special to do:
112  for (int i = 0; i < UX_LAYOUT_PAGES_LINE_COUNT; i++) {
113  const char *txt;
114  unsigned short length = G_ux.layout_paging.lengths[i];
115 
116  // When using UX_LOC_PAGING macro, first line contain the title
117  if (!i && G_ux.layout_paging.paging_title) {
118  const char *title = get_ux_loc_string(G_ux.layout_paging.paging_title - 1);
119  if (title) {
120  SPRINTF(G_ux.string_buffer,
121  (G_ux.layout_paging.count > 1) ? "%s (%d/%d)" : "%s",
122  title,
123  G_ux.layout_paging.current + 1,
124  G_ux.layout_paging.count);
125  }
126  else {
127  SPRINTF(G_ux.string_buffer,
128  "%d/%d",
129  G_ux.layout_paging.current + 1,
130  G_ux.layout_paging.count);
131  }
132  // Store text pointer & length for this line:
133  lines[0] = G_ux.string_buffer;
134  lengths[0] = strlen(G_ux.string_buffer);
135  continue;
136  }
137 
138  // Is there anything to display on this line?
139  if (length == 0) {
140  lines[i] = NULL;
141  lengths[i] = 0;
142  continue;
143  }
144  if (string_buffer) {
145  txt = string_buffer;
146  }
147  else if (text) {
148  txt = STRPIC(text);
149  }
150  else {
151  txt = G_ux.externalText;
152  }
153  // Take in account offset which is relative to txt ptr:
154  txt += G_ux.layout_paging.offsets[i];
155 
156  // Check if this line starts with an escape sequence:
157  while (txt && txt[0] == '\e') {
158  // There is at least one more byte which is readable.
159  // (even if it is the end of the string, it contains '\0')
160  unsigned short extra_byte = (unsigned char) txt[1];
161  // Is it an icon?
162  if (extra_byte >= FIRST_GLYPHS_ICON && extra_byte <= LAST_GLYPHS_ICON) {
163  // Does this line contain a 'left side icon'?
164  // If there is only an icon, then it is displayed on a full line.
165  // If there are text after the icon, it is displayed on left side.
166  // (=> This is what userid 0x0F is about!!)
167  if (length > 2) {
168  // This is a side icon => update icon_details ptr:
169  icon_details = get_glyphs_icon(extra_byte);
170  // Skip the side icon related bytes => update txt ptr & length:
171  txt += 2;
172  length -= 2;
173  // Skip all spaces characters after the side icon related bytes:
174  while (*txt == ' ' && length) {
175  ++txt;
176  --length;
177  }
178  continue; // Check if there is something else to handle...
179  }
180  else {
181  // There is just an icon displayed on a full line.
182  break;
183  }
184  }
185  else {
186  if (extra_byte >= FIRST_STRING_BUFFER && extra_byte <= LAST_STRING_BUFFER) {
187  // WARNING: if the string buffer contain an escape sequence to a
188  // string buffer, then we'll have an infinite loop! => Avoid that...
189  if (string_buffer) {
190  // Don't recursively parse string buffers!
191  txt = NULL;
192  length = 0;
193  break;
194  }
195 
196  // There is a string buffer, here => update txt to point on it:
197  txt = get_string_buffer(extra_byte);
198  // We have just switched from txt to string buffer => offset=0.
199  // (G_ux.layout_paging.offsets[i] is relative to original txt ptr)
200  // (G_ux.layout_paging.lengths[i] is related to string buffer)
201  // (G_ux.layout_paging.offsets[>i] are relative to string buffer)
202  // Use that string buffer for next lines on this page:
203  string_buffer = txt;
204  // Check for an escape sequence at the beginning of the string buffer:
205  continue;
206  }
207  else { // This is not an icon nor a screen buffer...ignore it!
208  txt = NULL;
209  length = 0;
210  break;
211  }
212  }
213  }
214  // Store text pointer & length for this line:
215  lines[i] = txt;
216  lengths[i] = length;
217  }
218 
219  // Return a pointer to the side icon, if there is one on this page:
220  return icon_details;
221 }
222 
223 // =============================================================================
224 
225 const bagl_element_t *ux_layout_pages_prepro_common(const bagl_element_t *element, const char *text)
226 {
227  const char *lines[UX_LAYOUT_PAGES_LINE_COUNT];
228  unsigned short lengths[UX_LAYOUT_PAGES_LINE_COUNT];
229 
230  // copy element before any mod
231  memmove(&G_ux.tmp_element, element, sizeof(bagl_element_t));
232 
233  switch (element->component.userid) {
234  case 0x01:
235  // no step before AND no pages before
236  if (ux_flow_is_first() && G_ux.layout_paging.current == 0) {
237  return NULL;
238  }
239  break;
240 
241  case 0x02:
242  if (ux_flow_is_last() && G_ux.layout_paging.current == G_ux.layout_paging.count - 1) {
243  return NULL;
244  }
245  break;
246 
247  // Do we have to display 14x14 Icon on the left side, vertically centered?
248  case 0x0F:
249  case 0x1F: {
250  // Check all lines on this page to see if there is an icon on the side:
251  const bagl_icon_details_t *icon_side_details
252  = update_lines_length(text, lines, lengths);
253  if (icon_side_details != NULL) {
254  G_ux.tmp_element.text = (const void *) icon_side_details;
255  return &G_ux.tmp_element;
256  }
257  // There is no 14x14 icon to display on the side.
258  return NULL;
259  }
260  // Up to 4 lines of text (can be mixed with one centered 14x14 Icon):
261  case 0x10:
262  case 0x11:
263  case 0x12:
264  case 0x13:
265  case 0x20:
266  case 0x21:
267  case 0x22:
268  case 0x23: {
269  unsigned short lineidx = element->component.userid & 0xF;
270  // Check if there is something to display on this line:
271  if (lineidx >= UX_LAYOUT_PAGES_LINE_COUNT || G_ux.layout_paging.lengths[lineidx] == 0) {
272  return NULL;
273  }
274  // Check all lines on this page and update lines & lengths information:
275  // (if icon_side_details != NULL => there is an icon on left side)
276  const bagl_icon_details_t *icon_side_details
277  = update_lines_length(text, lines, lengths);
278  G_ux.tmp_element.component.font_id = G_ux.layout_paging.fond_ids[lineidx];
279  // When the icon is displayed on left side, text is left aligned:
280  if (icon_side_details != NULL) {
281  G_ux.tmp_element.component.font_id |= BAGL_FONT_ALIGNMENT_LEFT;
282  }
283  else {
284  G_ux.tmp_element.component.font_id |= BAGL_FONT_ALIGNMENT_CENTER;
285  }
286  // Let's eat a little bit more vertical space on LNX:
287 #if (BAGL_HEIGHT == 64)
288  short y_margin = BAGL_HEIGHT - FONT_HEIGHT; // Start at 52
289 #endif //(BAGL_HEIGHT==64)
290  unsigned short nb_lines = 0; // Number of lines displayed in this screen
291  short y_coord = 0; // Y coordinate for this element
292  short x_coord = G_ux.tmp_element.component.x;
293  unsigned short used_height = 0; // Number of vertical pixels used
294 
295  // Find at which X,Y coordinates this line have to be displayed:
296  // (we need to scan all lines...)
297  for (int i = 0; i < UX_LAYOUT_PAGES_LINE_COUNT; i++) {
298  unsigned short length;
299  const char *txt;
300 
301  // Skip that line if it doesn't contain anything:
302  if ((length = lengths[i]) == 0 || (txt = lines[i]) == NULL) {
303  continue;
304  }
305  // By default consider this line will contain text => use font height
306  unsigned short height = FONT_HEIGHT;
307 
308  // If there is an icon displayed on the left side, adjust xcoord and
309  // available width of ALL lines:
310  if (icon_side_details != NULL) {
311  // The icon will be displayed when element->component.userid == 0x0F.
312  x_coord = BAGL_ICON_TEXT_XCOORD; // That's the X coord for PNN on LNS
313  G_ux.tmp_element.component.width = BAGL_ICON_TEXT_WIDTH;
314  }
315  // Does this line contain an icon?
316  // If yes it is the first characters in the string.
317  // If there is only the icon, then it is displayed on a full line.
318  // If there are text after the icon, it is displayed on left side.
319  // (This MUST HAVE BEEN handled before)
320  if (txt[0] == '\e') {
321  unsigned short extra_byte = (unsigned char) txt[1];
322  // Is it really an icon?
323  if (extra_byte >= FIRST_GLYPHS_ICON && extra_byte <= LAST_GLYPHS_ICON) {
324  // Be sure this icon have to be displayed here:
325  if (length == 2) {
326  // That line contain an icon => update height with correct value:
327  const bagl_icon_details_t *icon_details = get_glyphs_icon(extra_byte);
328  height = icon_details->height;
329  // If the icon is on current line, display it instead of text:
330  if (lineidx == i) {
331  G_ux.tmp_element.component.type = BAGL_ICON;
332  G_ux.tmp_element.component.icon_id = 0;
333  G_ux.tmp_element.text = (const char *) icon_details;
334  // Update x_coord, width & height using icon's width:
335  x_coord
336  += (G_ux.tmp_element.component.width - icon_details->width) / 2;
337  G_ux.tmp_element.component.height = icon_details->height;
338  G_ux.tmp_element.component.width = icon_details->width;
339  }
340  }
341  }
342  }
343  else if (lineidx == i) {
344  // Update G_ux.tmp_element.text value for the line we want to display:
345  SPRINTF(
346  G_ux.layout_paging.line_buffer,
347  "%.*s",
348  // avoid overflow
349  (unsigned int) (MIN(sizeof(G_ux.layout_paging.line_buffer) - 1, length)),
350  txt);
351  G_ux.tmp_element.text = G_ux.layout_paging.line_buffer;
352  // Don't forget to add Baseline for characters:
353  y_coord += FONT_BASELINE;
354  }
355  if (i < lineidx) {
356  y_coord += height;
357  }
358  used_height += height;
359  ++nb_lines;
360 #if (BAGL_HEIGHT == 64)
361  // Vertical margin depending on the number of lines displayed:
362  y_margin /= 2; // y_margin values will be 26,13,6,3
363 #endif //(BAGL_HEIGHT==64)
364  }
365  // Y will depend on the number of lines displayed.
366 #if (BAGL_HEIGHT == 64)
367  y_coord += y_margin;
368  used_height += 2 * y_margin;
369 #endif //(BAGL_HEIGHT==64)
370  // Vertical padding (vertical space between each element) is:
371  // vertical_padding = (BAGL_HEIGHT - used_height) / (nb_lines + 1);
372  // So we can compute Y coordinate which will be:
373  y_coord += (((lineidx + 1) * (BAGL_HEIGHT - used_height)) + (nb_lines + 1) / 2)
374  / (nb_lines + 1);
375  // NB: previous computations provides almost same values than original
376  // ones, but we can replace them with manually fixed values if necessary.
377  G_ux.tmp_element.component.x = x_coord;
378  G_ux.tmp_element.component.y = y_coord;
379  break;
380  }
381  }
382  return &G_ux.tmp_element;
383 }
384 
385 // =============================================================================
386 
387 static bool is_loc_word_delim(unsigned char c)
388 {
389  // return !((c >= 'a' && c <= 'z')
390  // || (c >= 'A' && c <= 'Z')
391  // || (c >= '0' && c <= '9'));
392  return c == ' ' || c == '\n' || c == '\b' || c == '\f' || c == '\e' || c == '-' || c == '_';
393 }
394 
395 // return the number of pages to be displayed when current page to show is -1
396 unsigned int ux_layout_pages_compute(const char *text_to_split,
397  unsigned int page_to_display,
398  ux_layout_paging_state_t *paging_state)
399 {
400  bagl_font_id_e font_id;
401 
402  // reset length and offset of lines for this page
403  memset(paging_state->offsets, 0, sizeof(paging_state->offsets));
404  memset(paging_state->lengths, 0, sizeof(paging_state->lengths));
405 
406  // a page has been asked, but no page exists
407  if (page_to_display >= paging_state->count && page_to_display != (unsigned int) -1) {
408  return 0;
409  }
410 
411 #ifndef HAVE_FONTS
412  unsigned char paging_format;
413 #endif // HAVE_FONTS
414  // Regular font by default at the beginning of the first page:
415  unsigned char use_bold_font;
416  if (G_ux.layout_paging.format & PAGING_FORMAT_NB) {
417  use_bold_font = 1;
418  }
419  else {
420  use_bold_font = 0;
421  }
422  // compute offset/length of text of each line for the current page
423  unsigned int page = 0;
424  unsigned int line = 0;
425  const char *start = (text_to_split ? STRPIC(text_to_split) : G_ux.externalText);
426  const char *start2 = start;
427  const char *end = start + strlen(start);
428  unsigned int string_buffer_mark_offset = (unsigned int) -1;
429  unsigned int string_buffer_mark_len = 0;
430  unsigned char string_buffer_id = 0; // By default, 0 => no string buffer
431  unsigned char icon_on_side = 0;
432  unsigned char extra_byte;
433  const char *page_start = start;
434  unsigned char page_use_bold_font = use_bold_font;
435  unsigned int string_buffer_start_page = (unsigned int) -1;
436  paging_state->string_buffer_id = string_buffer_id;
437  while (start < end) {
438  unsigned int len, icon_on_side_len;
439  unsigned int linew;
440  unsigned char c;
441  page_start_loop:
442  // When using UX_LOC_PAGING macro, first line contain the title on all pages
443  if (!line && G_ux.layout_paging.paging_title) {
444  paging_state->fond_ids[0] = BAGL_FONT_OPEN_SANS_REGULAR_11px; // By default
445  paging_state->lengths[0] = 1; // If 0 the line will be skipped
446  line = 1;
447  }
448  len = icon_on_side_len = 0;
449  linew = 0;
450  c = 0;
451 
452  // Skip all spaces characters at the beginning of the line:
453  while (*start == ' ' && (start + 1) < end) {
454  ++start;
455  }
456  // If first character is \b, update bold state and remove it from the string
457  if (*start == '\b') {
458  use_bold_font ^= 1;
459  ++start;
460  }
461  const char *last_word_delim = start;
462  // Initialise font_id at the beginning of each line depending on bold state:
463  if (use_bold_font) {
464  font_id = BAGL_FONT_OPEN_SANS_EXTRABOLD_11px;
465 #ifndef HAVE_FONTS
466  paging_format = PAGING_FORMAT_NB;
467 #endif // HAVE_FONTS
468  }
469  else {
470  font_id = BAGL_FONT_OPEN_SANS_REGULAR_11px;
471 #ifndef HAVE_FONTS
472  paging_format = PAGING_FORMAT_NN;
473 #endif // HAVE_FONTS
474  }
475  // not reached end of content
476  while (start + len < end
477  // line is not full
478  && (icon_on_side == 0 || linew <= BAGL_ICON_TEXT_WIDTH)
479  && linew <= PIXEL_PER_LINE
480  // avoid display buffer overflow for each line
481  && len < (sizeof(G_ux.layout_paging.line_buffer) - 1)) {
482  // ========================
483  // Handle escape sequences:
484  // =========================
485  // Check if this line starts with the escape character \e:
486  if (start[icon_on_side_len] == '\e') {
487  extra_byte = start[icon_on_side_len + 1];
488  // ----------------------------
489  // Special case: string buffer:
490  // ----------------------------
491  // If this is a string buffer, then restart parsing the string buffer:
492  if (extra_byte >= FIRST_STRING_BUFFER && extra_byte <= LAST_STRING_BUFFER) {
493  // Don't allow recursive string buffer!
494  if (string_buffer_id || !get_string_buffer(extra_byte)) {
495  // Ignore this escape sequence
496  if (!icon_on_side_len) {
497  start += 2;
498  }
499  }
500  else {
501  // Store the string buffer mark offset/len from original string:
502  string_buffer_mark_offset = start - start2;
503  string_buffer_mark_len = icon_on_side_len;
504  string_buffer_start_page = page;
505  // If the string just contain \b + the string buffer, switch now!
506  if (string_buffer_mark_offset == 1 && start2[0] == '\b') {
507  string_buffer_mark_offset = 0;
508  }
509  string_buffer_id = extra_byte;
510  // Get String buffer addr:
511  start = get_string_buffer(extra_byte);
512  start2 = start;
513  end = start + strlen(start);
514  }
515  // Just continue parsing starting from the same line, but with new text buffer:
516  goto page_start_loop;
517  }
518  // Currently, escape character use just one additional byte.
519  // If next character is '\n' or '\f' or '\0' we'll remove it and store 2 bytes:
520  // (this mean we was dealing with a full line icon)
521  // -----------------------------
522  // Special case: full line icon:
523  // -----------------------------
524  if (start[icon_on_side_len + 2] == '\n' || start[icon_on_side_len + 2] == '\f'
525  || start[icon_on_side_len + 2] == '\0') {
526  c = start[icon_on_side_len + 2];
527  len = icon_on_side_len + 3;
528  break;
529  }
530  // -----------------------------
531  // Special case: left side icon:
532  // -----------------------------
533  // Otherwise, we continue adding following characters to that string.
534  // Check if line != 0 to restart scanning at line 0 with icon_on_side set!
535  if (!icon_on_side && line) {
536  line = 0;
537  icon_on_side = 1;
538  start = page_start;
539  end = start + strlen(start);
540  // If we switched to a string buffer on this page, reset start2:
541  if (string_buffer_start_page != (unsigned int) -1
542  && string_buffer_start_page == page) {
543  start2 = (text_to_split ? STRPIC(text_to_split) : G_ux.externalText);
544  string_buffer_start_page = (unsigned int) -1;
545  paging_state->string_buffer_id = string_buffer_id = 0;
546  string_buffer_mark_offset = (unsigned int) -1;
547  string_buffer_mark_len = 0;
548  }
549  use_bold_font = page_use_bold_font;
550  // reset length and offset of lines for this page
551  if (page_to_display == page) {
552  memset(paging_state->offsets, 0, sizeof(paging_state->offsets));
553  memset(paging_state->lengths, 0, sizeof(paging_state->lengths));
554  }
555  goto page_start_loop;
556  }
557  icon_on_side = 1;
558  len += 2;
559  // Skip all spaces characters after the icon on side bytes:
560  while (start[len] == ' ') {
561  ++len;
562  }
563  // Be able to check \e after some icon on the side bytes!
564  icon_on_side_len = len;
565  // ---------------------------------------------------------
566  // Special case: string buffer right after a left side icon:
567  // ---------------------------------------------------------
568  continue;
569  // =================================
570  // End of escape sequences handling:
571  // =================================
572  }
573  // compute new line length
574 #ifdef HAVE_FONTS
575  linew = bagl_compute_line_width(font_id, 0, start, len + 1, BAGL_ENCODING_DEFAULT);
576 #else // HAVE_FONTS
577  linew = se_compute_line_width_light(start, len + 1, paging_format);
578 #endif // HAVE_FONTS
579  // if (start[len] )
580  if ((icon_on_side != 0 && linew > BAGL_ICON_TEXT_WIDTH) || linew > PIXEL_PER_LINE) {
581  // we got a full line
582  break;
583  }
584  c = start[len];
585  if (is_loc_word_delim(c)) {
586  last_word_delim = &start[len];
587  }
588  len++;
589  // New line, don't go further
590  if (c == '\n' || c == '\f') {
591  break;
592  }
593  }
594 
595  // if not splitting line onto a word delimiter, then cut at the previous word_delim, adjust
596  // len accordingly (and a word delim has been found already)
597  if (start + len < end && last_word_delim != start && len) {
598  // if line split within a word
599  if ((!is_loc_word_delim(start[len - 1]) && !is_loc_word_delim(start[len]))) {
600  len = last_word_delim - start;
601  }
602  }
603 
604  // Update boldness: parse text and toggle boldness if needed:
605  for (unsigned int i = 0; i < len; i++) {
606  if (start[i] == '\b') {
607  use_bold_font ^= 1;
608  }
609  }
610  // fill up the paging structure
611  if (page_to_display != (unsigned int) -1 && page_to_display == page
612  && page_to_display < paging_state->count) {
613  // If we just switched to a string buffer, keep the offset from original string:
614  if (string_buffer_mark_offset != (unsigned int) -1) {
615  paging_state->offsets[line] = string_buffer_mark_offset;
616  }
617  else {
618  paging_state->offsets[line] = start - start2;
619  }
620  // Store string buffer id if we are not in the initial page or if there
621  // is only the string buffer to display in that page
622  if (string_buffer_start_page != (unsigned int) -1
623  && (string_buffer_start_page < page
624  || (string_buffer_start_page == page && string_buffer_mark_offset == 0))) {
625  paging_state->string_buffer_id = string_buffer_id; // Contain 0xB1, 0xB2, etc
626  }
627  // Remove the '\n' or '\f' from the string (=> no need to handle it anymore!)
628  if (c == '\n' || c == '\f' || c == '\0') {
629  paging_state->lengths[line] = len - 1 + string_buffer_mark_len;
630  }
631  else {
632  paging_state->lengths[line] = len + string_buffer_mark_len;
633  }
634  string_buffer_mark_offset = (unsigned int) -1;
635  string_buffer_mark_len = 0;
636 
637  paging_state->fond_ids[line] = font_id;
638 
639  // won't compute all pages, we reached the one to display
640 #if UX_LAYOUT_PAGES_LINE_COUNT > 1
641  if (line >= UX_LAYOUT_PAGES_LINE_COUNT - 1)
642 #endif // UX_LAYOUT_PAGES_LINE_COUNT
643  {
644  return 1;
645  }
646  }
647 
648  // prepare for next line
649  start += len;
650 
651  // skip to next line/page
652  line++;
653 
654  // Do we want to jump to next page?
655  if (c == '\f'
656  || (
657 #if UX_LAYOUT_PAGES_LINE_COUNT > 1
658  line >= UX_LAYOUT_PAGES_LINE_COUNT &&
659 #endif // UX_LAYOUT_PAGES_LINE_COUNT
660  start < end)) {
661  page++;
662  line = 0;
663  icon_on_side = 0;
664  page_start = start;
665  string_buffer_mark_offset = (unsigned int) -1;
666  string_buffer_mark_len = 0;
667  page_use_bold_font = use_bold_font;
668  }
669  }
670 
671  // return total number of pages detected
672  return page + 1;
673 }
674 
675 // =============================================================================
676 
677 void ux_layout_pages_display_init(const char *text)
678 {
679  // Compute all UX_LOC_PAGE stuff into G_ux.layout_paging
680  G_ux.layout_paging.current = 0;
681  G_ux.layout_paging.count = 1;
682  G_ux.layout_paging.paging_title = 0;
683  G_ux.layout_paging.format = PAGING_FORMAT_NN;
684  // request offsets and lengths of lines for the current page
685  ux_layout_pages_compute(text, G_ux.layout_paging.current, &G_ux.layout_paging);
686 }
687 
688 const bagl_element_t *ux_layout_pages_display_element(const bagl_element_t *element,
689  const char *text)
690 {
691  return ux_layout_pages_prepro_common(element, text);
692 }
693 
694 // =============================================================================
695 
696 // redisplay current page
697 void ux_layout_pages_redisplay_common(unsigned int stack_slot,
698  const char *text,
699  button_push_callback_t button_callback,
701 {
702  ux_stack_slot_t *slot = &G_ux.stack[stack_slot];
703 
704  slot->element_arrays[0].element_array = ux_layout_pages_elements;
705  slot->element_arrays[0].element_array_count = ARRAYLEN(ux_layout_pages_elements);
706  slot->element_arrays_count = 1;
707 
708  // request offsets and lengths of lines for the current page
709  ux_layout_pages_compute(text, G_ux.layout_paging.current, &G_ux.layout_paging);
710 
712  slot->button_push_callback = button_callback;
713  ux_stack_display(stack_slot);
714 }
715 
716 static const bagl_element_t *ux_layout_pages_prepro_by_addr(const bagl_element_t *element)
717 {
718  // don't display if null
719  const void *params = ux_stack_get_current_step_params();
720  if (NULL == params) {
721  return NULL;
722  }
723  const char *text;
724 
725 #if defined(HAVE_INDEXED_STRINGS)
726  text = get_ux_loc_string(((const ux_loc_layout_params_t *) params)->index);
727 #else // defined(HAVE_INDEXED_STRINGS)
728  text = ((const ux_layout_pages_params_t *) params)->text;
729 #endif // defined(HAVE_INDEXED_STRINGS)
730  return ux_layout_pages_prepro_common(element, text);
731 }
732 
733 unsigned int ux_layout_pages_button_callback_by_addr(unsigned int button_mask,
734  unsigned int button_mask_counter);
735 unsigned int ux_loc_layout_paging_button_callback_by_addr(unsigned int button_mask,
736  unsigned int button_mask_counter);
737 
738 void ux_layout_pages_redisplay_by_addr(unsigned int stack_slot)
739 {
740  const char *text;
741  const void *params = ux_stack_get_current_step_params();
742  if (NULL == params) {
743  return;
744  }
745 #if defined(HAVE_INDEXED_STRINGS)
746  text = get_ux_loc_string(((const ux_loc_layout_params_t *) params)->index);
747 #else // defined(HAVE_INDEXED_STRINGS)
748  text = ((const ux_layout_pages_params_t *) params)->text;
749 #endif // defined(HAVE_INDEXED_STRINGS)
750  ux_layout_pages_redisplay_common(
751  stack_slot, text, ux_layout_pages_button_callback_by_addr, ux_layout_pages_prepro_by_addr);
752 }
753 
754 static const bagl_element_t *ux_loc_layout_paging_prepro_by_addr(const bagl_element_t *element)
755 {
756  // don't display if null
757  const void *params = ux_stack_get_current_step_params();
758  if (NULL == params) {
759  return NULL;
760  }
761  const char *text;
762 
763  // As 0 mean it's not a UX_LOC_PAGING (due to all the memset layout_paging)
764  // Store index value + 1
765  G_ux.layout_paging.paging_title = ((const ux_loc_layout_params_t *) params)->index + 1;
766  text = get_ux_loc_string(G_ux.layout_paging.paging_title - 1 + 1);
767  return ux_layout_pages_prepro_common(element, text);
768 }
769 
770 void ux_loc_layout_paging_redisplay_by_addr(unsigned int stack_slot)
771 {
772  const char *text;
773  const void *params = ux_stack_get_current_step_params();
774  if (NULL == params) {
775  return;
776  }
777  // As 0 mean it's not a UX_LOC_PAGING (due to all the memset layout_paging)
778  // Store index value + 1
779  G_ux.layout_paging.paging_title = ((const ux_loc_layout_params_t *) params)->index + 1;
780  text = get_ux_loc_string(G_ux.layout_paging.paging_title - 1 + 1);
781  ux_layout_pages_redisplay_common(stack_slot,
782  text,
783  ux_loc_layout_paging_button_callback_by_addr,
784  ux_loc_layout_paging_prepro_by_addr);
785 }
786 
787 // Function located in ux_layout_paging.c
788 unsigned int ux_layout_paging_button_callback_common(unsigned int button_mask,
789  unsigned int button_mask_counter,
790  ux_layout_paging_redisplay_t redisplay);
791 
792 unsigned int ux_loc_layout_paging_button_callback_by_addr(unsigned int button_mask,
793  unsigned int button_mask_counter)
794 {
795  return ux_layout_paging_button_callback_common(
796  button_mask, button_mask_counter, ux_loc_layout_paging_redisplay_by_addr);
797 }
798 
799 unsigned int ux_layout_pages_button_callback_by_addr(unsigned int button_mask,
800  unsigned int button_mask_counter)
801 {
802  return ux_layout_paging_button_callback_common(
803  button_mask, button_mask_counter, ux_layout_pages_redisplay_by_addr);
804 }
805 
806 void ux_layout_pages_init(unsigned int stack_slot)
807 {
808  const char *text;
809  const void *params = ux_stack_get_step_params(stack_slot);
810 
811  G_ux.layout_paging.format = PAGING_FORMAT_NN; // By default
812  G_ux.layout_paging.paging_title = 0; // 0 mean it's not a UX_LOC_PAGING
813 #if defined(HAVE_INDEXED_STRINGS)
814  text = get_ux_loc_string(((const ux_loc_layout_params_t *) params)->index);
815 #else // defined(HAVE_INDEXED_STRINGS)
816  text = ((const ux_layout_pages_params_t *) params)->text;
817 #endif // defined(HAVE_INDEXED_STRINGS)
818  ux_layout_pages_init_common(stack_slot, text, ux_layout_pages_redisplay_by_addr);
819 }
820 
821 void ux_loc_layout_pages_init(unsigned int stack_slot)
822 {
823  ux_layout_pages_init(stack_slot);
824 }
825 
826 void ux_loc_layout_paging_init(unsigned int stack_slot)
827 {
828  const char *text;
829  const void *params = ux_stack_get_step_params(stack_slot);
830 
831  G_ux.layout_paging.format = PAGING_FORMAT_NN; // By default
832  // As 0 mean it's not a UX_LOC_PAGING (due to all the memset layout_paging)
833  // Store index value + 1
834  G_ux.layout_paging.paging_title = ((const ux_loc_layout_params_t *) params)->index + 1;
835  text = get_ux_loc_string(G_ux.layout_paging.paging_title - 1 + 1);
836  ux_layout_pages_init_common(stack_slot, text, ux_loc_layout_paging_redisplay_by_addr);
837 }
838 
839 #endif // (defined HAVE_BOLOS || defined(BUILD_PNG))
840 
841 void ux_layout_pages_init_common(unsigned int stack_slot,
842  const char *text,
843  ux_layout_paging_redisplay_t redisplay)
844 {
845  // At this very moment, we don't want to get rid of the format nor paging_title, so
846  // keep the one which has just been set (in case of direction backward or forward).
847  unsigned int backup_format = G_ux.layout_paging.format;
848  unsigned int backup_title = G_ux.layout_paging.paging_title;
849 
850  // depending flow browsing direction, select the correct page to display
851  switch (ux_flow_direction()) {
853  ux_layout_paging_reset();
854  // ask the paging to start at the last page.
855  // This step must be performed after the 'ux_layout_paging_reset' call,
856  // thus we cannot mutualize the call with the one in the 'forward' case.
857  G_ux.layout_paging.current = (unsigned int) -1;
858  break;
860  // open the first page
861  ux_layout_paging_reset();
862  break;
864  // shall already be at the first page
865  break;
866  }
867 
868  G_ux.layout_paging.format = backup_format;
869  G_ux.layout_paging.paging_title = backup_title;
870 
871  // store params
872  ux_stack_init(stack_slot);
873 
874  // compute number of chars to display from the params complete string
875  if ((text == NULL) && (G_ux.externalText == NULL)) {
876  text = ""; // empty string to avoid disrupting the ux flow.
877  }
878 
879  // count total number of pages
880  G_ux.layout_paging.count = ux_layout_pages_compute(
881  text, (unsigned int) -1, &G_ux.layout_paging); // at least one page
882 
883  // PRINTF("%d pages needed for =>%s<=\n", G_ux.layout_paging.count, text);
884  if (G_ux.layout_paging.count == 0) {
885  ux_layout_paging_reset();
886  }
887 
888  // if (start != end) {
889  // ux_layout_paging_reset();
890  // }
891 
892  // perform displaying the last page as requested (-1UL in prevstep hook does this)
893  if (G_ux.layout_paging.count && G_ux.layout_paging.current > (G_ux.layout_paging.count - 1)) {
894  G_ux.layout_paging.current = G_ux.layout_paging.count - 1;
895  }
896 
897  redisplay(stack_slot);
898 }
899 
900 #endif // HAVE_BAGL
901 #endif // defined(HAVE_INDEXED_STRINGS)
902 #endif // HAVE_UX_FLOW
#define BAGL_ENCODING_DEFAULT
Definition: nbgl_fonts.h:100
const char * get_ux_loc_string(uint32_t index)
#define MIN(x, y)
Definition: nbgl_types.h:79
const char * text
Definition: ux_bagl.h:68
bagl_component_t component
Definition: ux_bagl.h:58
unsigned char element_arrays_count
Definition: ux_bagl.h:279
button_push_callback_t button_push_callback
Definition: ux_bagl.h:292
struct ux_stack_slot_s::@44 element_arrays[UX_STACK_SLOT_ARRAY_COUNT]
bagl_element_callback_t screen_before_element_display_callback
Definition: ux_bagl.h:291
const bagl_element_t * element_array
Definition: ux_bagl.h:283
unsigned char element_array_count
Definition: ux_bagl.h:284
const bagl_element_t *(* bagl_element_callback_t)(const bagl_element_t *element)
Definition: ux_bagl.h:54
#define G_ux
Definition: ux_bagl.h:345
void ux_stack_display(unsigned int stack_slot)
Definition: ux_stack.c:331
void ux_stack_init(unsigned int stack_slot)
Definition: ux_stack.c:171
unsigned int(* button_push_callback_t)(unsigned int button_mask, unsigned int button_mask_counter)
Definition: ux_bagl.h:142
@ FLOW_DIRECTION_BACKWARD
@ FLOW_DIRECTION_FORWARD
@ FLOW_DIRECTION_START
ux_flow_direction_t ux_flow_direction(void)
unsigned int ux_flow_is_first(void)
const void * ux_stack_get_step_params(unsigned int stack_slot)
const void * ux_stack_get_current_step_params(void)
unsigned int ux_flow_is_last(void)
#define STRPIC(x)
void ux_layout_pages_init(unsigned int stack_slot)
void ux_loc_layout_paging_init(unsigned int stack_slot)
void ux_loc_layout_pages_init(unsigned int stack_slot)