sucklessConfigs

configurations of the suckless.org software that I use
Log | Files | Refs

icat-mini.sh (19230B)


      1 #!/bin/sh
      2 
      3 # vim: shiftwidth=4
      4 
      5 script_name="$(basename "$0")"
      6 
      7 short_help="Usage: $script_name [OPTIONS] <image_file>
      8 
      9 This is a script to display images in the terminal using the kitty graphics
     10 protocol with Unicode placeholders. It is very basic, please use something else
     11 if you have alternatives.
     12 
     13 Options:
     14   -h                  Show this help.
     15   -s SCALE            The scale of the image, may be floating point.
     16   -c N, --cols N      The number of columns.
     17   -r N, --rows N      The number of rows.
     18   --max-cols N        The maximum number of columns.
     19   --max-rows N        The maximum number of rows.
     20   --cell-size WxH     The cell size in pixels.
     21   -m METHOD           The uploading method, may be 'file', 'direct' or 'auto'.
     22   --speed SPEED       The multiplier for the animation speed (float).
     23 "
     24 
     25 # Exit the script on keyboard interrupt
     26 trap "echo 'icat-mini was interrupted' >&2; exit 1" INT
     27 
     28 cols=""
     29 rows=""
     30 file=""
     31 tty="/dev/tty"
     32 uploading_method="auto"
     33 cell_size=""
     34 scale=1
     35 max_cols=""
     36 max_rows=""
     37 speed=""
     38 
     39 # Parse the command line.
     40 while [ $# -gt 0 ]; do
     41     case "$1" in
     42         -c|--columns|--cols)
     43             cols="$2"
     44             shift 2
     45             ;;
     46         -r|--rows|-l|--lines)
     47             rows="$2"
     48             shift 2
     49             ;;
     50         -s|--scale)
     51             scale="$2"
     52             shift 2
     53             ;;
     54         -h|--help)
     55             echo "$short_help"
     56             exit 0
     57             ;;
     58         -m|--upload-method|--uploading-method)
     59             uploading_method="$2"
     60             shift 2
     61             ;;
     62         --cell-size)
     63             cell_size="$2"
     64             shift 2
     65             ;;
     66         --max-cols)
     67             max_cols="$2"
     68             shift 2
     69             ;;
     70         --max-rows)
     71             max_rows="$2"
     72             shift 2
     73             ;;
     74         --speed)
     75             speed="$2"
     76             shift 2
     77             ;;
     78         --)
     79             file="$2"
     80             shift 2
     81             ;;
     82         -*)
     83             echo "Unknown option: $1" >&2
     84             exit 1
     85             ;;
     86         *)
     87             if [ -n "$file" ]; then
     88                 echo "Multiple image files are not supported: $file and $1" >&2
     89                 exit 1
     90             fi
     91             file="$1"
     92             shift
     93             ;;
     94     esac
     95 done
     96 
     97 file="$(realpath "$file")"
     98 
     99 #####################################################################
    100 # Adjust the terminal state
    101 #####################################################################
    102 
    103 stty_orig="$(stty -g < "$tty")"
    104 stty -echo < "$tty"
    105 # Disable ctrl-z. Pressing ctrl-z during image uploading may cause some
    106 # horrible issues otherwise.
    107 stty susp undef < "$tty"
    108 stty -icanon < "$tty"
    109 
    110 restore_echo() {
    111     [ -n "$stty_orig" ] || return
    112     stty $stty_orig < "$tty"
    113 }
    114 
    115 trap restore_echo EXIT TERM
    116 
    117 #####################################################################
    118 # Detect imagemagick
    119 #####################################################################
    120 
    121 # If there is the 'magick' command, use it instead of separate 'convert' and
    122 # 'identify' commands.
    123 if command -v magick > /dev/null; then
    124     identify="magick identify"
    125     convert="magick"
    126 else
    127     identify="identify"
    128     convert="convert"
    129 fi
    130 
    131 #####################################################################
    132 # Detect tmux
    133 #####################################################################
    134 
    135 # Check if we are inside tmux.
    136 inside_tmux=""
    137 if [ -n "$TMUX" ]; then
    138     case "$TERM" in
    139         *tmux*|*screen*)
    140             inside_tmux=1
    141             ;;
    142     esac
    143 fi
    144 
    145 #####################################################################
    146 # Compute the number of rows and columns
    147 #####################################################################
    148 
    149 is_pos_int() {
    150     if [ -z "$1" ]; then
    151         return 1 # false
    152     fi
    153     if [ -z "$(printf '%s' "$1" | tr -d '[:digit:]')" ]; then
    154         if [ "$1" -gt 0 ]; then
    155             return 0 # true
    156         fi
    157     fi
    158     return 1 # false
    159 }
    160 
    161 if [ -n "$cols" ] || [ -n "$rows" ]; then
    162     if [ -n "$max_cols" ] || [ -n "$max_rows" ]; then
    163         echo "You can't specify both max-cols/rows and cols/rows" >&2
    164         exit 1
    165     fi
    166 fi
    167 
    168 # Get the max number of cols and rows.
    169 [ -n "$max_cols" ] || max_cols="$(tput cols)"
    170 [ -n "$max_rows" ] || max_rows="$(tput lines)"
    171 if [ "$max_rows" -gt 255 ]; then
    172     max_rows=255
    173 fi
    174 
    175 python_ioctl_command="import array, fcntl, termios
    176 buf = array.array('H', [0, 0, 0, 0])
    177 fcntl.ioctl(0, termios.TIOCGWINSZ, buf)
    178 print(int(buf[2]/buf[1]), int(buf[3]/buf[0]))"
    179 
    180 # Get the cell size in pixels if either cols or rows are not specified.
    181 if [ -z "$cols" ] || [ -z "$rows" ]; then
    182     cell_width=""
    183     cell_height=""
    184     # If the cell size is specified, use it.
    185     if [ -n "$cell_size" ]; then
    186         cell_width="${cell_size%x*}"
    187         cell_height="${cell_size#*x}"
    188         if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then
    189             echo "Invalid cell size: $cell_size" >&2
    190             exit 1
    191         fi
    192     fi
    193     # Otherwise try to use TIOCGWINSZ ioctl via python.
    194     if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then
    195         cell_size_ioctl="$(python3 -c "$python_ioctl_command" < "$tty" 2> /dev/null)"
    196         cell_width="${cell_size_ioctl% *}"
    197         cell_height="${cell_size_ioctl#* }"
    198         if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then
    199             cell_width=""
    200             cell_height=""
    201         fi
    202     fi
    203     # If it didn't work, try to use csi XTWINOPS.
    204     if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then
    205         if [ -n "$inside_tmux" ]; then
    206             printf '\ePtmux;\e\e[16t\e\\' >> "$tty"
    207         else
    208             printf '\e[16t' >> "$tty"
    209         fi
    210         # The expected response will look like ^[[6;<height>;<width>t
    211         term_response=""
    212         while true; do
    213             char=$(dd bs=1 count=1 <"$tty" 2>/dev/null)
    214             if [ "$char" = "t" ]; then
    215                 break
    216             fi
    217             term_response="$term_response$char"
    218         done
    219         cell_height="$(printf '%s' "$term_response" | cut -d ';' -f 2)"
    220         cell_width="$(printf '%s' "$term_response" | cut -d ';' -f 3)"
    221         if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then
    222             cell_width=8
    223             cell_height=16
    224         fi
    225     fi
    226 fi
    227 
    228 # Compute a formula with bc and round to the nearest integer.
    229 bc_round() {
    230     LC_NUMERIC=C printf '%.0f' "$(printf '%s\n' "scale=2;($1) + 0.5" | bc)"
    231 }
    232 
    233 # Compute the number of rows and columns of the image.
    234 if [ -z "$cols" ] || [ -z "$rows" ]; then
    235     # Get the size of the image and its resolution. If it's an animation, use
    236     # the first frame.
    237     format_output="$($identify -format '%w %h\n' "$file" | head -1)"
    238     img_width="$(printf '%s' "$format_output" | cut -d ' ' -f 1)"
    239     img_height="$(printf '%s' "$format_output" | cut -d ' ' -f 2)"
    240     if ! is_pos_int "$img_width" || ! is_pos_int "$img_height"; then
    241         echo "Couldn't get image size from identify: $format_output" >&2
    242         echo >&2
    243         exit 1
    244     fi
    245     opt_cols_expr="(${scale}*${img_width}/${cell_width})"
    246     opt_rows_expr="(${scale}*${img_height}/${cell_height})"
    247     if [ -z "$cols" ] && [ -z "$rows" ]; then
    248         # If columns and rows are not specified, compute the optimal values
    249         # using the information about rows and columns per inch.
    250         cols="$(bc_round "$opt_cols_expr")"
    251         rows="$(bc_round "$opt_rows_expr")"
    252         # Make sure that automatically computed rows and columns are within some
    253         # sane limits
    254         if [ "$cols" -gt "$max_cols" ]; then
    255             rows="$(bc_round "$rows * $max_cols / $cols")"
    256             cols="$max_cols"
    257         fi
    258         if [ "$rows" -gt "$max_rows" ]; then
    259             cols="$(bc_round "$cols * $max_rows / $rows")"
    260             rows="$max_rows"
    261         fi
    262     elif [ -z "$cols" ]; then
    263         # If only one dimension is specified, compute the other one to match the
    264         # aspect ratio as close as possible.
    265         cols="$(bc_round "${opt_cols_expr}*${rows}/${opt_rows_expr}")"
    266     elif [ -z "$rows" ]; then
    267         rows="$(bc_round "${opt_rows_expr}*${cols}/${opt_cols_expr}")"
    268     fi
    269 
    270     if [ "$cols" -lt 1 ]; then
    271         cols=1
    272     fi
    273     if [ "$rows" -lt 1 ]; then
    274         rows=1
    275     fi
    276 fi
    277 
    278 #####################################################################
    279 # Generate an image id
    280 #####################################################################
    281 
    282 image_id=""
    283 while [ -z "$image_id" ]; do
    284     image_id="$(shuf -i 16777217-4294967295 -n 1)"
    285     # Check that the id requires 24-bit fg colors.
    286     if [ "$(expr \( "$image_id" / 256 \) % 65536)" -eq 0 ]; then
    287         image_id=""
    288     fi
    289 done
    290 
    291 #####################################################################
    292 # Uploading the image
    293 #####################################################################
    294 
    295 # Choose the uploading method
    296 if [ "$uploading_method" = "auto" ]; then
    297     if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ] || [ -n "$SSH_CONNECTION" ]; then
    298         uploading_method="direct"
    299     else
    300         uploading_method="file"
    301     fi
    302 fi
    303 
    304 # Functions to emit the start and the end of a graphics command.
    305 if [ -n "$inside_tmux" ]; then
    306     # If we are in tmux we have to wrap the command in Ptmux.
    307     graphics_command_start='\ePtmux;\e\e_G'
    308     graphics_command_end='\e\e\\\e\\'
    309 else
    310     graphics_command_start='\e_G'
    311     graphics_command_end='\e\\'
    312 fi
    313 
    314 start_gr_command() {
    315     printf "$graphics_command_start" >> "$tty"
    316 }
    317 end_gr_command() {
    318     printf "$graphics_command_end" >> "$tty"
    319 }
    320 
    321 # Send a graphics command with the correct start and end
    322 gr_command() {
    323     start_gr_command
    324     printf '%s' "$1" >> "$tty"
    325     end_gr_command
    326 }
    327 
    328 # Send an uploading command. Usage: gr_upload <action> <command> <file>
    329 # Where <action> is a part of command that specifies the action, it will be
    330 # repeated for every chunk (if the method is direct), and <command> is the rest
    331 # of the command that specifies the image parameters. <action> and <command>
    332 # must not include the transmission method or ';'.
    333 # Example:
    334 # gr_upload "a=T,q=2" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file"
    335 gr_upload() {
    336     arg_action="$1"
    337     arg_command="$2"
    338     arg_file="$3"
    339     if [ "$uploading_method" = "file" ]; then
    340         # base64-encode the filename
    341         encoded_filename=$(printf '%s' "$arg_file" | base64 -w0)
    342         gr_command "${arg_action},${arg_command},t=f;${encoded_filename}"
    343     fi
    344     if [ "$uploading_method" = "direct" ]; then
    345         # Create a temporary directory to store the chunked image.
    346         chunkdir="$(mktemp -d)"
    347         if [ ! "$chunkdir" ] || [ ! -d "$chunkdir" ]; then
    348             echo "Can't create a temp dir" >&2
    349             exit 1
    350         fi
    351         # base64-encode the file and split it into chunks. The size of each
    352         # graphics command shouldn't be more than 4096, so we set the size of an
    353         # encoded chunk to be 3968, slightly less than that.
    354         chunk_size=3968
    355         cat "$arg_file" | base64 -w0 | split -b "$chunk_size" - "$chunkdir/chunk_"
    356 
    357         # Issue a command indicating that we want to start data transmission for
    358         # a new image.
    359         gr_command "${arg_action},${arg_command},t=d,m=1"
    360 
    361         # Transmit chunks.
    362         for chunk in "$chunkdir/chunk_"*; do
    363             start_gr_command
    364             printf '%s' "${arg_action},i=${image_id},m=1;" >> "$tty"
    365             cat "$chunk" >> "$tty"
    366             end_gr_command
    367             rm "$chunk"
    368         done
    369 
    370         # Tell the terminal that we are done.
    371         gr_command "${arg_action},i=$image_id,m=0"
    372 
    373         # Remove the temporary directory.
    374         rmdir "$chunkdir"
    375     fi
    376 }
    377 
    378 delayed_frame_dir_cleanup() {
    379     arg_frame_dir="$1"
    380     sleep 2
    381     if [ -n "$arg_frame_dir" ]; then
    382         for frame in "$arg_frame_dir"/frame_*.png; do
    383             rm "$frame"
    384         done
    385         rmdir "$arg_frame_dir"
    386     fi
    387 }
    388 
    389 upload_image_and_print_placeholder() {
    390     # Check if the file is an animation.
    391     frame_count=$($identify -format '%n\n' "$file" | head -n 1)
    392     if [ "$frame_count" -gt 1 ]; then
    393         # The file is an animation, decompose into frames and upload each frame.
    394         frame_dir="$(mktemp -d)"
    395         frame_dir="$HOME/temp/frames${frame_dir}"
    396         mkdir -p "$frame_dir"
    397         if [ ! "$frame_dir" ] || [ ! -d "$frame_dir" ]; then
    398             echo "Can't create a temp dir for frames" >&2
    399             exit 1
    400         fi
    401 
    402         # Decompose the animation into separate frames.
    403         $convert "$file" -coalesce "$frame_dir/frame_%06d.png"
    404 
    405         # Get all frame delays at once, in centiseconds, as a space-separated
    406         # string.
    407         delays=$($identify -format "%T " "$file")
    408 
    409         frame_number=1
    410         for frame in "$frame_dir"/frame_*.png; do
    411             # Read the delay for the current frame and convert it from
    412             # centiseconds to milliseconds.
    413             delay=$(printf '%s' "$delays" | cut -d ' ' -f "$frame_number")
    414             delay=$((delay * 10))
    415             # If the delay is 0, set it to 100ms.
    416             if [ "$delay" -eq 0 ]; then
    417                 delay=100
    418             fi
    419 
    420             if [ -n "$speed" ]; then
    421                 delay=$(bc_round "$delay / $speed")
    422             fi
    423 
    424             if [ "$frame_number" -eq 1 ]; then
    425                 # Upload the first frame with a=T
    426                 gr_upload "q=2,a=T" "f=100,U=1,i=${image_id},c=${cols},r=${rows}" "$frame"
    427                 # Set the delay for the first frame and also play the animation
    428                 # in loading mode (s=2).
    429                 gr_command "a=a,v=1,s=2,r=${frame_number},z=${delay},i=${image_id}"
    430                 # Print the placeholder after the first frame to reduce the wait
    431                 # time.
    432                 print_placeholder
    433             else
    434                 # Upload subsequent frames with a=f
    435                 gr_upload "q=2,a=f" "f=100,i=${image_id},z=${delay}" "$frame"
    436             fi
    437 
    438             frame_number=$((frame_number + 1))
    439         done
    440 
    441         # Play the animation in loop mode (s=3).
    442         gr_command "a=a,v=1,s=3,i=${image_id}"
    443 
    444         # Remove the temporary directory, but do it in the background with a
    445         # delay to avoid removing files before they are loaded by the terminal.
    446         delayed_frame_dir_cleanup "$frame_dir" 2> /dev/null &
    447     else
    448         # The file is not an animation, upload it directly
    449         gr_upload "q=2,a=T" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file"
    450         # Print the placeholder
    451         print_placeholder
    452     fi
    453 }
    454 
    455 #####################################################################
    456 # Printing the image placeholder
    457 #####################################################################
    458 
    459 print_placeholder() {
    460     # Each line starts with the escape sequence to set the foreground color to
    461     # the image id.
    462     blue="$(expr "$image_id" % 256 )"
    463     green="$(expr \( "$image_id" / 256 \) % 256 )"
    464     red="$(expr \( "$image_id" / 65536 \) % 256 )"
    465     line_start="$(printf "\e[38;2;%d;%d;%dm" "$red" "$green" "$blue")"
    466     line_end="$(printf "\e[39;m")"
    467 
    468     id4th="$(expr \( "$image_id" / 16777216 \) % 256 )"
    469     eval "id_diacritic=\$d${id4th}"
    470 
    471     # Reset the brush state, mostly to reset the underline color.
    472     printf "\e[0m"
    473 
    474     # Fill the output with characters representing the image
    475     for y in $(seq 0 "$(expr "$rows" - 1)"); do
    476         eval "row_diacritic=\$d${y}"
    477         printf '%s' "$line_start"
    478         for x in $(seq 0 "$(expr "$cols" - 1)"); do
    479             eval "col_diacritic=\$d${x}"
    480             # Note that when $x is out of bounds, the column diacritic will
    481             # be empty, meaning that the column should be guessed by the
    482             # terminal.
    483             if [ "$x" -ge "$num_diacritics" ]; then
    484                 printf '%s' "${placeholder}${row_diacritic}"
    485             else
    486                 printf '%s' "${placeholder}${row_diacritic}${col_diacritic}${id_diacritic}"
    487             fi
    488         done
    489         printf '%s\n' "$line_end"
    490     done
    491 
    492     printf "\e[0m"
    493 }
    494 
    495 d0="̅"
    496 d1="̍"
    497 d2="̎"
    498 d3="̐"
    499 d4="̒"
    500 d5="̽"
    501 d6="̾"
    502 d7="̿"
    503 d8="͆"
    504 d9="͊"
    505 d10="͋"
    506 d11="͌"
    507 d12="͐"
    508 d13="͑"
    509 d14="͒"
    510 d15="͗"
    511 d16="͛"
    512 d17="ͣ"
    513 d18="ͤ"
    514 d19="ͥ"
    515 d20="ͦ"
    516 d21="ͧ"
    517 d22="ͨ"
    518 d23="ͩ"
    519 d24="ͪ"
    520 d25="ͫ"
    521 d26="ͬ"
    522 d27="ͭ"
    523 d28="ͮ"
    524 d29="ͯ"
    525 d30="҃"
    526 d31="҄"
    527 d32="҅"
    528 d33="҆"
    529 d34="҇"
    530 d35="֒"
    531 d36="֓"
    532 d37="֔"
    533 d38="֕"
    534 d39="֗"
    535 d40="֘"
    536 d41="֙"
    537 d42="֜"
    538 d43="֝"
    539 d44="֞"
    540 d45="֟"
    541 d46="֠"
    542 d47="֡"
    543 d48="֨"
    544 d49="֩"
    545 d50="֫"
    546 d51="֬"
    547 d52="֯"
    548 d53="ׄ"
    549 d54="ؐ"
    550 d55="ؑ"
    551 d56="ؒ"
    552 d57="ؓ"
    553 d58="ؔ"
    554 d59="ؕ"
    555 d60="ؖ"
    556 d61="ؗ"
    557 d62="ٗ"
    558 d63="٘"
    559 d64="ٙ"
    560 d65="ٚ"
    561 d66="ٛ"
    562 d67="ٝ"
    563 d68="ٞ"
    564 d69="ۖ"
    565 d70="ۗ"
    566 d71="ۘ"
    567 d72="ۙ"
    568 d73="ۚ"
    569 d74="ۛ"
    570 d75="ۜ"
    571 d76="۟"
    572 d77="۠"
    573 d78="ۡ"
    574 d79="ۢ"
    575 d80="ۤ"
    576 d81="ۧ"
    577 d82="ۨ"
    578 d83="۫"
    579 d84="۬"
    580 d85="ܰ"
    581 d86="ܲ"
    582 d87="ܳ"
    583 d88="ܵ"
    584 d89="ܶ"
    585 d90="ܺ"
    586 d91="ܽ"
    587 d92="ܿ"
    588 d93="݀"
    589 d94="݁"
    590 d95="݃"
    591 d96="݅"
    592 d97="݇"
    593 d98="݉"
    594 d99="݊"
    595 d100="߫"
    596 d101="߬"
    597 d102="߭"
    598 d103="߮"
    599 d104="߯"
    600 d105="߰"
    601 d106="߱"
    602 d107="߳"
    603 d108="ࠖ"
    604 d109="ࠗ"
    605 d110="࠘"
    606 d111="࠙"
    607 d112="ࠛ"
    608 d113="ࠜ"
    609 d114="ࠝ"
    610 d115="ࠞ"
    611 d116="ࠟ"
    612 d117="ࠠ"
    613 d118="ࠡ"
    614 d119="ࠢ"
    615 d120="ࠣ"
    616 d121="ࠥ"
    617 d122="ࠦ"
    618 d123="ࠧ"
    619 d124="ࠩ"
    620 d125="ࠪ"
    621 d126="ࠫ"
    622 d127="ࠬ"
    623 d128="࠭"
    624 d129="॑"
    625 d130="॓"
    626 d131="॔"
    627 d132="ྂ"
    628 d133="ྃ"
    629 d134="྆"
    630 d135="྇"
    631 d136="፝"
    632 d137="፞"
    633 d138="፟"
    634 d139="៝"
    635 d140="᤺"
    636 d141="ᨗ"
    637 d142="᩵"
    638 d143="᩶"
    639 d144="᩷"
    640 d145="᩸"
    641 d146="᩹"
    642 d147="᩺"
    643 d148="᩻"
    644 d149="᩼"
    645 d150="᭫"
    646 d151="᭭"
    647 d152="᭮"
    648 d153="᭯"
    649 d154="᭰"
    650 d155="᭱"
    651 d156="᭲"
    652 d157="᭳"
    653 d158="᳐"
    654 d159="᳑"
    655 d160="᳒"
    656 d161="᳚"
    657 d162="᳛"
    658 d163="᳠"
    659 d164="᷀"
    660 d165="᷁"
    661 d166="᷃"
    662 d167="᷄"
    663 d168="᷅"
    664 d169="᷆"
    665 d170="᷇"
    666 d171="᷈"
    667 d172="᷉"
    668 d173="᷋"
    669 d174="᷌"
    670 d175="᷑"
    671 d176="᷒"
    672 d177="ᷓ"
    673 d178="ᷔ"
    674 d179="ᷕ"
    675 d180="ᷖ"
    676 d181="ᷗ"
    677 d182="ᷘ"
    678 d183="ᷙ"
    679 d184="ᷚ"
    680 d185="ᷛ"
    681 d186="ᷜ"
    682 d187="ᷝ"
    683 d188="ᷞ"
    684 d189="ᷟ"
    685 d190="ᷠ"
    686 d191="ᷡ"
    687 d192="ᷢ"
    688 d193="ᷣ"
    689 d194="ᷤ"
    690 d195="ᷥ"
    691 d196="ᷦ"
    692 d197="᷾"
    693 d198="⃐"
    694 d199="⃑"
    695 d200="⃔"
    696 d201="⃕"
    697 d202="⃖"
    698 d203="⃗"
    699 d204="⃛"
    700 d205="⃜"
    701 d206="⃡"
    702 d207="⃧"
    703 d208="⃩"
    704 d209="⃰"
    705 d210="⳯"
    706 d211="⳰"
    707 d212="⳱"
    708 d213="ⷠ"
    709 d214="ⷡ"
    710 d215="ⷢ"
    711 d216="ⷣ"
    712 d217="ⷤ"
    713 d218="ⷥ"
    714 d219="ⷦ"
    715 d220="ⷧ"
    716 d221="ⷨ"
    717 d222="ⷩ"
    718 d223="ⷪ"
    719 d224="ⷫ"
    720 d225="ⷬ"
    721 d226="ⷭ"
    722 d227="ⷮ"
    723 d228="ⷯ"
    724 d229="ⷰ"
    725 d230="ⷱ"
    726 d231="ⷲ"
    727 d232="ⷳ"
    728 d233="ⷴ"
    729 d234="ⷵ"
    730 d235="ⷶ"
    731 d236="ⷷ"
    732 d237="ⷸ"
    733 d238="ⷹ"
    734 d239="ⷺ"
    735 d240="ⷻ"
    736 d241="ⷼ"
    737 d242="ⷽ"
    738 d243="ⷾ"
    739 d244="ⷿ"
    740 d245="꙯"
    741 d246="꙼"
    742 d247="꙽"
    743 d248="꛰"
    744 d249="꛱"
    745 d250="꣠"
    746 d251="꣡"
    747 d252="꣢"
    748 d253="꣣"
    749 d254="꣤"
    750 d255="꣥"
    751 d256="꣦"
    752 d257="꣧"
    753 d258="꣨"
    754 d259="꣩"
    755 d260="꣪"
    756 d261="꣫"
    757 d262="꣬"
    758 d263="꣭"
    759 d264="꣮"
    760 d265="꣯"
    761 d266="꣰"
    762 d267="꣱"
    763 d268="ꪰ"
    764 d269="ꪲ"
    765 d270="ꪳ"
    766 d271="ꪷ"
    767 d272="ꪸ"
    768 d273="ꪾ"
    769 d274="꪿"
    770 d275="꫁"
    771 d276="︠"
    772 d277="︡"
    773 d278="︢"
    774 d279="︣"
    775 d280="︤"
    776 d281="︥"
    777 d282="︦"
    778 d283="𐨏"
    779 d284="𐨸"
    780 d285="𝆅"
    781 d286="𝆆"
    782 d287="𝆇"
    783 d288="𝆈"
    784 d289="𝆉"
    785 d290="𝆪"
    786 d291="𝆫"
    787 d292="𝆬"
    788 d293="𝆭"
    789 d294="𝉂"
    790 d295="𝉃"
    791 d296="𝉄"
    792 
    793 num_diacritics="297"
    794 
    795 placeholder="􎻮"
    796 
    797 #####################################################################
    798 # Upload the image and print the placeholder
    799 #####################################################################
    800 
    801 upload_image_and_print_placeholder