Implement unicode placeholders in icat
This commit is contained in:
parent
ed8a88e009
commit
1218a152bf
@ -504,6 +504,10 @@ the desired number of lines and columns::
|
|||||||
|
|
||||||
<ESC>_Ga=p,U=1,i=<image_id>,c=<columns>,r=<rows><ESC>\
|
<ESC>_Ga=p,U=1,i=<image_id>,c=<columns>,r=<rows><ESC>\
|
||||||
|
|
||||||
|
The creation of the placement need not be a separate escape code, it can be
|
||||||
|
combined with ``a=T`` to both transmit and create the virtual placement with a
|
||||||
|
single code.
|
||||||
|
|
||||||
The image will eventually be fit to the specified rectangle, its aspect ratio
|
The image will eventually be fit to the specified rectangle, its aspect ratio
|
||||||
preserved. Finally, the image can be actually displayed by using the
|
preserved. Finally, the image can be actually displayed by using the
|
||||||
placeholder character, encoding the image ID in its foreground color. The row
|
placeholder character, encoding the image ID in its foreground color. The row
|
||||||
@ -912,6 +916,8 @@ Key Value Default Description
|
|||||||
``r`` Positive integer ``0`` The number of rows to display the image over
|
``r`` Positive integer ``0`` The number of rows to display the image over
|
||||||
``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor to after the image.
|
``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor to after the image.
|
||||||
``1`` is to not move the cursor at all when placing the image.
|
``1`` is to not move the cursor at all when placing the image.
|
||||||
|
``U`` Positive integer ``0`` Set to ``1`` to create a virtual placement for a Unicode placeholder.
|
||||||
|
``1`` is to not move the cursor at all when placing the image.
|
||||||
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
|
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
|
||||||
|
|
||||||
**Keys for animation frame loading**
|
**Keys for animation frame loading**
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import bz2
|
|||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@ -459,6 +460,10 @@ def generate_constants() -> str:
|
|||||||
from kitty.options.types import Options
|
from kitty.options.types import Options
|
||||||
from kitty.options.utils import allowed_shell_integration_values
|
from kitty.options.utils import allowed_shell_integration_values
|
||||||
ref_map = load_ref_map()
|
ref_map = load_ref_map()
|
||||||
|
with open('kitty/data-types.h') as dt:
|
||||||
|
m = re.search(r'^#define IMAGE_PLACEHOLDER_CHAR (\S+)', dt.read(), flags=re.M)
|
||||||
|
assert m is not None
|
||||||
|
placeholder_char = int(m.group(1), 16)
|
||||||
dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help))
|
dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help))
|
||||||
return f'''\
|
return f'''\
|
||||||
package kitty
|
package kitty
|
||||||
@ -468,6 +473,7 @@ type VersionType struct {{
|
|||||||
}}
|
}}
|
||||||
const VersionString string = "{kc.str_version}"
|
const VersionString string = "{kc.str_version}"
|
||||||
const WebsiteBaseURL string = "{kc.website_base_url}"
|
const WebsiteBaseURL string = "{kc.website_base_url}"
|
||||||
|
const ImagePlaceholderChar rune = {placeholder_char}
|
||||||
const VCSRevision string = ""
|
const VCSRevision string = ""
|
||||||
const SSHControlMasterTemplate = "{kc.ssh_control_master_template}"
|
const SSHControlMasterTemplate = "{kc.ssh_control_master_template}"
|
||||||
const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSION}"
|
const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSION}"
|
||||||
|
|||||||
@ -220,13 +220,18 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
|
|||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
use_unicode_placeholder := opts.UnicodePlaceholder
|
||||||
for num_of_items > 0 {
|
for num_of_items > 0 {
|
||||||
imgd := <-output_channel
|
imgd := <-output_channel
|
||||||
|
imgd.use_unicode_placeholder = use_unicode_placeholder
|
||||||
num_of_items--
|
num_of_items--
|
||||||
if imgd.err != nil {
|
if imgd.err != nil {
|
||||||
print_error("Failed to process \x1b[31m%s\x1b[39m: %v\r\n", imgd.source_name, imgd.err)
|
print_error("Failed to process \x1b[31m%s\x1b[39m: %s\r\n", imgd.source_name, imgd.err)
|
||||||
} else {
|
} else {
|
||||||
transmit_image(imgd)
|
transmit_image(imgd)
|
||||||
|
if imgd.err != nil {
|
||||||
|
print_error("Failed to transmit \x1b[31m%s\x1b[39m: %s\r\n", imgd.source_name, imgd.err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keep_going.Store(false)
|
keep_going.Store(false)
|
||||||
|
|||||||
@ -181,9 +181,12 @@ type image_data struct {
|
|||||||
scaled_frac struct{ x, y float64 }
|
scaled_frac struct{ x, y float64 }
|
||||||
frames []*image_frame
|
frames []*image_frame
|
||||||
image_number uint32
|
image_number uint32
|
||||||
|
image_id uint32
|
||||||
cell_x_offset int
|
cell_x_offset int
|
||||||
move_x_by int
|
move_x_by int
|
||||||
move_to struct{ x, y int }
|
move_to struct{ x, y int }
|
||||||
|
width_cells, height_cells int
|
||||||
|
use_unicode_placeholder bool
|
||||||
|
|
||||||
// for error reporting
|
// for error reporting
|
||||||
err error
|
err error
|
||||||
|
|||||||
@ -9,14 +9,17 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"kitty"
|
||||||
"math"
|
"math"
|
||||||
not_rand "math/rand"
|
not_rand "math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"kitty/tools/tui/graphics"
|
"kitty/tools/tui/graphics"
|
||||||
"kitty/tools/tui/loop"
|
"kitty/tools/tui/loop"
|
||||||
"kitty/tools/utils"
|
"kitty/tools/utils"
|
||||||
|
"kitty/tools/utils/images"
|
||||||
"kitty/tools/utils/shm"
|
"kitty/tools/utils/shm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,8 +33,16 @@ func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics
|
|||||||
if imgd.image_number != 0 {
|
if imgd.image_number != 0 {
|
||||||
gc.SetImageNumber(imgd.image_number)
|
gc.SetImageNumber(imgd.image_number)
|
||||||
}
|
}
|
||||||
|
if imgd.image_id != 0 {
|
||||||
|
gc.SetImageId(imgd.image_id)
|
||||||
|
}
|
||||||
if frame_num == 0 {
|
if frame_num == 0 {
|
||||||
gc.SetAction(graphics.GRT_action_transmit_and_display)
|
gc.SetAction(graphics.GRT_action_transmit_and_display)
|
||||||
|
if imgd.use_unicode_placeholder {
|
||||||
|
gc.SetUnicodePlaceholder(graphics.GRT_create_unicode_placeholder)
|
||||||
|
gc.SetColumns(uint64(imgd.width_cells))
|
||||||
|
gc.SetRows(uint64(imgd.height_cells))
|
||||||
|
}
|
||||||
if imgd.cell_x_offset > 0 {
|
if imgd.cell_x_offset > 0 {
|
||||||
gc.SetXOffset(uint64(imgd.cell_x_offset))
|
gc.SetXOffset(uint64(imgd.cell_x_offset))
|
||||||
}
|
}
|
||||||
@ -182,24 +193,26 @@ func calculate_in_cell_x_offset(width, cell_width int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func place_cursor(imgd *image_data) {
|
func place_cursor(imgd *image_data) {
|
||||||
cw := int(screen_size.Xpixel) / int(int(screen_size.Col))
|
cw := int(screen_size.Xpixel) / int(screen_size.Col)
|
||||||
|
ch := int(screen_size.Ypixel) / int(screen_size.Row)
|
||||||
imgd.cell_x_offset = calculate_in_cell_x_offset(imgd.canvas_width, cw)
|
imgd.cell_x_offset = calculate_in_cell_x_offset(imgd.canvas_width, cw)
|
||||||
num_of_cells_needed := int(math.Ceil(float64(imgd.canvas_width) / float64(cw)))
|
imgd.width_cells = int(math.Ceil(float64(imgd.canvas_width) / float64(cw)))
|
||||||
|
imgd.height_cells = int(math.Ceil(float64(imgd.canvas_height) / float64(ch)))
|
||||||
if place == nil {
|
if place == nil {
|
||||||
switch opts.Align {
|
switch opts.Align {
|
||||||
case "center":
|
case "center":
|
||||||
imgd.move_x_by = (int(screen_size.Col) - num_of_cells_needed) / 2
|
imgd.move_x_by = (int(screen_size.Col) - imgd.width_cells) / 2
|
||||||
case "right":
|
case "right":
|
||||||
imgd.move_x_by = (int(screen_size.Col) - num_of_cells_needed)
|
imgd.move_x_by = (int(screen_size.Col) - imgd.width_cells)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
imgd.move_to.x = place.left + 1
|
imgd.move_to.x = place.left + 1
|
||||||
imgd.move_to.y = place.top + 1
|
imgd.move_to.y = place.top + 1
|
||||||
switch opts.Align {
|
switch opts.Align {
|
||||||
case "center":
|
case "center":
|
||||||
imgd.move_to.x += (place.width - num_of_cells_needed) / 2
|
imgd.move_to.x += (place.width - imgd.width_cells) / 2
|
||||||
case "right":
|
case "right":
|
||||||
imgd.move_to.x += (place.width - num_of_cells_needed)
|
imgd.move_to.x += (place.width - imgd.width_cells)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,6 +230,35 @@ func next_random() (ans uint32) {
|
|||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func write_unicode_placeholder(imgd *image_data) {
|
||||||
|
prefix := ""
|
||||||
|
foreground := fmt.Sprintf("\033[38:2:%d:%d:%dm", (imgd.image_id>>16)&255, (imgd.image_id>>8)&255, imgd.image_id&255)
|
||||||
|
os.Stdout.WriteString(loop.SAVE_PRIVATE_MODE_VALUES + foreground)
|
||||||
|
restore := "\033[39m" + loop.RESTORE_PRIVATE_MODE_VALUES
|
||||||
|
if imgd.move_to.y > 0 {
|
||||||
|
fmt.Print(loop.SAVE_CURSOR)
|
||||||
|
restore += loop.RESTORE_CURSOR
|
||||||
|
} else if imgd.move_x_by > 0 {
|
||||||
|
prefix = strings.Repeat(" ", imgd.move_x_by)
|
||||||
|
}
|
||||||
|
defer func() { os.Stdout.WriteString(restore) }()
|
||||||
|
if imgd.move_to.y > 0 {
|
||||||
|
fmt.Printf(loop.MoveCursorToTemplate, imgd.move_to.y, 0)
|
||||||
|
}
|
||||||
|
id_char := string(images.NumberToDiacritic[(imgd.image_id>>24)&255])
|
||||||
|
for r := 0; r < imgd.height_cells; r++ {
|
||||||
|
if imgd.move_to.x > 0 {
|
||||||
|
fmt.Printf("\x1b[%dC", imgd.move_to.x)
|
||||||
|
} else {
|
||||||
|
os.Stdout.WriteString(prefix)
|
||||||
|
}
|
||||||
|
for c := 0; c < imgd.width_cells; c++ {
|
||||||
|
os.Stdout.WriteString(string(kitty.ImagePlaceholderChar) + string(images.NumberToDiacritic[r]) + string(images.NumberToDiacritic[c]) + id_char)
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString("\n\r")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func transmit_image(imgd *image_data) {
|
func transmit_image(imgd *image_data) {
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, frame := range imgd.frames {
|
for _, frame := range imgd.frames {
|
||||||
@ -252,27 +294,49 @@ func transmit_image(imgd *image_data) {
|
|||||||
if f == nil {
|
if f == nil {
|
||||||
f = transmit_stream
|
f = transmit_stream
|
||||||
}
|
}
|
||||||
|
if imgd.use_unicode_placeholder {
|
||||||
|
for imgd.image_id&0xFF000000 == 0 || imgd.image_id&0x00FFFF00 == 0 {
|
||||||
|
// Generate a 32-bit image id using rejection sampling such that the most
|
||||||
|
// significant byte and the two bytes in the middle are non-zero to avoid
|
||||||
|
// collisions with applications that cannot represent non-zero most
|
||||||
|
// significant bytes (which is represented by the third combining character)
|
||||||
|
// or two non-zero bytes in the middle (which requires 24-bit color mode).
|
||||||
|
imgd.image_id = next_random()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if len(imgd.frames) > 1 {
|
if len(imgd.frames) > 1 {
|
||||||
for imgd.image_number == 0 {
|
for imgd.image_number == 0 {
|
||||||
imgd.image_number = next_random()
|
imgd.image_number = next_random()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
place_cursor(imgd)
|
place_cursor(imgd)
|
||||||
|
if imgd.use_unicode_placeholder && utils.Max(imgd.width_cells, imgd.height_cells) >= len(images.NumberToDiacritic) {
|
||||||
|
imgd.err = fmt.Errorf("Image too large to be displayed using Unicode placeholders. Maximum size is %dx%d cells", len(images.NumberToDiacritic), len(images.NumberToDiacritic))
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Print("\r")
|
fmt.Print("\r")
|
||||||
|
if !imgd.use_unicode_placeholder {
|
||||||
if imgd.move_x_by > 0 {
|
if imgd.move_x_by > 0 {
|
||||||
fmt.Printf("\x1b[%dC", imgd.move_x_by)
|
fmt.Printf("\x1b[%dC", imgd.move_x_by)
|
||||||
}
|
}
|
||||||
if imgd.move_to.x > 0 {
|
if imgd.move_to.x > 0 {
|
||||||
fmt.Printf(loop.MoveCursorToTemplate, imgd.move_to.y, imgd.move_to.x)
|
fmt.Printf(loop.MoveCursorToTemplate, imgd.move_to.y, imgd.move_to.x)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame_control_cmd := graphics.GraphicsCommand{}
|
frame_control_cmd := graphics.GraphicsCommand{}
|
||||||
frame_control_cmd.SetAction(graphics.GRT_action_animate).SetImageNumber(imgd.image_number)
|
frame_control_cmd.SetAction(graphics.GRT_action_animate)
|
||||||
|
if imgd.image_id != 0 {
|
||||||
|
frame_control_cmd.SetImageId(imgd.image_id)
|
||||||
|
} else {
|
||||||
|
frame_control_cmd.SetImageNumber(imgd.image_number)
|
||||||
|
}
|
||||||
is_animated := len(imgd.frames) > 1
|
is_animated := len(imgd.frames) > 1
|
||||||
|
|
||||||
for frame_num, frame := range imgd.frames {
|
for frame_num, frame := range imgd.frames {
|
||||||
err := f(imgd, frame_num, frame)
|
err := f(imgd, frame_num, frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
print_error("\rFailed to transmit %s with error: %v", imgd.source_name, err)
|
imgd.err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if is_animated {
|
if is_animated {
|
||||||
@ -296,6 +360,9 @@ func transmit_image(imgd *image_data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if imgd.use_unicode_placeholder {
|
||||||
|
write_unicode_placeholder(imgd)
|
||||||
|
}
|
||||||
if is_animated {
|
if is_animated {
|
||||||
c := frame_control_cmd
|
c := frame_control_cmd
|
||||||
c.SetAnimationControl(3) // set animation to normal mode
|
c.SetAnimationControl(3) // set animation to normal mode
|
||||||
|
|||||||
@ -258,6 +258,28 @@ func GRT_C_from_string(a string) (ans GRT_C, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GRT_U int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GRT_no_unicode_placeholder GRT_U = iota
|
||||||
|
GRT_create_unicode_placeholder
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self GRT_U) String() string {
|
||||||
|
return strconv.Itoa(int(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GRT_U_from_string(a string) (ans GRT_U, err error) {
|
||||||
|
switch a {
|
||||||
|
case "0":
|
||||||
|
case "1":
|
||||||
|
ans = GRT_create_unicode_placeholder
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Not a valid cursor movement value: %#v", a)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type CompositionMode int
|
type CompositionMode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -437,6 +459,7 @@ type GraphicsCommand struct {
|
|||||||
m GRT_m
|
m GRT_m
|
||||||
C GRT_C
|
C GRT_C
|
||||||
d GRT_d
|
d GRT_d
|
||||||
|
U GRT_U
|
||||||
|
|
||||||
s, v, S, O, x, y, w, h, X, Y, c, r uint64
|
s, v, S, O, x, y, w, h, X, Y, c, r uint64
|
||||||
|
|
||||||
@ -464,6 +487,7 @@ func (self *GraphicsCommand) serialize_non_default_fields() (ans []string) {
|
|||||||
write_key('o', self.o, null.o)
|
write_key('o', self.o, null.o)
|
||||||
write_key('m', self.m, null.m)
|
write_key('m', self.m, null.m)
|
||||||
write_key('C', self.C, null.C)
|
write_key('C', self.C, null.C)
|
||||||
|
write_key('U', self.U, null.U)
|
||||||
write_key('d', self.d, null.d)
|
write_key('d', self.d, null.d)
|
||||||
|
|
||||||
write_key('s', self.s, null.s)
|
write_key('s', self.s, null.s)
|
||||||
@ -637,6 +661,8 @@ func (self *GraphicsCommand) SetString(key byte, value string) (err error) {
|
|||||||
err = set_val(&self.m, GRT_m_from_string, value)
|
err = set_val(&self.m, GRT_m_from_string, value)
|
||||||
case 'C':
|
case 'C':
|
||||||
err = set_val(&self.C, GRT_C_from_string, value)
|
err = set_val(&self.C, GRT_C_from_string, value)
|
||||||
|
case 'U':
|
||||||
|
err = set_val(&self.U, GRT_U_from_string, value)
|
||||||
case 'd':
|
case 'd':
|
||||||
err = set_val(&self.d, GRT_d_from_string, value)
|
err = set_val(&self.d, GRT_d_from_string, value)
|
||||||
case 's':
|
case 's':
|
||||||
@ -776,6 +802,15 @@ func (self *GraphicsCommand) SetCursorMovement(c GRT_C) *GraphicsCommand {
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *GraphicsCommand) UnicodePlaceholder() GRT_U {
|
||||||
|
return self.U
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *GraphicsCommand) SetUnicodePlaceholder(U GRT_U) *GraphicsCommand {
|
||||||
|
self.U = U
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
func (self *GraphicsCommand) Format() GRT_f {
|
func (self *GraphicsCommand) Format() GRT_f {
|
||||||
return self.f
|
return self.f
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user