module fortplot_pdf_text_escape !! PDF text escaping and symbol mapping utilities implicit none private public :: escape_pdf_string public :: unicode_to_symbol_char public :: unicode_codepoint_to_pdf_escape contains subroutine escape_pdf_string(input, output, output_len) !! Escape special characters in PDF strings character(len=*), intent(in) :: input character(len=*), intent(out) :: output integer, intent(out) :: output_len integer :: i, j, n character :: ch n = len(input) j = 0 do i = 1, n ch = input(i:i) if (ch == '(' .or. ch == ')' .or. ch == '\\') then j = j + 1 if (j <= len(output)) output(j:j) = '\\' j = j + 1 if (j <= len(output)) output(j:j) = ch else j = j + 1 if (j <= len(output)) output(j:j) = ch end if end do output_len = j if (j < len(output)) output(j+1:) = ' ' end subroutine escape_pdf_string subroutine unicode_to_symbol_char(unicode_codepoint, symbol_char) !! Convert Unicode codepoint to Symbol font character integer, intent(in) :: unicode_codepoint character(len=*), intent(out) :: symbol_char character(len=8) :: esc logical :: found symbol_char = '' esc = '' found = .false. ! Common math symbols supported by Symbol font ! U+221A (square root) maps to octal \214 in Symbol encoding if (unicode_codepoint == 8730) then symbol_char = achar(92)//'214' return end if call lookup_lowercase_greek(unicode_codepoint, esc, found) if (.not. found) call lookup_uppercase_greek(unicode_codepoint, esc, found) if (found) symbol_char = trim(esc) end subroutine unicode_to_symbol_char subroutine unicode_codepoint_to_pdf_escape(codepoint, escape_seq) !! Convert Unicode codepoint to PDF escape sequence integer, intent(in) :: codepoint character(len=*), intent(out) :: escape_seq logical :: found escape_seq = '' found = .false. select case(codepoint) case(176) escape_seq = achar(92)//'260' found = .true. case(177) escape_seq = achar(92)//'261' found = .true. case(178) escape_seq = achar(92)//'262' found = .true. case(179) escape_seq = achar(92)//'263' found = .true. case(181) escape_seq = achar(92)//'265' found = .true. case(183) escape_seq = achar(92)//'267' found = .true. case(185) escape_seq = achar(92)//'271' found = .true. case(215) escape_seq = achar(92)//'327' found = .true. case(247) escape_seq = achar(92)//'367' found = .true. end select if (.not. found) then call lookup_lowercase_greek(codepoint, escape_seq, found) end if if (.not. found) then call lookup_uppercase_greek(codepoint, escape_seq, found) end if if (.not. found) escape_seq = '' end subroutine unicode_codepoint_to_pdf_escape subroutine lookup_lowercase_greek(codepoint, escape_seq, found) integer, intent(in) :: codepoint character(len=*), intent(out) :: escape_seq logical, intent(out) :: found found = .true. select case(codepoint) case(945) escape_seq = achar(92)//'141' case(946) escape_seq = achar(92)//'142' case(947) escape_seq = achar(92)//'147' case(948) escape_seq = achar(92)//'144' case(949) escape_seq = achar(92)//'145' case(950) escape_seq = achar(92)//'172' case(951) escape_seq = achar(92)//'150' case(952) escape_seq = achar(92)//'161' case(953) escape_seq = achar(92)//'151' case(954) escape_seq = achar(92)//'153' case(955) escape_seq = achar(92)//'154' case(956) escape_seq = achar(92)//'155' case(957) escape_seq = achar(92)//'156' case(958) escape_seq = achar(92)//'170' case(959) escape_seq = achar(92)//'157' case(960) escape_seq = achar(92)//'160' case(961) escape_seq = achar(92)//'162' case(963) escape_seq = achar(92)//'163' case(964) escape_seq = achar(92)//'164' case(965) escape_seq = achar(92)//'165' case(966) escape_seq = achar(92)//'146' case(967) escape_seq = achar(92)//'143' case(968) escape_seq = achar(92)//'171' case(969) escape_seq = achar(92)//'167' case default found = .false. end select end subroutine lookup_lowercase_greek subroutine lookup_uppercase_greek(codepoint, escape_seq, found) integer, intent(in) :: codepoint character(len=*), intent(out) :: escape_seq logical, intent(out) :: found found = .true. select case(codepoint) case(913) escape_seq = achar(92)//'101' case(914) escape_seq = achar(92)//'102' case(915) escape_seq = achar(92)//'107' case(916) escape_seq = achar(92)//'104' case(917) escape_seq = achar(92)//'105' case(918) escape_seq = achar(92)//'132' case(919) escape_seq = achar(92)//'110' case(920) escape_seq = achar(92)//'121' case(921) escape_seq = achar(92)//'111' case(922) escape_seq = achar(92)//'113' case(923) escape_seq = achar(92)//'114' case(924) escape_seq = achar(92)//'115' case(925) escape_seq = achar(92)//'116' case(926) escape_seq = achar(92)//'130' case(927) escape_seq = achar(92)//'117' case(928) escape_seq = achar(92)//'120' case(929) escape_seq = achar(92)//'122' case(931) escape_seq = achar(92)//'123' case(932) escape_seq = achar(92)//'124' case(933) escape_seq = achar(92)//'125' case(934) escape_seq = achar(92)//'106' case(935) escape_seq = achar(92)//'103' case(936) escape_seq = achar(92)//'131' case(937) escape_seq = achar(92)//'127' case default found = .false. end select end subroutine lookup_uppercase_greek end module fortplot_pdf_text_escape