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 ! U+221E (infinity) maps to octal \245 in Symbol encoding if (unicode_codepoint == 8734) then symbol_char = achar(92)//'245' return end if ! U+2202 (partial differential) maps to octal \266 in Symbol encoding if (unicode_codepoint == 8706) then symbol_char = achar(92)//'266' return end if ! Arrows in Symbol encoding select case (unicode_codepoint) case (8592) symbol_char = achar(92)//'254' return case (8593) symbol_char = achar(92)//'255' return case (8594) symbol_char = achar(92)//'256' return case (8595) symbol_char = achar(92)//'257' return case (8596) symbol_char = achar(92)//'253' return end select 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 (188) escape_seq = achar(92)//'274' found = .true. 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