module fortplot_ascii_text_elements !! ASCII terminal plotting backend - Text Element Management !! !! This module handles text element creation, storage, and coordinate !! conversion for ASCII canvas rendering. !! !! Author: fortplot contributors use fortplot_ascii_mathtext, only: sanitize_ascii_text use fortplot_ascii_primitives, only: ascii_draw_text_primitive use fortplot_margins, only: plot_area_t use fortplot_ascii_utils, only: text_element_t use, intrinsic :: iso_fortran_env, only: wp => real64 implicit none private public :: add_text_element, store_text_element contains subroutine add_text_element(text_elements, num_text_elements, x, y, text, & current_r, current_g, current_b, & x_min, x_max, y_min, y_max, plot_area, plot_width, plot_height) !! Create and store a text element on the ASCII canvas type(text_element_t), intent(inout) :: text_elements(:) integer, intent(inout) :: num_text_elements real(wp), intent(in) :: x, y character(len=*), intent(in) :: text real(wp), intent(in) :: current_r, current_g, current_b real(wp), intent(in) :: x_min, x_max, y_min, y_max type(plot_area_t), intent(in), optional :: plot_area integer, intent(in) :: plot_width, plot_height integer :: text_x, text_y character(len=500) :: processed_text integer :: processed_len logical :: use_plot_area, screen_coords ! Produce ASCII-safe text: LaTeX -> Unicode -> strip math delimiters, ! simplify mathtext, and transliterate remaining symbols. call sanitize_ascii_text(text, processed_text, processed_len) ! Store text element for later rendering if (num_text_elements < size(text_elements)) then num_text_elements = num_text_elements + 1 use_plot_area = .false. if (present(plot_area)) then use_plot_area = plot_area%width > 0 .and. plot_area%height > 0 end if ! Convert coordinates - check if already in screen coordinates screen_coords = x >= 1.0_wp .and. x <= real(plot_width, wp) .and. & y >= 1.0_wp .and. y <= real(plot_height, wp) if (screen_coords) then ! Already in screen coordinates (e.g., legend, or tick labels ! placed in reserved bands outside the plot rectangle). text_x = nint(x) text_y = nint(y) else if (use_plot_area) then ! Convert from data coordinates to the active plot area. text_x = plot_area%left + 1 + nint((x - x_min)/(x_max - x_min)* & real(max(1, plot_area%width - 2), wp)) text_y = plot_area%bottom + plot_area%height - 1 - nint((y - y_min)/(y_max - y_min)* & real(max(1, plot_area%height - 2), wp)) else ! Convert from data coordinates to canvas coordinates. text_x = nint((x - x_min)/(x_max - x_min)*real(plot_width, wp)) text_y = nint((y_max - y)/(y_max - y_min)*real(plot_height, wp)) end if end if if (screen_coords .and. use_plot_area) then ! Screen-placed text may legitimately sit outside the plot ! rectangle (x tick labels below, secondary y labels to the ! right), so clamp only to the canvas (issue #2066). text_x = max(1, min(text_x, plot_width)) text_y = max(1, min(text_y, plot_height)) else if (use_plot_area) then text_x = max(plot_area%left + 1, & min(text_x, plot_area%left + max(1, plot_area%width) - processed_len - 1)) text_y = max(plot_area%bottom + 1, & min(text_y, plot_area%bottom + max(1, plot_area%height) - 1)) else ! Clamp to canvas bounds. Reserve margin on the right so text ! never touches the border ``|`` glyph (issue #1706). text_x = max(2, min(text_x, max(2, plot_width - processed_len - 1))) text_y = max(1, min(text_y, plot_height)) end if text_elements(num_text_elements)%text = processed_text(1:processed_len) text_elements(num_text_elements)%x = text_x text_elements(num_text_elements)%y = text_y text_elements(num_text_elements)%color_r = current_r text_elements(num_text_elements)%color_g = current_g text_elements(num_text_elements)%color_b = current_b end if end subroutine add_text_element subroutine store_text_element(text_elements, num_text_elements, text_x, text_y, & processed_text, x, y, text, x_min, x_max, y_min, y_max, & plot_area, plot_width, plot_height, current_r, current_g, current_b) !! Store a pre-processed text element for later ASCII rendering type(text_element_t), intent(inout) :: text_elements(:) integer, intent(inout) :: num_text_elements integer, intent(out) :: text_x, text_y character(len=:), allocatable, intent(out) :: processed_text real(wp), intent(in) :: x, y character(len=*), intent(in) :: text real(wp), intent(in) :: x_min, x_max, y_min, y_max type(plot_area_t), intent(in), optional :: plot_area integer, intent(in) :: plot_width, plot_height real(wp), intent(in) :: current_r, current_g, current_b type(plot_area_t) :: effective_plot_area if (present(plot_area)) then effective_plot_area = plot_area else effective_plot_area%left = 1 effective_plot_area%bottom = 1 effective_plot_area%width = plot_width effective_plot_area%height = plot_height end if call ascii_draw_text_primitive(text_x, text_y, processed_text, & x, y, text, x_min, x_max, y_min, y_max, & effective_plot_area, plot_width, plot_height, & current_r, current_g, current_b) num_text_elements = num_text_elements + 1 text_elements(num_text_elements)%text = processed_text text_elements(num_text_elements)%x = text_x text_elements(num_text_elements)%y = text_y text_elements(num_text_elements)%color_r = current_r text_elements(num_text_elements)%color_g = current_g text_elements(num_text_elements)%color_b = current_b end subroutine store_text_element end module fortplot_ascii_text_elements