mirror of
https://github.com/google/pebble.git
synced 2025-03-25 04:49:06 +00:00
484 lines
20 KiB
C
484 lines
20 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "clar.h"
|
|
#include "fixtures/load_test_resources.h"
|
|
|
|
#include "applib/fonts/fonts_private.h"
|
|
#include "applib/graphics/framebuffer.h"
|
|
#include "applib/graphics/graphics.h"
|
|
#include "applib/graphics/gtypes.h"
|
|
#include "applib/graphics/text.h"
|
|
#include "applib/graphics/text_resources.h"
|
|
#include "applib/ui/layer.h"
|
|
#include "applib/ui/window_private.h"
|
|
#include "resource/resource_ids.auto.h"
|
|
#include "util/size.h"
|
|
|
|
|
|
// Helper Functions
|
|
////////////////////////////////////
|
|
#include "test_graphics.h"
|
|
#include "8bit/test_framebuffer.h"
|
|
#include "util.h"
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Stubs
|
|
#include "stubs_analytics.h"
|
|
#include "stubs_app_state.h"
|
|
#include "stubs_applib_resource.h"
|
|
#include "stubs_bootbits.h"
|
|
#include "stubs_heap.h"
|
|
#include "stubs_logging.h"
|
|
#include "stubs_memory_layout.h"
|
|
#include "stubs_mutex.h"
|
|
#include "stubs_passert.h"
|
|
#include "stubs_pbl_malloc.h"
|
|
#include "stubs_pebble_tasks.h"
|
|
#include "stubs_print.h"
|
|
#include "stubs_prompt.h"
|
|
#include "stubs_serial.h"
|
|
#include "stubs_sleep.h"
|
|
#include "stubs_syscall_internal.h"
|
|
#include "stubs_syscalls.h"
|
|
#include "stubs_system_reset.h"
|
|
#include "stubs_task_watchdog.h"
|
|
#include "stubs_ui_window.h"
|
|
#include "stubs_unobstructed_area.h"
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Tests
|
|
|
|
static FrameBuffer *fb = NULL;
|
|
static GContext ctx;
|
|
|
|
#define NUM_STEPS (5)
|
|
#define DELTA 20
|
|
static FontInfo s_font_info;
|
|
static GBitmap *s_dest_bitmap;
|
|
static char *s_text = "A B C D E F G "
|
|
"H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o "
|
|
"p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R"
|
|
"S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z";
|
|
|
|
void prv_prepare_fb_steps_xy(GSize size, int16_t steps_x, int16_t steps_y) {
|
|
gbitmap_destroy(s_dest_bitmap);
|
|
s_dest_bitmap = gbitmap_create_blank(GSize(size.w * steps_x, size.h * steps_y),
|
|
GBITMAP_NATIVE_FORMAT);
|
|
ctx.dest_bitmap = *s_dest_bitmap;
|
|
ctx.draw_state.clip_box = (GRect){.size = size};
|
|
ctx.draw_state.drawing_box = ctx.draw_state.clip_box;
|
|
graphics_context_set_text_color(&ctx, GColorBlack);
|
|
graphics_context_set_fill_color(&ctx, GColorLightGray);
|
|
memset(s_dest_bitmap->addr, 0xff, s_dest_bitmap->row_size_bytes * s_dest_bitmap->bounds.size.h);
|
|
}
|
|
|
|
void prv_prepare_fb_steps(GSize size) {
|
|
prv_prepare_fb_steps_xy(size, NUM_STEPS, NUM_STEPS);
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__initialize(void) {
|
|
fb = malloc(sizeof(FrameBuffer));
|
|
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
|
|
ctx = (GContext){};
|
|
|
|
// Setup resources
|
|
fake_spi_flash_init(0, 0x1000000);
|
|
pfs_init(false);
|
|
pfs_format(true /* write erase headers */);
|
|
load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME, false /* is_next */);
|
|
|
|
resource_init();
|
|
|
|
memset(&s_font_info, 0, sizeof(s_font_info));
|
|
|
|
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_18_BOLD, 0, &s_font_info));
|
|
|
|
test_graphics_context_init(&ctx, fb);
|
|
|
|
setup_test_aa_sw(&ctx, fb, ctx.dest_bitmap.bounds, ctx.dest_bitmap.bounds, false, 1);
|
|
|
|
prv_prepare_fb_steps(GSize(DISP_COLS, DISP_ROWS));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__cleanup(void) {
|
|
free(fb);
|
|
gbitmap_destroy(s_dest_bitmap);
|
|
s_dest_bitmap = NULL;
|
|
}
|
|
|
|
|
|
#define RECT_TEXT_0_0 GRect(0, 0, DISP_COLS, DISP_ROWS)
|
|
GRangeHorizontal perimeter_for_display_round(const GPerimeter *perimeter,
|
|
const GSize *ctx_size,
|
|
GRangeVertical vertical_range,
|
|
uint16_t inset);
|
|
|
|
static uint8_t *prv_bitmap_offset_for_steps(GBitmap *bmp, int sx, int sy,
|
|
int steps_x, int steps_y) {
|
|
sx += (steps_x - 1) / 2;
|
|
sy += (steps_y - 1) / 2;
|
|
|
|
int16_t step_w = bmp->bounds.size.w / steps_x;
|
|
int16_t step_h = bmp->bounds.size.h / steps_y;
|
|
|
|
return ((uint8_t *)bmp->addr) + (sy * step_h * bmp->row_size_bytes) + (sx * step_w);
|
|
}
|
|
|
|
typedef enum {
|
|
RenderMoveTextBox,
|
|
RenderMoveDrawBox,
|
|
} RenderMoveMode;
|
|
|
|
void render_steps(TextLayoutExtended *layout, RenderMoveMode mode, int delta, int16_t height,
|
|
char **texts) {
|
|
const GTextLayoutCacheRef layout_cache = (GTextLayoutCacheRef const)layout;
|
|
|
|
int steps_x = ctx.dest_bitmap.bounds.size.w / ctx.draw_state.clip_box.size.w;
|
|
int steps_y = ctx.dest_bitmap.bounds.size.h / ctx.draw_state.clip_box.size.h;
|
|
|
|
int text_idx = 0;
|
|
for (int sx = -((steps_x - 1) / 2) ; sx <= steps_x / 2; sx++) {
|
|
for (int sy = -((steps_y - 1) / 2); sy <= steps_y / 2; sy++) {
|
|
// as draw_text internally uses absolute coordinates to derive its state we cannot
|
|
// simply adjust the draw_box to accomplish a side-by-side comparison
|
|
ctx.dest_bitmap.addr = prv_bitmap_offset_for_steps(s_dest_bitmap, sx, sy, steps_x, steps_y);
|
|
|
|
GRect box = {.size = GSize(DISP_COLS, height)};
|
|
const GPoint origin = GPoint(delta * sx, delta * sy);
|
|
if (mode == RenderMoveTextBox) {
|
|
box.origin = origin;
|
|
ctx.draw_state.drawing_box.origin = GPointZero;
|
|
} else {
|
|
ctx.draw_state.drawing_box.origin = origin;
|
|
}
|
|
|
|
graphics_fill_rect(&ctx, &box);
|
|
graphics_draw_rect(&ctx, &(GRect){.origin = GPoint(-ctx.draw_state.drawing_box.origin.x,
|
|
-ctx.draw_state.drawing_box.origin.y),
|
|
.size = ctx.draw_state.clip_box.size});
|
|
|
|
char *text = texts ? texts[text_idx++] : s_text;
|
|
graphics_draw_text(&ctx, text, &s_font_info, box,
|
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, layout_cache);
|
|
}
|
|
}
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__flow_no_paging(void) {
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
},
|
|
};
|
|
render_steps(&layout, RenderMoveTextBox, DELTA, DISP_ROWS, NULL);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__flow_no_paging_draw_box(void) {
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
},
|
|
};
|
|
render_steps(&layout, RenderMoveDrawBox, DELTA, DISP_ROWS, NULL);
|
|
// should result in the very same output as if you did a placement via text box
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, "test_graphics_draw_text_flow__flow_no_paging.pbi"));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__with_origin_zero(void) {
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
.paging.page_on_screen.size_h = DISP_ROWS, // setting a page height != enables positioning
|
|
.paging.origin_on_screen = {0, 0},
|
|
},
|
|
};
|
|
render_steps(&layout, RenderMoveTextBox, DELTA, DISP_ROWS, NULL);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__with_origin_non_zero(void) {
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
.paging.page_on_screen.size_h = DISP_ROWS, // setting a page height != enables positioning
|
|
.paging.origin_on_screen = {DELTA, 2 * DELTA},
|
|
},
|
|
};
|
|
render_steps(&layout, RenderMoveTextBox, DELTA, DISP_ROWS, NULL);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__with_paging(void) {
|
|
prv_prepare_fb_steps(GSize(DISP_COLS, 2 * DISP_ROWS));
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
.paging.page_on_screen = {
|
|
.origin_y = 25,
|
|
.size_h = 100
|
|
}, // setting a page height != enables positioning
|
|
},
|
|
};
|
|
render_steps(&layout, RenderMoveTextBox, DELTA, 1000, NULL);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__avoid_repeat_text_to_avoid_orphans(void) {
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
.paging.page_on_screen = {
|
|
.origin_y = 25,
|
|
.size_h = 100
|
|
}, // setting a page height != enables positioning
|
|
},
|
|
};
|
|
|
|
char first_page_one_line[] = "A B C D E F G H I";
|
|
char second_page_one_line[] = "A B C D E F G H I J K L M N";
|
|
char second_page_two_lines[] = "A B C D E F G H I J K L M N O P Q R S T U V";
|
|
char second_page_full[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
|
|
"a b c d e f g h j k l m n o p q r s t u v w x y z "
|
|
"A";
|
|
char third_page_one_line[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
|
|
"a b c d e f g h j k l m n o p q r s t u v w x y z "
|
|
"A B C D E F G";
|
|
char third_page_two_lines[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
|
|
"a b c d e f g h j k l m n o p q r s t u v w x y z "
|
|
"A B C D E F G I J K L M N O P";
|
|
char third_page_full[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
|
|
"a b c d e f g h j k l m n o p q r s t u v w x y z "
|
|
"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
|
|
"a b c d e f g h j k l m n o p q r s t u";
|
|
char fourth_page_one_line[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
|
|
"a b c d e f g h j k l m n o p q r s t u v w x y z "
|
|
"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
|
|
"a b c d e f g h j k l m n o p q r s t u v w x y z";
|
|
char *texts[] = {
|
|
first_page_one_line,
|
|
second_page_one_line, second_page_two_lines, second_page_full,
|
|
third_page_one_line, third_page_two_lines, third_page_full, fourth_page_one_line,
|
|
};
|
|
|
|
const int16_t num_steps = ARRAY_LENGTH(texts);
|
|
const GSize size = GSize(144, 300);
|
|
|
|
prv_prepare_fb_steps_xy(size, num_steps, 1);
|
|
ctx.draw_state.avoid_text_orphans = true;
|
|
|
|
render_steps(&layout, RenderMoveDrawBox, 0, size.h, texts);
|
|
|
|
// draw markers to visualize page breaks
|
|
ctx.draw_state.clip_box = s_dest_bitmap->bounds;
|
|
ctx.draw_state.drawing_box = s_dest_bitmap->bounds;
|
|
ctx.dest_bitmap.addr = s_dest_bitmap->addr;
|
|
graphics_context_set_stroke_color(&ctx, GColorDarkGray);
|
|
int16_t y = layout.flow_data.paging.page_on_screen.origin_y;
|
|
while (y < size.h) {
|
|
graphics_draw_line(&ctx, GPoint(0, y), GPoint(s_dest_bitmap->bounds.size.w, y));
|
|
y += layout.flow_data.paging.page_on_screen.size_h;
|
|
}
|
|
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
GRangeHorizontal perimeter_for_circle(GRangeVertical vertical_range, GPoint center, int32_t radius);
|
|
GRangeHorizontal perimeter_for_display_rect(const GPerimeter *perimeter,
|
|
const GSize *ctx_size,
|
|
GRangeVertical vertical_range,
|
|
uint16_t inset);
|
|
|
|
// easiest way to make these dimensions identical to spalding although the tests take
|
|
// defaults from basalt's screen resolution. The original `perimeter_for_display_round`
|
|
// uses the platform-specific `DISP_FRAME`
|
|
static GRangeHorizontal prv_perimeter_for_display_round(const GPerimeter *perimeter,
|
|
const GSize *ctx_size,
|
|
GRangeVertical vertical_range,
|
|
uint16_t inset) {
|
|
const GRect disp_180_frame = GRect(0, 0, 180, 180);
|
|
const GPoint center = grect_center_point(&disp_180_frame);
|
|
const int32_t radius = grect_shortest_side(disp_180_frame) / 2 - inset;
|
|
return perimeter_for_circle(vertical_range, center, radius);
|
|
}
|
|
|
|
|
|
void test_graphics_draw_text_flow__draw_text_doom(void) {
|
|
// text and configuration we see in text_flow demo app
|
|
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_24_BOLD, 0, &s_font_info));
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback = prv_perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
.paging.page_on_screen = {
|
|
.origin_y = 48,
|
|
.size_h = 85
|
|
},
|
|
.paging.origin_on_screen.y = 412,
|
|
},
|
|
};
|
|
char text[] = "Dib: You're just jealous...\nZim: This has nothing to do with jelly!\n"
|
|
"Zim: You dare agree with me? Prepare to meet your horrible doom!";
|
|
|
|
prv_prepare_fb_steps_xy(GSize(180, 300), 1, 1);
|
|
ctx.draw_state.avoid_text_orphans = true;
|
|
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
|
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
|
|
(GTextAttributes *const) &layout);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
|
|
prv_prepare_fb_steps_xy(GSize(180, 300), 1, 1);
|
|
ctx.draw_state.avoid_text_orphans = true;
|
|
ctx.draw_state.clip_box.origin.y = 48;
|
|
ctx.draw_state.clip_box.size.h = 85;
|
|
ctx.draw_state.drawing_box.origin.y = -183;
|
|
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
|
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
|
|
(GTextAttributes *const) &layout);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, namecat(namecat(__func__, "__clipped"), ".pbi")));
|
|
|
|
prv_prepare_fb_steps_xy(GSize(180, 300), 1, 1);
|
|
ctx.draw_state.avoid_text_orphans = false;
|
|
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
|
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
|
|
(GTextAttributes *const) &layout);
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, namecat(namecat(__func__, "__with_orphan"), ".pbi")));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__max_used_size_draw_text_doom(void) {
|
|
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_24_BOLD, 0, &s_font_info));
|
|
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback = prv_perimeter_for_display_round},
|
|
.perimeter.inset = 8,
|
|
.paging.page_on_screen = {
|
|
.origin_y = 48,
|
|
.size_h = 85
|
|
},
|
|
.paging.origin_on_screen.y = 412,
|
|
},
|
|
};
|
|
void *layout_ref = (void *)&layout;
|
|
|
|
char text[] = "Dib: You're just jealous...\nZim: This has nothing to do with jelly!\n"
|
|
"Zim: You dare agree with me? Prepare to meet your horrible doom!";
|
|
|
|
const GFont font = &s_font_info;
|
|
const GRect box = GRect(0, 0, 180, 1000);
|
|
const GTextOverflowMode overflow_mode = GTextOverflowModeTrailingEllipsis;
|
|
const GTextAlignment text_alignment = GTextAlignmentCenter;
|
|
const GSize fb_size = GSize(180, 300);
|
|
const int16_t steps_x = 1;
|
|
const int16_t steps_y = 1;
|
|
|
|
prv_prepare_fb_steps_xy(fb_size, steps_x, steps_y);
|
|
ctx.draw_state.avoid_text_orphans = true;
|
|
|
|
const GSize size_with_orphan_avoidance = graphics_text_layout_get_max_used_size(&ctx,
|
|
text, font, box,
|
|
overflow_mode,
|
|
text_alignment,
|
|
layout_ref);
|
|
|
|
// TODO: PBL-34191 move .avoid_text_orphans from GContext to TextLayout so layout is invalidated
|
|
// Invalidate the layout so it will be recalculated for the next step
|
|
layout.hash = 0;
|
|
|
|
prv_prepare_fb_steps_xy(fb_size, steps_x, steps_y);
|
|
ctx.draw_state.avoid_text_orphans = false;
|
|
|
|
const GSize size_without_orphan_avoidance = graphics_text_layout_get_max_used_size(&ctx,
|
|
text, font,
|
|
box,
|
|
overflow_mode,
|
|
text_alignment,
|
|
layout_ref);
|
|
|
|
// We should get different heights because the orphan avoidance algorithm adds an extra line
|
|
cl_assert_equal_i(size_with_orphan_avoidance.h, 279);
|
|
cl_assert_equal_i(size_without_orphan_avoidance.h, 255);
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__no_infinite_loop(void) {
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback = perimeter_for_display_rect},
|
|
},
|
|
};
|
|
char text[] = "Prevent orhpans for tall-enough pages.";
|
|
const int16_t line_height = 22;
|
|
// some more pixels to show that orphan prevention really only applies if there's enough space
|
|
// for enough *full* lines
|
|
const int16_t some = 5;
|
|
prv_prepare_fb_steps_xy(GSize(180, 300), 3, 1);
|
|
ctx.draw_state.avoid_text_orphans = true;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
const int number_of_lines_per_page = i + 1;
|
|
layout.flow_data.paging.page_on_screen.size_h = number_of_lines_per_page * line_height + some;
|
|
layout.flow_data.paging.origin_on_screen.y =
|
|
layout.flow_data.paging.page_on_screen.size_h - line_height;
|
|
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
|
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
|
|
(GTextAttributes *const) &layout);
|
|
|
|
const int16_t second_page_start_y =
|
|
(layout.flow_data.paging.page_on_screen.size_h - layout.flow_data.paging.origin_on_screen.y);
|
|
const int16_t second_page_end_y =
|
|
(second_page_start_y + layout.flow_data.paging.page_on_screen.size_h);
|
|
graphics_draw_line(&ctx, GPoint(0, second_page_start_y), GPoint(180, second_page_start_y));
|
|
graphics_draw_line(&ctx, GPoint(0, second_page_end_y), GPoint(180, second_page_end_y));
|
|
ctx.draw_state.drawing_box.origin.x += 180;
|
|
ctx.draw_state.clip_box.origin.x += 180;
|
|
}
|
|
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_text_flow__no_infinite_loop2(void) {
|
|
// replicates the bug described in PBL-29267 noticed in the notification app
|
|
// the following values are those we measured in GDB when it entered the infinite loop
|
|
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_24_BOLD, 0, &s_font_info));
|
|
TextLayoutExtended layout = {
|
|
.flow_data = {
|
|
.perimeter.impl = &(GPerimeter){.callback = prv_perimeter_for_display_round,},
|
|
.perimeter.inset = 8,
|
|
.paging = {
|
|
.origin_on_screen = GPoint(12, 83),
|
|
.page_on_screen.origin_y = 24,
|
|
.page_on_screen.size_h = 140,
|
|
}
|
|
},
|
|
};
|
|
char text[] = "Late again? Can you be on time ever? Seriosly? Dude!!!";
|
|
prv_prepare_fb_steps_xy(GSize(180, 360), 1, 1);
|
|
ctx.draw_state.avoid_text_orphans = true;
|
|
|
|
GRect box = (GRect){.origin = {.x = 12, .y = 59}, .size = {.w = 156, .h = 2480}};
|
|
graphics_draw_text(&ctx, text, &s_font_info, box,
|
|
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
|
|
(GTextAttributes *const) &layout);
|
|
|
|
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
|
|
}
|