Image placement using Unicode placeholders

This commit introduces the Unicode placeholder image placement method.
In particular:
- Virtual placements can be created by passing `U=1` in a put command.
- Images with virtual placements can be displayed using the placeholder
  character `U+10EEEE` with diacritics indicating rows and columns.
- The image ID is indicated by the foreground color of the placeholder.
  Additionally, the most significant byte of the ID can be specified via
  the third diacritic.
- Underline color can be optionally used to specify the placement ID.
- A bug was fixed, which caused incomplete image removal when it was
  overwritten by another image with the same ID.
This commit is contained in:
Sergei Grechanik 2022-10-29 19:48:06 -07:00
parent 1f84e2d4e5
commit d63eeada73
20 changed files with 1288 additions and 17 deletions

1
.gitattributes vendored
View File

@ -3,6 +3,7 @@ kitty/emoji.h linguist-generated=true
kitty/charsets.c linguist-generated=true
kitty/key_encoding.py linguist-generated=true
kitty/unicode-data.c linguist-generated=true
kitty/rowcolumn-diacritics.c linguist-generated=true
kitty/rgb.py linguist-generated=true
kitty/srgb_gamma.c linguist-generated=true
kitty/gl-wrapper.* linguist-generated=true

View File

@ -146,6 +146,8 @@ Detailed list of changes
- macOS: Export kitty selected text to the system for use with services that accept it (patch by Sertaç Ö. Yıldız)
- Image placement using Unicode placeholders (:pull:`5664`)
0.26.5 [2022-11-07]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -472,6 +472,87 @@ z-index and the same id, then the behavior is undefined.
Support for the C=1 cursor movement policy
Unicode placeholders
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 0.27.0
Unicode placeholders
You can also use a special Unicode character ``U+10EEEE`` as a placeholder for
an image. This approach is less flexible, but it works for any application that
supports Unicode and foreground colors (tmux, vim, etc). To use it, you need to
create a virtual image placement by specifying ``U=1`` and the desirable number
of lines and columns::
<ESC>_Ga=p,U=1,i=<image_id>,c=<columns>,r=<rows><ESC>\
The image will be fit to the specified rectangle, its aspect ratio preserved (in
the future more scaling modes may be added to the protocol). Now you can display
the image using the placeholder character, encoding the image ID in its
foreground color. The row and column values are specified with diacritics listed
in ``rowcolumn-diacritics.txt``. For example, here is how you can print a 2x2
placeholder for image ID 42::
printf "\e[38;5;42m\U10EEEE\U0305\U0305\U10EEEE\U0305\U030D\n"
printf "\e[38;5;42m\U10EEEE\U030D\U0305\U10EEEE\U030D\U030D\n"
By using only the foreground color you are limited to 8-bit IDs in 256 color
mode and to 24-bit IDs in true color mode. If you need more bits for the image
ID, you can specify the most significant byte via the third diacritic. For
example, this is the placeholder for the image ID ``738197504 = 42 + 2 << 24``::
printf "\e[38;5;42m\U10EEEE\U0305\U0305\U030E\U10EEEE\U0305\U030D\U030E\n"
printf "\e[38;5;42m\U10EEEE\U030D\U0305\U030E\U10EEEE\U030D\U030D\U030E\n"
You can also specify a placement ID using the underline color (if it's omitted
or zero, the terminal may choose any virtual placement of the given image). The
background color is interpreted as the background color, visible if the image is
transparent. Other text attributes are reserved for future use.
Row, column and most significant byte diacritics may also be omitted, in which
case the placeholder cell will inherit the missing values from the placeholder
cell to the left:
- If no diacritics are present, and the previous placeholder cell has the same
foreground and underline colors, then the row of the current cell will be the
row of the cell to the left, the column will be the column of the cell to the
left plus one, and the most significant image ID byte will be the most
significant image ID byte of the cell to the left.
- If only the row diacritic is present, and the previous placeholder cell has
the same row and the same foreground and underline colors, then the column of
the current cell will be the column of the cell to the left plus one, and the
most significant image ID byte will be the most significant image ID byte of
the cell to the left.
- If only the row and column diacritics are present, and the previous
placeholder cell has the same row, the same foreground and underline colors,
and its column is one less than the current column, then the most significant
image ID byte of the current cell will be the most significant image ID byte
of the cell to the left.
These rules are applied left-to-right, which allows specifying only row
diacritics of the first column, i.e. here is a 2 rows by 3 columns placeholder::
printf "\e[38;5;42m\U10EEEE\U0305\U10EEEE\U10EEEE\n"
printf "\e[38;5;42m\U10EEEE\U030D\U10EEEE\U10EEEE\n"
This will not work for horizontal scrolling and overlapping images since the two
given rules will fail to guess the missing information. In such cases, the
terminal may apply other heuristics (but it doesn't have to).
It is important to distinguish between virtual image placements and real images
displayed on top of placeholders. Virtual placements are invisible and only play
the role of prototypes for real images. Virtual placements can be deleted by a
deletion command only when the `d` key is equal to ``i``, ``I``, ``n`` or ``N``.
The key values ``a``, ``c``, ``p``, ``q``, ``x``, ``y``, ``z`` and their capital
variants never affect virtual placements because they do not have a physical
location on the screen.
Real images displayed on top of placeholders are not considered placements from
the protocol perspective. They cannot be manipulated using graphics commands,
instead they should be moved, deleted, or modified by manipulating the
underlying placeholder as normal text.
Deleting images
---------------------

View File

@ -274,6 +274,7 @@ def graphics_parser() -> None:
'Y': ('cell_y_offset', 'uint'),
'z': ('z_index', 'int'),
'C': ('cursor_movement', 'uint'),
'U': ('unicode_placement', 'uint'),
}
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
write_header(text, 'kitty/parse-graphics-command.h')

View File

@ -529,6 +529,52 @@ def add_all(p: Callable[..., None], for_go: bool = False) -> None:
subprocess.check_call(['gofmt', '-w', '-s', gof.name])
def gen_rowcolumn_diacritics() -> None:
# codes of all row/column diacritics
codes = []
with open("./rowcolumn-diacritics.txt", "r") as file:
for line in file.readlines():
if line.startswith('#'):
continue
code = int(line.split(";")[0], 16)
codes.append(code)
with create_header('kitty/rowcolumn-diacritics.c') as p:
p('#include "unicode-data.h"')
p('int diacritic_to_num(char_type code) {')
p('\tswitch (code) {')
range_start_num = 1
range_start = 0
range_end = 0
def print_range() -> None:
if range_start >= range_end:
return
write_case((range_start, range_end), p)
p('\t\treturn code - ' + hex(range_start) + ' + ' +
str(range_start_num) + ';')
for code in codes:
if range_end == code:
range_end += 1
else:
print_range()
range_start_num += range_end - range_start
range_start = code
range_end = code + 1
print_range()
p('\t}')
p('\treturn 0;')
p('}')
with open('kittens/icat/rowcolumn_diacritics.py', 'w') as f:
f.write('# generated by gen-wcwidth.py, do not edit\n\n')
strings_utf8 = ', '.join(repr(chr(c).encode('utf-8')) for c in codes)
f.write("rowcolumn_diacritics_utf8 = ({}) # noqa\n".format(strings_utf8))
parse_ucd()
parse_prop_list()
parse_emoji()
@ -537,3 +583,4 @@ def add_all(p: Callable[..., None], for_go: bool = False) -> None:
gen_wcwidth()
gen_emoji()
gen_names()
gen_rowcolumn_diacritics()

View File

@ -81,6 +81,9 @@ typedef struct ImageAnchorPosition {
#define DECORATION_FG_CODE 58
#define CHAR_IS_BLANK(ch) ((ch) == 32 || (ch) == 0)
// PUA character used as an image placeholder.
#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE
#define FG 1
#define BG 2
@ -192,6 +195,7 @@ typedef union LineAttrs {
struct {
uint8_t is_continued : 1;
uint8_t has_dirty_text : 1;
uint8_t has_image_placeholders : 1;
PromptKind prompt_kind : 2;
};
uint8_t val;

View File

@ -21,6 +21,7 @@ from kitty.options.types import Options
from kitty.types import SignalInfo
# Constants {{{
IMAGE_PLACEHOLDER_CHAR: int
GLFW_PRIMARY_SELECTION: int
GLFW_CLIPBOARD: int
CLD_KILLED: int

View File

@ -535,6 +535,7 @@ START_ALLOW_CASE_RANGE
case 0:
case ' ':
case '\t':
case IMAGE_PLACEHOLDER_CHAR:
return BLANK_FONT;
case 0x2500 ... 0x2573:
case 0x2574 ... 0x259f:

View File

@ -149,6 +149,7 @@ img_by_client_number(GraphicsManager *self, uint32_t number) {
static void
remove_image(GraphicsManager *self, size_t idx) {
assert(idx < self->image_count);
free_image(self, self->images + idx);
remove_i_from_array(self->images, idx, self->image_count);
self->layers_dirty = true;
@ -555,7 +556,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
img->root_frame_data_loaded = false;
img->is_drawn = false;
img->current_frame_shown_at = 0;
free_refs_data(img);
free_image(self, img);
*is_dirty = true;
self->layers_dirty = true;
} else {
@ -651,12 +652,12 @@ static void
update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) {
uint32_t t;
if (num_cols == 0) {
t = ref->src_width + ref->cell_x_offset;
t = (uint32_t)(ref->src_width + ref->cell_x_offset);
num_cols = t / cell.width;
if (t > num_cols * cell.width) num_cols += 1;
}
if (num_rows == 0) {
t = ref->src_height + ref->cell_y_offset;
t = (uint32_t)(ref->src_height + ref->cell_y_offset);
num_rows = t / cell.height;
if (t > num_rows * cell.height) num_rows += 1;
}
@ -664,6 +665,176 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelS
ref->effective_num_cols = num_cols;
}
// Create a real image ref for a virtual image ref (placement) positioned in the
// given cells. This is used for images positioned using Unicode placeholders.
//
// The image is resized to fit a box of cells with dimensions
// `image_ref->columns` by `image_ref->rows`. The parameters `img_col`,
// `img_row, `columns`, `rows` describe a part of this box that we want to
// display.
//
// Parameters:
// - `self` - the graphics manager
// - `screen_row` - the starting row of the screen
// - `screen_col` - the starting column of the screen
// - `image_id` - the id of the image
// - `placement_id` - the id of the placement (0 to find it automatically), it
// must be a virtual placement
// - `img_col` - the column of the image box we want to start with (base 0)
// - `img_row` - the row of the image box we want to start with (base 0)
// - `columns` - the number of columns we want to display
// - `rows` - the number of rows we want to display
// - `cell` - the size of a screen cell
Image *grman_put_cell_image(GraphicsManager *self, uint32_t screen_row,
uint32_t screen_col, uint32_t image_id,
uint32_t placement_id, uint32_t img_col,
uint32_t img_row, uint32_t columns, uint32_t rows,
CellPixelSize cell) {
Image *img = img_by_client_id(self, image_id);
if (img == NULL) return NULL;
ImageRef *virt_img_ref = NULL;
if (placement_id) {
// Find the placement by the id. It must be a virtual placement.
for (size_t i = 0; i < img->refcnt; i++) {
if (img->refs[i].is_virtual_ref &&
img->refs[i].client_id == placement_id) {
virt_img_ref = img->refs + i;
break;
}
}
} else {
// Find the first virtual image placement.
for (size_t i = 0; i < img->refcnt; i++) {
if (img->refs[i].is_virtual_ref) {
virt_img_ref = img->refs + i;
break;
}
}
}
if (!virt_img_ref) return NULL;
// Create the ref structure on stack first. We will not create a real
// reference if the image is completely out of bounds.
ImageRef ref = {0};
ref.is_cell_image = true;
uint32_t img_rows = virt_img_ref->num_rows;
uint32_t img_columns = virt_img_ref->num_cols;
// If the number of columns or rows for the image is not set, compute them
// in such a way that the image is as close as possible to its natural size.
if (img_columns == 0)
img_columns = (img->width + cell.width - 1) / cell.width;
if (img_rows == 0) img_rows = (img->height + cell.height - 1) / cell.height;
ref.start_row = screen_row;
ref.start_column = screen_col;
ref.num_cols = columns;
ref.num_rows = rows;
// The image is fit to the destination box of size
// (cell.width * img_columns) by (cell.height * img_rows)
// The conversion from source (image) coordinates to destination (box)
// coordinates is done by the following formula:
// x_dst = x_src * x_scale + x_offset
// y_dst = y_src * y_scale + y_offset
float x_offset, y_offset, x_scale, y_scale;
// Fit the image to the box while preserving aspect ratio
if (img->width * img_rows * cell.height >
img->height * img_columns * cell.width) {
// Fit to width and center vertically.
x_offset = 0;
x_scale = (float)(img_columns * cell.width) / img->width;
y_scale = x_scale;
y_offset = (img_rows * cell.height - img->height * y_scale) / 2;
} else {
// Fit to height and center horizontally.
y_offset = 0;
y_scale = (float)(img_rows * cell.height) / img->height;
x_scale = y_scale;
x_offset = (img_columns * cell.width - img->width * x_scale) / 2;
}
// Now we can compute source (image) coordinates from destination (box)
// coordinates by formula:
// x_src = (x_dst - x_offset) / x_scale
// y_src = (y_dst - y_offset) / y_scale
// Destination (box) coordinates of the rectangle we want to display.
uint32_t x_dst = img_col * cell.width;
uint32_t y_dst = img_row * cell.height;
uint32_t w_dst = columns * cell.width;
uint32_t h_dst = rows * cell.height;
// Compute the source coordinates of the rectangle.
ref.src_x = (x_dst - x_offset) / x_scale;
ref.src_y = (y_dst - y_offset) / y_scale;
ref.src_width = w_dst / x_scale;
ref.src_height = h_dst / y_scale;
// If the top left corner is out of bounds of the source image, we can
// adjust cell offsets and the starting row/column. And if the rectangle is
// completely out of bounds, we can avoid creating a real reference. This
// is just an optimization, the image will be displayed correctly even if we
// do not do this.
if (ref.src_x < 0) {
ref.src_width += ref.src_x;
ref.cell_x_offset = (uint32_t)(-ref.src_x * x_scale);
ref.src_x = 0;
uint32_t col_offset = ref.cell_x_offset / cell.width;
ref.cell_x_offset %= cell.width;
ref.start_column += col_offset;
if (ref.num_cols <= col_offset)
return img;
ref.num_cols -= col_offset;
}
if (ref.src_y < 0) {
ref.src_height += ref.src_y;
ref.cell_y_offset = (uint32_t)(-ref.src_y * y_scale);
ref.src_y = 0;
uint32_t row_offset = ref.cell_y_offset / cell.height;
ref.cell_y_offset %= cell.height;
ref.start_row += row_offset;
if (ref.num_rows <= row_offset)
return img;
ref.num_rows -= row_offset;
}
// For the bottom right corner we can remove only completely empty rows and
// columns.
if (ref.src_x + ref.src_width > img->width) {
float redundant_w = ref.src_x + ref.src_width - img->width;
uint32_t redundant_cols = (uint32_t)(redundant_w * x_scale) / cell.width;
if (ref.num_cols <= redundant_cols)
return img;
ref.src_width -= redundant_cols * cell.width / x_scale;
ref.num_cols -= redundant_cols;
}
if (ref.src_y + ref.src_height > img->height) {
float redundant_h = ref.src_y + ref.src_height - img->height;
uint32_t redundant_rows = (uint32_t)(redundant_h * y_scale) / cell.height;
if (ref.num_rows <= redundant_rows)
return img;
ref.src_height -= redundant_rows * cell.height / y_scale;
ref.num_rows -= redundant_rows;
}
// The cursor will be drawn on top of the image.
ref.z_index = -1;
// Create a real ref.
ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 16, true);
self->layers_dirty = true;
ImageRef *real_ref = img->refs + img->refcnt++;
*real_ref = ref;
img->atime = monotonic();
update_src_rect(real_ref, img);
update_dest_rect(real_ref, ref.num_cols, ref.num_rows, cell);
return img;
}
static uint32_t
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
@ -691,8 +862,8 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
}
img->atime = monotonic();
ref->src_x = g->x_offset; ref->src_y = g->y_offset; ref->src_width = g->width ? g->width : img->width; ref->src_height = g->height ? g->height : img->height;
ref->src_width = MIN(ref->src_width, img->width - (img->width > ref->src_x ? ref->src_x : img->width));
ref->src_height = MIN(ref->src_height, img->height - (img->height > ref->src_y ? ref->src_y : img->height));
ref->src_width = MIN(ref->src_width, img->width - ((float)img->width > ref->src_x ? ref->src_x : (float)img->width));
ref->src_height = MIN(ref->src_height, img->height - ((float)img->height > ref->src_y ? ref->src_y : (float)img->height));
ref->z_index = g->z_index;
ref->start_row = c->y; ref->start_column = c->x;
ref->cell_x_offset = MIN(g->cell_x_offset, cell.width - 1);
@ -701,8 +872,12 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
if (img->client_id) ref->client_id = g->placement_id;
update_src_rect(ref, img);
update_dest_rect(ref, g->num_cells, g->num_lines, cell);
if (g->unicode_placement) {
ref->is_virtual_ref = true;
ref->start_row = ref->start_column = 0;
}
// Move the cursor, the screen will take care of ensuring it is in bounds
if (g->cursor_movement != 1) {
if (g->cursor_movement != 1 && !g->unicode_placement) {
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
}
return img->client_id;
@ -759,6 +934,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
img->is_drawn = false;
for (j = 0; j < img->refcnt; j++) { ref = img->refs + j;
if (ref->is_virtual_ref) continue;
r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height;
if (ref->num_rows > 0) r.bottom = y0 - (ref->start_row + (int32_t)ref->num_rows) * dy;
else r.bottom = r.top - screen_height * (float)ref->src_height / screen_height_px;
@ -1414,6 +1590,7 @@ modify_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe
static bool
scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
if (ref->is_virtual_ref) return false;
ScrollData *d = (ScrollData*)data;
ref->start_row += d->amt;
return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit;
@ -1431,6 +1608,7 @@ ref_outside_region(const ImageRef *ref, index_type margin_top, index_type margin
static bool
scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) {
if (ref->is_virtual_ref) return false;
ScrollData *d = (ScrollData*)data;
if (ref_within_region(ref, d->margin_top, d->margin_bottom)) {
ref->start_row += d->amt;
@ -1468,13 +1646,44 @@ grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize
}
}
static bool
cell_image_row_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
if (ref->is_virtual_ref || !ref->is_cell_image)
return false;
int32_t top = *(int32_t *)data;
int32_t bottom = *((int32_t *)data + 1);
return ref_within_region(ref, top, bottom);
}
static bool
cell_image_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data UNUSED, CellPixelSize cell UNUSED) {
return !ref->is_virtual_ref && ref->is_cell_image;
}
// Remove cell images within the given region.
void
grman_remove_cell_images(GraphicsManager *self, int32_t top, int32_t bottom) {
CellPixelSize dummy = {0};
int32_t data[] = {top, bottom};
filter_refs(self, data, false, cell_image_row_filter_func, dummy, false);
}
void
grman_remove_all_cell_images(GraphicsManager *self) {
CellPixelSize dummy = {0};
filter_refs(self, NULL, false, cell_image_filter_func, dummy, false);
}
static bool
clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
if (ref->is_virtual_ref || ref->is_cell_image) return false;
return ref->start_row + (int32_t)ref->effective_num_rows > 0;
}
static bool
clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
if (ref->is_virtual_ref) return false;
return true;
}
@ -1500,18 +1709,21 @@ number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelS
static bool
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
if (ref->is_virtual_ref || ref->is_cell_image) return false;
const GraphicsCommand *g = data;
return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
}
static bool
y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
if (ref->is_virtual_ref || ref->is_cell_image) return false;
const GraphicsCommand *g = data;
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)g->y_offset - 1) < ((int32_t)(ref->start_row + ref->effective_num_rows));
}
static bool
z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
if (ref->is_virtual_ref || ref->is_cell_image) return false;
const GraphicsCommand *g = data;
return ref->z_index == g->z_index;
}
@ -1519,11 +1731,13 @@ z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixe
static bool
point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
if (ref->is_virtual_ref || ref->is_cell_image) return false;
return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell);
}
static bool
point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
if (ref->is_virtual_ref || ref->is_cell_image) return false;
return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell);
}
@ -1584,6 +1798,7 @@ grman_rescale(GraphicsManager *self, CellPixelSize cell) {
img = self->images + i;
for (size_t j = img->refcnt; j-- > 0;) {
ref = img->refs + j;
if (ref->is_virtual_ref || ref->is_cell_image) continue;
ref->cell_x_offset = MIN(ref->cell_x_offset, cell.width - 1);
ref->cell_y_offset = MIN(ref->cell_y_offset, cell.height - 1);
update_dest_rect(ref, ref->num_cols, ref->num_rows, cell);
@ -1851,6 +2066,7 @@ init_graphics(PyObject *module) {
if (PyType_Ready(&GraphicsManager_Type) < 0) return false;
if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false;
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
if (PyModule_AddIntMacro(module, IMAGE_PLACEHOLDER_CHAR) != 0) return false;
Py_INCREF(&GraphicsManager_Type);
return true;
}

View File

@ -21,6 +21,7 @@ typedef struct {
union { uint32_t num_cells, other_frame_number; };
union { int32_t z_index, gap; };
size_t payload_sz;
bool unicode_placement;
} GraphicsCommand;
typedef struct {
@ -28,12 +29,18 @@ typedef struct {
} ImageRect;
typedef struct {
uint32_t src_width, src_height, src_x, src_y;
float src_width, src_height, src_x, src_y;
uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows, effective_num_cols;
int32_t z_index;
int32_t start_row, start_column;
uint32_t client_id;
ImageRect src_rect;
// Indicates whether this reference represents a cell image that should be
// removed when the corresponding cells are modified.
bool is_cell_image;
// Virtual refs are not displayed but they can be used as prototypes for
// refs placed using unicode placeholders.
bool is_virtual_ref;
} ImageRef;
typedef struct {
@ -154,10 +161,13 @@ gl_pos_y(const unsigned int px_from_top_margin, const unsigned int viewport_size
GraphicsManager* grman_alloc(void);
void grman_clear(GraphicsManager*, bool, CellPixelSize fg);
const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize fg);
Image* grman_put_cell_image(GraphicsManager *self, uint32_t row, uint32_t col, uint32_t image_id, uint32_t placement_id, uint32_t x, uint32_t y, uint32_t w, uint32_t h, CellPixelSize cell);
bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize);
void grman_scroll_images(GraphicsManager *self, const ScrollData*, CellPixelSize fg);
void grman_resize(GraphicsManager*, index_type, index_type, index_type, index_type);
void grman_rescale(GraphicsManager *self, CellPixelSize fg);
void grman_remove_cell_images(GraphicsManager *self, int32_t top, int32_t bottom);
void grman_remove_all_cell_images(GraphicsManager *self);
void gpu_data_for_image(ImageRenderData *ans, float left, float top, float right, float bottom);
void gpu_data_for_centered_image(ImageRenderData *ans, unsigned int screen_width_px, unsigned int screen_height_px, unsigned int width, unsigned int height);
bool png_path_to_bitmap(const char *path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz);

View File

@ -201,6 +201,11 @@ historybuf_mark_line_dirty(HistoryBuf *self, index_type y) {
attrptr(self, index_of(self, y))->has_dirty_text = true;
}
void
historybuf_set_line_has_image_placeholders(HistoryBuf *self, index_type y, bool val) {
attrptr(self, index_of(self, y))->has_image_placeholders = val;
}
void
historybuf_clear(HistoryBuf *self) {
pagerhist_clear(self);

View File

@ -52,6 +52,11 @@ linebuf_mark_line_clean(LineBuf *self, index_type y) {
self->line_attrs[y].has_dirty_text = false;
}
void
linebuf_set_line_has_image_placeholders(LineBuf *self, index_type y, bool val) {
self->line_attrs[y].has_image_placeholders = val;
}
void
linebuf_clear_attrs_and_dirty(LineBuf *self, index_type y) {
self->line_attrs[y].val = 0;

View File

@ -112,6 +112,7 @@ void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, H
void linebuf_mark_line_dirty(LineBuf *self, index_type y);
void linebuf_clear_attrs_and_dirty(LineBuf *self, index_type y);
void linebuf_mark_line_clean(LineBuf *self, index_type y);
void linebuf_set_line_has_image_placeholders(LineBuf *self, index_type y, bool val);
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
void linebuf_set_last_char_as_continuation(LineBuf *self, index_type y, bool continued);
bool linebuf_line_ends_with_continuation(LineBuf *self, index_type y);
@ -124,6 +125,7 @@ bool history_buf_endswith_wrap(HistoryBuf *self);
CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type num);
void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
void historybuf_mark_line_dirty(HistoryBuf *self, index_type y);
void historybuf_set_line_has_image_placeholders(HistoryBuf *self, index_type y, bool val);
void historybuf_refresh_sprite_positions(HistoryBuf *self);
void historybuf_clear(HistoryBuf *self);
void mark_text_in_line(PyObject *marker, Line *line);

View File

@ -39,7 +39,8 @@ static inline void parse_graphics_code(Screen *screen,
cell_x_offset = 'X',
cell_y_offset = 'Y',
z_index = 'z',
cursor_movement = 'C'
cursor_movement = 'C',
unicode_placement = 'U'
};
enum KEYS key = 'a';
@ -124,6 +125,9 @@ static inline void parse_graphics_code(Screen *screen,
case cursor_movement:
value_state = UINT;
break;
case unicode_placement:
value_state = UINT;
break;
default:
REPORT_ERROR("Malformed GraphicsCommand control block, invalid key "
"character: 0x%x",
@ -268,6 +272,7 @@ static inline void parse_graphics_code(Screen *screen,
U(cell_x_offset);
U(cell_y_offset);
U(cursor_movement);
U(unicode_placement);
default:
break;
}
@ -327,7 +332,7 @@ static inline void parse_graphics_code(Screen *screen,
REPORT_VA_COMMAND(
"s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI "
"si sI} y#",
"sI si sI} y#",
"graphics_command", "action", g.action, "delete_action", g.delete_action,
"transmission_type", g.transmission_type, "compressed", g.compressed,
"format", (unsigned int)g.format, "more", (unsigned int)g.more, "id",
@ -341,8 +346,9 @@ static inline void parse_graphics_code(Screen *screen,
"num_cells", (unsigned int)g.num_cells, "num_lines",
(unsigned int)g.num_lines, "cell_x_offset", (unsigned int)g.cell_x_offset,
"cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement",
(unsigned int)g.cursor_movement, "z_index", (int)g.z_index, "payload_sz",
g.payload_sz, payload, g.payload_sz);
(unsigned int)g.cursor_movement, "unicode_placement",
(unsigned int)g.unicode_placement, "z_index", (int)g.z_index,
"payload_sz", g.payload_sz, payload, g.payload_sz);
screen_handle_graphics_command(screen, &g, payload);
}

187
kitty/rowcolumn-diacritics.c generated Normal file
View File

@ -0,0 +1,187 @@
// Unicode data, built from the Unicode Standard 15.0.0
// Code generated by gen-wcwidth.py, DO NOT EDIT.
#include "data-types.h"
START_ALLOW_CASE_RANGE
#include "unicode-data.h"
int diacritic_to_num(char_type code) {
switch (code) {
case 0x305 ... 0x306:
return code - 0x305 + 1;
case 0x30d ... 0x30f:
return code - 0x30d + 2;
case 0x310 ... 0x311:
return code - 0x310 + 4;
case 0x312 ... 0x313:
return code - 0x312 + 5;
case 0x33d ... 0x340:
return code - 0x33d + 6;
case 0x346 ... 0x347:
return code - 0x346 + 9;
case 0x34a ... 0x34d:
return code - 0x34a + 10;
case 0x350 ... 0x353:
return code - 0x350 + 13;
case 0x357 ... 0x358:
return code - 0x357 + 16;
case 0x35b ... 0x35c:
return code - 0x35b + 17;
case 0x363 ... 0x370:
return code - 0x363 + 18;
case 0x483 ... 0x488:
return code - 0x483 + 31;
case 0x592 ... 0x596:
return code - 0x592 + 36;
case 0x597 ... 0x59a:
return code - 0x597 + 40;
case 0x59c ... 0x5a2:
return code - 0x59c + 43;
case 0x5a8 ... 0x5aa:
return code - 0x5a8 + 49;
case 0x5ab ... 0x5ad:
return code - 0x5ab + 51;
case 0x5af ... 0x5b0:
return code - 0x5af + 53;
case 0x5c4 ... 0x5c5:
return code - 0x5c4 + 54;
case 0x610 ... 0x618:
return code - 0x610 + 55;
case 0x657 ... 0x65c:
return code - 0x657 + 63;
case 0x65d ... 0x65f:
return code - 0x65d + 68;
case 0x6d6 ... 0x6dd:
return code - 0x6d6 + 70;
case 0x6df ... 0x6e3:
return code - 0x6df + 77;
case 0x6e4 ... 0x6e5:
return code - 0x6e4 + 81;
case 0x6e7 ... 0x6e9:
return code - 0x6e7 + 82;
case 0x6eb ... 0x6ed:
return code - 0x6eb + 84;
case 0x730 ... 0x731:
return code - 0x730 + 86;
case 0x732 ... 0x734:
return code - 0x732 + 87;
case 0x735 ... 0x737:
return code - 0x735 + 89;
case 0x73a ... 0x73b:
return code - 0x73a + 91;
case 0x73d ... 0x73e:
return code - 0x73d + 92;
case 0x73f ... 0x742:
return code - 0x73f + 93;
case 0x743 ... 0x744:
return code - 0x743 + 96;
case 0x745 ... 0x746:
return code - 0x745 + 97;
case 0x747 ... 0x748:
return code - 0x747 + 98;
case 0x749 ... 0x74b:
return code - 0x749 + 99;
case 0x7eb ... 0x7f2:
return code - 0x7eb + 101;
case 0x7f3 ... 0x7f4:
return code - 0x7f3 + 108;
case 0x816 ... 0x81a:
return code - 0x816 + 109;
case 0x81b ... 0x824:
return code - 0x81b + 113;
case 0x825 ... 0x828:
return code - 0x825 + 122;
case 0x829 ... 0x82e:
return code - 0x829 + 125;
case 0x951 ... 0x952:
return code - 0x951 + 130;
case 0x953 ... 0x955:
return code - 0x953 + 131;
case 0xf82 ... 0xf84:
return code - 0xf82 + 133;
case 0xf86 ... 0xf88:
return code - 0xf86 + 135;
case 0x135d ... 0x1360:
return code - 0x135d + 137;
case 0x17dd ... 0x17de:
return code - 0x17dd + 140;
case 0x193a ... 0x193b:
return code - 0x193a + 141;
case 0x1a17 ... 0x1a18:
return code - 0x1a17 + 142;
case 0x1a75 ... 0x1a7d:
return code - 0x1a75 + 143;
case 0x1b6b ... 0x1b6c:
return code - 0x1b6b + 151;
case 0x1b6d ... 0x1b74:
return code - 0x1b6d + 152;
case 0x1cd0 ... 0x1cd3:
return code - 0x1cd0 + 159;
case 0x1cda ... 0x1cdc:
return code - 0x1cda + 162;
case 0x1ce0 ... 0x1ce1:
return code - 0x1ce0 + 164;
case 0x1dc0 ... 0x1dc2:
return code - 0x1dc0 + 165;
case 0x1dc3 ... 0x1dca:
return code - 0x1dc3 + 167;
case 0x1dcb ... 0x1dcd:
return code - 0x1dcb + 174;
case 0x1dd1 ... 0x1de7:
return code - 0x1dd1 + 176;
case 0x1dfe ... 0x1dff:
return code - 0x1dfe + 198;
case 0x20d0 ... 0x20d2:
return code - 0x20d0 + 199;
case 0x20d4 ... 0x20d8:
return code - 0x20d4 + 201;
case 0x20db ... 0x20dd:
return code - 0x20db + 205;
case 0x20e1 ... 0x20e2:
return code - 0x20e1 + 207;
case 0x20e7 ... 0x20e8:
return code - 0x20e7 + 208;
case 0x20e9 ... 0x20ea:
return code - 0x20e9 + 209;
case 0x20f0 ... 0x20f1:
return code - 0x20f0 + 210;
case 0x2cef ... 0x2cf2:
return code - 0x2cef + 211;
case 0x2de0 ... 0x2e00:
return code - 0x2de0 + 214;
case 0xa66f ... 0xa670:
return code - 0xa66f + 246;
case 0xa67c ... 0xa67e:
return code - 0xa67c + 247;
case 0xa6f0 ... 0xa6f2:
return code - 0xa6f0 + 249;
case 0xa8e0 ... 0xa8f2:
return code - 0xa8e0 + 251;
case 0xaab0 ... 0xaab1:
return code - 0xaab0 + 269;
case 0xaab2 ... 0xaab4:
return code - 0xaab2 + 270;
case 0xaab7 ... 0xaab9:
return code - 0xaab7 + 272;
case 0xaabe ... 0xaac0:
return code - 0xaabe + 274;
case 0xaac1 ... 0xaac2:
return code - 0xaac1 + 276;
case 0xfe20 ... 0xfe27:
return code - 0xfe20 + 277;
case 0x10a0f ... 0x10a10:
return code - 0x10a0f + 284;
case 0x10a38 ... 0x10a39:
return code - 0x10a38 + 285;
case 0x1d185 ... 0x1d18a:
return code - 0x1d185 + 286;
case 0x1d1aa ... 0x1d1ae:
return code - 0x1d1aa + 291;
case 0x1d242 ... 0x1d245:
return code - 0x1d242 + 295;
}
return 0;
}
END_ALLOW_CASE_RANGE

View File

@ -374,6 +374,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
if (is_main) setup_cursor(cursor);
/* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor.after.x, cursor.after.y, cursor.is_beyond_content); */
setup_cursor(main_saved_cursor);
grman_remove_all_cell_images(self->main_grman);
grman_resize(self->main_grman, self->lines, lines, self->columns, columns);
// Resize alt linebuf
@ -382,6 +383,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
Py_CLEAR(self->alt_linebuf); self->alt_linebuf = n;
if (!is_main) setup_cursor(cursor);
setup_cursor(alt_saved_cursor);
grman_remove_all_cell_images(self->alt_grman);
grman_resize(self->alt_grman, self->lines, lines, self->columns, columns);
#undef setup_cursor
@ -432,6 +434,8 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
void
screen_rescale_images(Screen *self) {
grman_remove_all_cell_images(self->main_grman);
grman_remove_all_cell_images(self->alt_grman);
grman_rescale(self->main_grman, self->cell_size);
grman_rescale(self->alt_grman, self->cell_size);
}
@ -708,6 +712,9 @@ draw_codepoint(Screen *self, char_type och, bool from_input_stream) {
line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, self->active_hyperlink_id);
self->cursor->x++;
}
if (UNLIKELY(ch == IMAGE_PLACEHOLDER_CHAR)) {
linebuf_set_line_has_image_placeholders(self->linebuf, self->cursor->y, true);
}
self->is_dirty = true;
if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
@ -1621,6 +1628,25 @@ screen_fake_move_cursor_to_position(Screen *self, index_type start_x, index_type
// Editing {{{
// Remove all cell images from a portion of the screen and mark lines that
// contain image placeholders as dirty to make sure they are redrawn. This is
// needed when we perform commands that may move some lines without marking them
// as dirty (like screen_insert_lines) and at the same time don't move image
// references (i.e. unlike screen_scroll, which moves everything).
static void
screen_dirty_line_graphics(Screen *self, unsigned int top, unsigned int bottom) {
bool need_to_remove = false;
for (unsigned int y = top; y <= bottom; y++) {
if (self->linebuf->line_attrs[y].has_image_placeholders) {
need_to_remove = true;
linebuf_mark_line_dirty(self->linebuf, y);
self->is_dirty = true;
}
}
if (need_to_remove)
grman_remove_cell_images(self->grman, top, bottom);
}
void
screen_erase_in_line(Screen *self, unsigned int how, bool private) {
/*Erases a line in a specific way.
@ -1651,6 +1677,7 @@ screen_erase_in_line(Screen *self, unsigned int how, bool private) {
break;
}
if (n > 0) {
screen_dirty_line_graphics(self, self->cursor->y, self->cursor->y);
linebuf_init_line(self->linebuf, self->cursor->y);
if (private) {
line_clear_text(self->linebuf->line, s, n, BLANK_CHAR);
@ -1701,6 +1728,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
return;
}
if (b > a) {
if (how != 3) screen_dirty_line_graphics(self, a, b);
for (unsigned int i=a; i < b; i++) {
linebuf_init_line(self->linebuf, i);
if (private) {
@ -1728,6 +1756,7 @@ screen_insert_lines(Screen *self, unsigned int count) {
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) {
screen_dirty_line_graphics(self, top, bottom);
linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom);
self->is_dirty = true;
clear_selection(&self->selections);
@ -1751,6 +1780,7 @@ screen_delete_lines(Screen *self, unsigned int count) {
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) {
screen_dirty_line_graphics(self, top, bottom);
linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom);
self->is_dirty = true;
clear_selection(&self->selections);
@ -2277,6 +2307,134 @@ screen_has_marker(Screen *self) {
return self->marker != NULL;
}
static uint32_t diacritic_to_rowcolumn(combining_type m) {
char_type c = codepoint_for_mark(m);
return diacritic_to_num(c);
}
static uint32_t color_to_id(color_type c) {
// Just take 24 most significant bits of the color. This works both for
// 24-bit and 8-bit colors.
return (c >> 8) & 0xffffff;
}
// Scan the line and create cell images in place of unicode placeholders
// reserved for image placement.
static void
screen_render_line_graphics(Screen *self, Line *line, int32_t row) {
// If there are no image placeholders now, no need to rescan the line.
if (!line->attrs.has_image_placeholders)
return;
// Remove existing images.
grman_remove_cell_images(self->grman, row, row);
// The placeholders might be erased. We will update the attribute.
line->attrs.has_image_placeholders = false;
index_type i;
uint32_t run_length = 0;
uint32_t prev_img_id_lower24bits = 0;
uint32_t prev_placement_id = 0;
// Note that the following values are 1-based, zero means unknown or incorrect.
uint32_t prev_img_id_higher8bits = 0;
uint32_t prev_img_row = 0;
uint32_t prev_img_col = 0;
for (i = 0; i < line->xnum; i++) {
CPUCell *cpu_cell = line->cpu_cells + i;
GPUCell *gpu_cell = line->gpu_cells + i;
uint32_t cur_img_id_lower24bits = 0;
uint32_t cur_placement_id = 0;
uint32_t cur_img_id_higher8bits = 0;
uint32_t cur_img_row = 0;
uint32_t cur_img_col = 0;
if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) {
line->attrs.has_image_placeholders = true;
// The lower 24 bits of the image id are encoded in the foreground
// color, and the placement id is (optionally) in the underline color.
cur_img_id_lower24bits = color_to_id(gpu_cell->fg);
cur_placement_id = color_to_id(gpu_cell->decoration_fg);
// If the char has diacritics, use them as row and column indices.
if (cpu_cell->cc_idx[0])
cur_img_row = diacritic_to_rowcolumn(cpu_cell->cc_idx[0]);
if (cpu_cell->cc_idx[1])
cur_img_col = diacritic_to_rowcolumn(cpu_cell->cc_idx[1]);
// The third diacritic is used to encode the higher 8 bits of the
// image id (optional).
if (cpu_cell->cc_idx[2])
cur_img_id_higher8bits = diacritic_to_rowcolumn(cpu_cell->cc_idx[2]);
}
// The current run is continued if the lower 24 bits of the image id and
// the placement id are the same as in the previous cell and everything
// else is unknown or compatible with the previous cell.
if (run_length > 0 && cur_img_id_lower24bits == prev_img_id_lower24bits &&
cur_placement_id == prev_placement_id &&
(!cur_img_row || cur_img_row == prev_img_row) &&
(!cur_img_col || cur_img_col == prev_img_col + 1) &&
(!cur_img_id_higher8bits || cur_img_id_higher8bits == prev_img_id_higher8bits)) {
// This cell continues the current run.
run_length++;
// If some values are unknown, infer them from the previous cell.
cur_img_row = MAX(prev_img_row, 1u);
cur_img_col = prev_img_col + 1;
cur_img_id_higher8bits = MAX(prev_img_id_higher8bits, 1u);
} else {
// This cell breaks the current run. Render the current run if it
// has a non-zero length.
if (run_length > 0) {
uint32_t img_id = prev_img_id_lower24bits | (prev_img_id_higher8bits - 1) << 24;
grman_put_cell_image(
self->grman, row, i - run_length, img_id,
prev_placement_id, prev_img_col - run_length,
prev_img_row - 1, run_length, 1, self->cell_size);
}
// Start a new run.
if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) {
run_length = 1;
if (!cur_img_col) cur_img_col = 1;
if (!cur_img_row) cur_img_row = 1;
if (!cur_img_id_higher8bits) cur_img_id_higher8bits = 1;
}
}
prev_img_id_lower24bits = cur_img_id_lower24bits;
prev_img_id_higher8bits = cur_img_id_higher8bits;
prev_placement_id = cur_placement_id;
prev_img_row = cur_img_row;
prev_img_col = cur_img_col;
}
if (run_length > 0) {
// Render the last run.
uint32_t img_id = prev_img_id_lower24bits | (prev_img_id_higher8bits - 1) << 24;
grman_put_cell_image(self->grman, row, i - run_length, img_id,
prev_placement_id, prev_img_col - run_length,
prev_img_row - 1, run_length, 1, self->cell_size);
}
}
// This functions is similar to screen_update_cell_data, but it only updates
// line graphics (cell images) and then marks lines as clean. It's used
// exclusively for testing unicode placeholders.
static void
screen_update_only_line_graphics_data(Screen *self) {
unsigned int history_line_added_count = self->history_line_added_count;
index_type lnum;
if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count);
screen_reset_dirty(self);
self->scroll_changed = false;
for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) {
lnum = self->scrolled_by - 1 - y;
historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
if (self->historybuf->line->attrs.has_dirty_text) {
screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by);
historybuf_mark_line_clean(self->historybuf, lnum);
}
}
for (index_type y = self->scrolled_by; y < self->lines; y++) {
lnum = y - self->scrolled_by;
linebuf_init_line(self->linebuf, lnum);
if (self->linebuf->line->attrs.has_dirty_text) {
screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by);
linebuf_mark_line_clean(self->linebuf, lnum);
}
}
}
void
screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data, bool cursor_has_moved) {
@ -2291,6 +2449,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
if (self->historybuf->line->attrs.has_dirty_text) {
render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures);
screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by);
if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line);
historybuf_mark_line_clean(self->historybuf, lnum);
}
@ -2302,6 +2461,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
if (self->linebuf->line->attrs.has_dirty_text ||
(cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor_y == lnum))) {
render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures);
screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by);
if (self->linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line);
linebuf_mark_line_clean(self->linebuf, lnum);
@ -4059,6 +4219,8 @@ line_edge_colors(Screen *self, PyObject *a UNUSED) {
return Py_BuildValue("kk", (unsigned long)left, (unsigned long)right);
}
WRAP0(update_only_line_graphics_data)
#define MND(name, args) {#name, (PyCFunction)name, args, #name},
#define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O)
@ -4144,6 +4306,7 @@ static PyMethodDef methods[] = {
MND(set_marker, METH_VARARGS)
MND(marked_cells, METH_NOARGS)
MND(scroll_to_next_mark, METH_VARARGS)
MND(update_only_line_graphics_data, METH_NOARGS)
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},
{NULL} /* Sentinel */

View File

@ -5,6 +5,9 @@
static const combining_type VS15 = 1364, VS16 = 1365;
// END_KNOWN_MARKS
// Converts row/column diacritics to numbers.
int diacritic_to_num(char_type ch);
bool is_combining_char(char_type ch);
bool is_ignored_char(char_type ch);
bool is_word_char(char_type ch);

View File

@ -114,21 +114,21 @@ def sl(payload, **kw):
return s, g, pl, sl
def put_helpers(self, cw, ch):
def put_helpers(self, cw, ch, cols=10, lines=5):
iid = 0
def create_screen():
s = self.create_screen(10, 5, cell_width=cw, cell_height=ch)
s = self.create_screen(cols, lines, cell_width=cw, cell_height=ch)
return s, 2 / s.columns, 2 / s.lines
def put_cmd(
z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0,
height=0, cell_x_off=0, cell_y_off=0, placement_id=0,
cursor_movement=0
cursor_movement=0, unicode_placeholder=0
):
return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d,C=%d' % (
return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d,C=%d,U=%d' % (
z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off,
cell_y_off, placement_id, cursor_movement
cell_y_off, placement_id, cursor_movement, unicode_placeholder
)
def put_image(screen, w, h, **kw):
@ -515,6 +515,232 @@ def test_image_put(self):
s.reset()
self.assertEqual(s.grman.disk_cache.total_size, 0)
def test_unicode_placeholders(self):
# This test tests basic image placement using using unicode placeholders
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
# Upload two images.
put_image(s, 20, 20, num_cols=4, num_lines=2, unicode_placeholder=1, id=42)
put_image(s, 10, 20, num_cols=4, num_lines=2, unicode_placeholder=1, id=(42<<16) + (43<<8) + 44)
# The references are virtual, so no visible refs yet.
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 0)
# A reminder of row/column diacritics meaning (assuming 0-based):
# \u0305 -> 0
# \u030D -> 1
# \u030E -> 2
# \u0310 -> 3
# Now print the placeholders for the first image.
# Encode the id as an 8-bit color.
s.apply_sgr("38;5;42")
# These two characters will become one 2x1 ref.
s.draw("\U0010EEEE\u0305\u0305\U0010EEEE\u0305\u030D")
# These two characters will be two separate refs (not contiguous).
s.draw("\U0010EEEE\u0305\u0305\U0010EEEE\u0305\u030E")
s.cursor_back(4)
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 3)
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 0.5, 'bottom': 0.5})
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 0.25, 'bottom': 0.5})
self.ae(refs[2]['src_rect'], {'left': 0.5, 'top': 0.0, 'right': 0.75, 'bottom': 0.5})
# Erase the line.
s.erase_in_line(2)
# There must be 0 refs after the line is erased.
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 0)
# Now test encoding IDs with the 24-bit color.
# The first image, 1x1
s.apply_sgr("38;2;0;0;42")
s.draw("\U0010EEEE\u0305\u0305")
# The second image, 2x1
s.apply_sgr("38;2;42;43;44")
s.draw("\U0010EEEE\u0305\u030D\U0010EEEE\u0305\u030E")
s.cursor_back(2)
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 2)
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 0.25, 'bottom': 0.5})
# The second ref spans the whole widths of the second image because it's
# fit to height and centered in a 4x2 box (specified in put_image).
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 0.5})
# Erase the line.
s.erase_in_line(2)
# Now test implicit column numbers.
# We will mix implicit and explicit column/row specifications, but they
# will be combine into just two references.
s.apply_sgr("38;5;42")
# full row 0 of the first image
s.draw("\U0010EEEE\u0305\u0305\U0010EEEE\u0305\U0010EEEE\U0010EEEE\u0305")
# full row 1 of the first image
s.draw("\U0010EEEE\u030D\U0010EEEE\U0010EEEE\U0010EEEE\u030D\u0310")
s.cursor_back(8)
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 2)
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 0.5})
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.5, 'right': 1.0, 'bottom': 1.0})
def test_unicode_placeholders_3rd_combining_char(self):
# This test tests that we can use the 3rd diacritic for the most
# significant byte
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
# Upload two images.
put_image(s, 20, 20, num_cols=4, num_lines=2, unicode_placeholder=1, id=42)
put_image(s, 20, 10, num_cols=4, num_lines=1, unicode_placeholder=1, id=(42 << 24) + 43)
# This one will have id=43, which does not exist.
s.apply_sgr("38;2;0;0;43")
s.draw("\U0010EEEE\u0305\U0010EEEE\U0010EEEE\U0010EEEE")
s.cursor_back(4)
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 0)
s.erase_in_line(2)
# This one will have id=42. We explicitly specify that the most
# significant byte is 0 (third \u305). Specifying the zero byte like
# this is not necessary but is correct.
s.apply_sgr("38;2;0;0;42")
s.draw("\U0010EEEE\u0305\u0305\u0305\U0010EEEE\u0305\u030D\u0305")
# This is the second image.
# \u059C -> 42
s.apply_sgr("38;2;0;0;43")
s.draw("\U0010EEEE\u0305\u0305\u059C\U0010EEEE\u0305\u030D\u059C")
# Check that we can continue by using implicit row/column specification.
s.draw("\U0010EEEE\u0305\U0010EEEE")
s.cursor_back(6)
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 2)
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 0.5, 'bottom': 0.5})
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.0})
s.erase_in_line(2)
# Now test the 8-bit color mode. Using the third diacritic, we can
# specify 16 bits: the most significant byte and the least significant
# byte.
s.apply_sgr("38;5;42")
s.draw("\U0010EEEE\u0305\u0305\u0305\U0010EEEE")
s.apply_sgr("38;5;43")
s.draw("\U0010EEEE\u0305\u0305\u059C\U0010EEEE\U0010EEEE\u0305\U0010EEEE")
s.cursor_back(6)
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 2)
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 0.5, 'bottom': 0.5})
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.0})
def test_unicode_placeholders_multiple_placements(self):
# Here we test placement specification via underline color.
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
put_image(s, 20, 20, num_cols=1, num_lines=1, placement_id=1, unicode_placeholder=1, id=42)
put_ref(s, id=42, num_cols=2, num_lines=1, placement_id=22, unicode_placeholder=1)
put_ref(s, id=42, num_cols=4, num_lines=2, placement_id=44, unicode_placeholder=1)
# The references are virtual, so no visible refs yet.
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 0)
# Draw the first row of each placement.
s.apply_sgr("38;5;42")
s.apply_sgr("58;5;1")
s.draw("\U0010EEEE\u0305")
s.apply_sgr("58;5;22")
s.draw("\U0010EEEE\u0305\U0010EEEE\u0305")
s.apply_sgr("58;5;44")
s.draw("\U0010EEEE\u0305\U0010EEEE\u0305\U0010EEEE\u0305\U0010EEEE\u0305")
s.update_only_line_graphics_data()
refs = layers(s)
self.ae(len(refs), 3)
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.5})
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.0})
self.ae(refs[2]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 0.5})
def test_unicode_placeholders_scroll(self):
# Here we test scrolling of a region. We'll draw an image spanning 8
# rows and then scroll only the middle part of this image. Each
# reference corresponds to one row.
cw, ch = 5, 10
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch, lines=8)
put_image(s, 5, 80, num_cols=1, num_lines=8, unicode_placeholder=1, id=42)
s.apply_sgr("38;5;42")
s.cursor_position(1, 0)
s.draw("\U0010EEEE\u0305\n")
s.cursor_position(2, 0)
s.draw("\U0010EEEE\u030D\n")
s.cursor_position(3, 0)
s.draw("\U0010EEEE\u030E\n")
s.cursor_position(4, 0)
s.draw("\U0010EEEE\u0310\n")
s.cursor_position(5, 0)
s.draw("\U0010EEEE\u0312\n")
s.cursor_position(6, 0)
s.draw("\U0010EEEE\u033D\n")
s.cursor_position(7, 0)
s.draw("\U0010EEEE\u033E\n")
s.cursor_position(8, 0)
s.draw("\U0010EEEE\u033F")
# Each line will contain a part of the image.
s.update_only_line_graphics_data()
refs = layers(s)
refs = sorted(refs, key=lambda r: r['src_rect']['top'])
self.ae(len(refs), 8)
for i in range(8):
self.ae(refs[i]['src_rect'], {'left': 0.0, 'top': 0.125*i, 'right': 1.0, 'bottom': 0.125*(i + 1)})
self.ae(refs[i]['dest_rect']['top'], 1 - 0.25*i)
# Now set margins to lines 3 and 6.
s.set_margins(3, 6) # 1-based indexing
# Scroll two lines down (i.e. move lines 3..6 up).
# Lines 3 and 4 will be erased.
s.cursor_position(6, 0)
s.index()
s.index()
s.update_only_line_graphics_data()
refs = layers(s)
refs = sorted(refs, key=lambda r: r['src_rect']['top'])
self.ae(len(refs), 6)
# Lines 1 and 2 are outside of the region, not scrolled.
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 0.125})
self.ae(refs[0]['dest_rect']['top'], 1.0)
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.125*1, 'right': 1.0, 'bottom': 0.125*2})
self.ae(refs[1]['dest_rect']['top'], 1.0 - 0.25*1)
# Lines 3 and 4 are erased.
# Lines 5 and 6 are now higher.
self.ae(refs[2]['src_rect'], {'left': 0.0, 'top': 0.125*4, 'right': 1.0, 'bottom': 0.125*5})
self.ae(refs[2]['dest_rect']['top'], 1.0 - 0.25*2)
self.ae(refs[3]['src_rect'], {'left': 0.0, 'top': 0.125*5, 'right': 1.0, 'bottom': 0.125*6})
self.ae(refs[3]['dest_rect']['top'], 1.0 - 0.25*3)
# Lines 7 and 8 are outside of the region.
self.ae(refs[4]['src_rect'], {'left': 0.0, 'top': 0.125*6, 'right': 1.0, 'bottom': 0.125*7})
self.ae(refs[4]['dest_rect']['top'], 1.0 - 0.25*6)
self.ae(refs[5]['src_rect'], {'left': 0.0, 'top': 0.125*7, 'right': 1.0, 'bottom': 0.125*8})
self.ae(refs[5]['dest_rect']['top'], 1.0 - 0.25*7)
# Now scroll three lines up (i.e. move lines 5..6 down).
# Line 6 will be erased.
s.cursor_position(3, 0)
s.reverse_index()
s.reverse_index()
s.reverse_index()
s.update_only_line_graphics_data()
refs = layers(s)
refs = sorted(refs, key=lambda r: r['src_rect']['top'])
self.ae(len(refs), 5)
# Lines 1 and 2 are outside of the region, not scrolled.
self.ae(refs[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 0.125})
self.ae(refs[0]['dest_rect']['top'], 1.0)
self.ae(refs[1]['src_rect'], {'left': 0.0, 'top': 0.125*1, 'right': 1.0, 'bottom': 0.125*2})
self.ae(refs[1]['dest_rect']['top'], 1.0 - 0.25*1)
# Lines 3, 4 and 6 are erased.
# Line 5 is now lower.
self.ae(refs[2]['src_rect'], {'left': 0.0, 'top': 0.125*4, 'right': 1.0, 'bottom': 0.125*5})
self.ae(refs[2]['dest_rect']['top'], 1.0 - 0.25*5)
# Lines 7 and 8 are outside of the region.
self.ae(refs[3]['src_rect'], {'left': 0.0, 'top': 0.125*6, 'right': 1.0, 'bottom': 0.125*7})
self.ae(refs[3]['dest_rect']['top'], 1.0 - 0.25*6)
self.ae(refs[4]['src_rect'], {'left': 0.0, 'top': 0.125*7, 'right': 1.0, 'bottom': 0.125*8})
self.ae(refs[4]['dest_rect']['top'], 1.0 - 0.25*7)
def test_gr_scroll(self):
cw, ch = 10, 20
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)

View File

@ -410,7 +410,7 @@ def c(**k):
for f in 'action delete_action transmission_type compressed'.split():
k.setdefault(f, b'\0')
for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width cursor_movement'
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet').split():
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet unicode_placement').split():
k.setdefault(f, 0)
p = k.pop('payload', '').encode('utf-8')
k['payload_sz'] = len(p)

310
rowcolumn-diacritics.txt Normal file
View File

@ -0,0 +1,310 @@
# This file lists the diacritics used to indicate row/column numbers for
# Unicode terminal image placeholders. It is derived from UnicodeData.txt for
# Unicode 6.0.0 (chosen somewhat arbitrarily: it's old enough, but still
# contains more than 255 suitable combining chars) using the following
# command:
#
# cat UnicodeData.txt | grep "Mn;230;NSM;;" | grep -v "0300\|0301\|0302\|0303\|0304\|0306\|0307\|0308\|0309\|030A\|030B\|030C\|030F\|0311\|0313\|0314\|0342\|0653\|0654"
#
# That is, we use combining chars of the same combining class 230 (above the
# base character) that do not have decomposition mappings, and we also remove
# some characters that may be fused with other characters during normalization,
# like 0041 0300 -> 00C0 which is À (A with grave).
#
0305;COMBINING OVERLINE;Mn;230;NSM;;;;;N;NON-SPACING OVERSCORE;;;;
030D;COMBINING VERTICAL LINE ABOVE;Mn;230;NSM;;;;;N;NON-SPACING VERTICAL LINE ABOVE;;;;
030E;COMBINING DOUBLE VERTICAL LINE ABOVE;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE VERTICAL LINE ABOVE;;;;
0310;COMBINING CANDRABINDU;Mn;230;NSM;;;;;N;NON-SPACING CANDRABINDU;;;;
0312;COMBINING TURNED COMMA ABOVE;Mn;230;NSM;;;;;N;NON-SPACING TURNED COMMA ABOVE;;;;
033D;COMBINING X ABOVE;Mn;230;NSM;;;;;N;NON-SPACING X ABOVE;;;;
033E;COMBINING VERTICAL TILDE;Mn;230;NSM;;;;;N;NON-SPACING VERTICAL TILDE;;;;
033F;COMBINING DOUBLE OVERLINE;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE OVERSCORE;;;;
0346;COMBINING BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;;
034A;COMBINING NOT TILDE ABOVE;Mn;230;NSM;;;;;N;;;;;
034B;COMBINING HOMOTHETIC ABOVE;Mn;230;NSM;;;;;N;;;;;
034C;COMBINING ALMOST EQUAL TO ABOVE;Mn;230;NSM;;;;;N;;;;;
0350;COMBINING RIGHT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;;
0351;COMBINING LEFT HALF RING ABOVE;Mn;230;NSM;;;;;N;;;;;
0352;COMBINING FERMATA;Mn;230;NSM;;;;;N;;;;;
0357;COMBINING RIGHT HALF RING ABOVE;Mn;230;NSM;;;;;N;;;;;
035B;COMBINING ZIGZAG ABOVE;Mn;230;NSM;;;;;N;;;;;
0363;COMBINING LATIN SMALL LETTER A;Mn;230;NSM;;;;;N;;;;;
0364;COMBINING LATIN SMALL LETTER E;Mn;230;NSM;;;;;N;;;;;
0365;COMBINING LATIN SMALL LETTER I;Mn;230;NSM;;;;;N;;;;;
0366;COMBINING LATIN SMALL LETTER O;Mn;230;NSM;;;;;N;;;;;
0367;COMBINING LATIN SMALL LETTER U;Mn;230;NSM;;;;;N;;;;;
0368;COMBINING LATIN SMALL LETTER C;Mn;230;NSM;;;;;N;;;;;
0369;COMBINING LATIN SMALL LETTER D;Mn;230;NSM;;;;;N;;;;;
036A;COMBINING LATIN SMALL LETTER H;Mn;230;NSM;;;;;N;;;;;
036B;COMBINING LATIN SMALL LETTER M;Mn;230;NSM;;;;;N;;;;;
036C;COMBINING LATIN SMALL LETTER R;Mn;230;NSM;;;;;N;;;;;
036D;COMBINING LATIN SMALL LETTER T;Mn;230;NSM;;;;;N;;;;;
036E;COMBINING LATIN SMALL LETTER V;Mn;230;NSM;;;;;N;;;;;
036F;COMBINING LATIN SMALL LETTER X;Mn;230;NSM;;;;;N;;;;;
0483;COMBINING CYRILLIC TITLO;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING TITLO;;;;
0484;COMBINING CYRILLIC PALATALIZATION;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING PALATALIZATION;;;;
0485;COMBINING CYRILLIC DASIA PNEUMATA;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING DASIA PNEUMATA;;;;
0486;COMBINING CYRILLIC PSILI PNEUMATA;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING PSILI PNEUMATA;;;;
0487;COMBINING CYRILLIC POKRYTIE;Mn;230;NSM;;;;;N;;;;;
0592;HEBREW ACCENT SEGOL;Mn;230;NSM;;;;;N;;;;;
0593;HEBREW ACCENT SHALSHELET;Mn;230;NSM;;;;;N;;;;;
0594;HEBREW ACCENT ZAQEF QATAN;Mn;230;NSM;;;;;N;;;;;
0595;HEBREW ACCENT ZAQEF GADOL;Mn;230;NSM;;;;;N;;;;;
0597;HEBREW ACCENT REVIA;Mn;230;NSM;;;;;N;;;;;
0598;HEBREW ACCENT ZARQA;Mn;230;NSM;;;;;N;;;;;
0599;HEBREW ACCENT PASHTA;Mn;230;NSM;;;;;N;;;;;
059C;HEBREW ACCENT GERESH;Mn;230;NSM;;;;;N;;;;;
059D;HEBREW ACCENT GERESH MUQDAM;Mn;230;NSM;;;;;N;;;;;
059E;HEBREW ACCENT GERSHAYIM;Mn;230;NSM;;;;;N;;;;;
059F;HEBREW ACCENT QARNEY PARA;Mn;230;NSM;;;;;N;;;;;
05A0;HEBREW ACCENT TELISHA GEDOLA;Mn;230;NSM;;;;;N;;;;;
05A1;HEBREW ACCENT PAZER;Mn;230;NSM;;;;;N;;;;;
05A8;HEBREW ACCENT QADMA;Mn;230;NSM;;;;;N;;;;;
05A9;HEBREW ACCENT TELISHA QETANA;Mn;230;NSM;;;;;N;;;;;
05AB;HEBREW ACCENT OLE;Mn;230;NSM;;;;;N;;;;;
05AC;HEBREW ACCENT ILUY;Mn;230;NSM;;;;;N;;;;;
05AF;HEBREW MARK MASORA CIRCLE;Mn;230;NSM;;;;;N;;;;;
05C4;HEBREW MARK UPPER DOT;Mn;230;NSM;;;;;N;;;;;
0610;ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM;Mn;230;NSM;;;;;N;;;;;
0611;ARABIC SIGN ALAYHE ASSALLAM;Mn;230;NSM;;;;;N;;;;;
0612;ARABIC SIGN RAHMATULLAH ALAYHE;Mn;230;NSM;;;;;N;;;;;
0613;ARABIC SIGN RADI ALLAHOU ANHU;Mn;230;NSM;;;;;N;;;;;
0614;ARABIC SIGN TAKHALLUS;Mn;230;NSM;;;;;N;;;;;
0615;ARABIC SMALL HIGH TAH;Mn;230;NSM;;;;;N;;;;;
0616;ARABIC SMALL HIGH LIGATURE ALEF WITH LAM WITH YEH;Mn;230;NSM;;;;;N;;;;;
0617;ARABIC SMALL HIGH ZAIN;Mn;230;NSM;;;;;N;;;;;
0657;ARABIC INVERTED DAMMA;Mn;230;NSM;;;;;N;;;;;
0658;ARABIC MARK NOON GHUNNA;Mn;230;NSM;;;;;N;;;;;
0659;ARABIC ZWARAKAY;Mn;230;NSM;;;;;N;;;;;
065A;ARABIC VOWEL SIGN SMALL V ABOVE;Mn;230;NSM;;;;;N;;;;;
065B;ARABIC VOWEL SIGN INVERTED SMALL V ABOVE;Mn;230;NSM;;;;;N;;;;;
065D;ARABIC REVERSED DAMMA;Mn;230;NSM;;;;;N;;;;;
065E;ARABIC FATHA WITH TWO DOTS;Mn;230;NSM;;;;;N;;;;;
06D6;ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA;Mn;230;NSM;;;;;N;;;;;
06D7;ARABIC SMALL HIGH LIGATURE QAF WITH LAM WITH ALEF MAKSURA;Mn;230;NSM;;;;;N;;;;;
06D8;ARABIC SMALL HIGH MEEM INITIAL FORM;Mn;230;NSM;;;;;N;;;;;
06D9;ARABIC SMALL HIGH LAM ALEF;Mn;230;NSM;;;;;N;;;;;
06DA;ARABIC SMALL HIGH JEEM;Mn;230;NSM;;;;;N;;;;;
06DB;ARABIC SMALL HIGH THREE DOTS;Mn;230;NSM;;;;;N;;;;;
06DC;ARABIC SMALL HIGH SEEN;Mn;230;NSM;;;;;N;;;;;
06DF;ARABIC SMALL HIGH ROUNDED ZERO;Mn;230;NSM;;;;;N;;;;;
06E0;ARABIC SMALL HIGH UPRIGHT RECTANGULAR ZERO;Mn;230;NSM;;;;;N;;;;;
06E1;ARABIC SMALL HIGH DOTLESS HEAD OF KHAH;Mn;230;NSM;;;;;N;;;;;
06E2;ARABIC SMALL HIGH MEEM ISOLATED FORM;Mn;230;NSM;;;;;N;;;;;
06E4;ARABIC SMALL HIGH MADDA;Mn;230;NSM;;;;;N;;;;;
06E7;ARABIC SMALL HIGH YEH;Mn;230;NSM;;;;;N;;;;;
06E8;ARABIC SMALL HIGH NOON;Mn;230;NSM;;;;;N;;;;;
06EB;ARABIC EMPTY CENTRE HIGH STOP;Mn;230;NSM;;;;;N;;;;;
06EC;ARABIC ROUNDED HIGH STOP WITH FILLED CENTRE;Mn;230;NSM;;;;;N;;;;;
0730;SYRIAC PTHAHA ABOVE;Mn;230;NSM;;;;;N;;;;;
0732;SYRIAC PTHAHA DOTTED;Mn;230;NSM;;;;;N;;;;;
0733;SYRIAC ZQAPHA ABOVE;Mn;230;NSM;;;;;N;;;;;
0735;SYRIAC ZQAPHA DOTTED;Mn;230;NSM;;;;;N;;;;;
0736;SYRIAC RBASA ABOVE;Mn;230;NSM;;;;;N;;;;;
073A;SYRIAC HBASA ABOVE;Mn;230;NSM;;;;;N;;;;;
073D;SYRIAC ESASA ABOVE;Mn;230;NSM;;;;;N;;;;;
073F;SYRIAC RWAHA;Mn;230;NSM;;;;;N;;;;;
0740;SYRIAC FEMININE DOT;Mn;230;NSM;;;;;N;;;;;
0741;SYRIAC QUSHSHAYA;Mn;230;NSM;;;;;N;;;;;
0743;SYRIAC TWO VERTICAL DOTS ABOVE;Mn;230;NSM;;;;;N;;;;;
0745;SYRIAC THREE DOTS ABOVE;Mn;230;NSM;;;;;N;;;;;
0747;SYRIAC OBLIQUE LINE ABOVE;Mn;230;NSM;;;;;N;;;;;
0749;SYRIAC MUSIC;Mn;230;NSM;;;;;N;;;;;
074A;SYRIAC BARREKH;Mn;230;NSM;;;;;N;;;;;
07EB;NKO COMBINING SHORT HIGH TONE;Mn;230;NSM;;;;;N;;;;;
07EC;NKO COMBINING SHORT LOW TONE;Mn;230;NSM;;;;;N;;;;;
07ED;NKO COMBINING SHORT RISING TONE;Mn;230;NSM;;;;;N;;;;;
07EE;NKO COMBINING LONG DESCENDING TONE;Mn;230;NSM;;;;;N;;;;;
07EF;NKO COMBINING LONG HIGH TONE;Mn;230;NSM;;;;;N;;;;;
07F0;NKO COMBINING LONG LOW TONE;Mn;230;NSM;;;;;N;;;;;
07F1;NKO COMBINING LONG RISING TONE;Mn;230;NSM;;;;;N;;;;;
07F3;NKO COMBINING DOUBLE DOT ABOVE;Mn;230;NSM;;;;;N;;;;;
0816;SAMARITAN MARK IN;Mn;230;NSM;;;;;N;;;;;
0817;SAMARITAN MARK IN-ALAF;Mn;230;NSM;;;;;N;;;;;
0818;SAMARITAN MARK OCCLUSION;Mn;230;NSM;;;;;N;;;;;
0819;SAMARITAN MARK DAGESH;Mn;230;NSM;;;;;N;;;;;
081B;SAMARITAN MARK EPENTHETIC YUT;Mn;230;NSM;;;;;N;;;;;
081C;SAMARITAN VOWEL SIGN LONG E;Mn;230;NSM;;;;;N;;;;;
081D;SAMARITAN VOWEL SIGN E;Mn;230;NSM;;;;;N;;;;;
081E;SAMARITAN VOWEL SIGN OVERLONG AA;Mn;230;NSM;;;;;N;;;;;
081F;SAMARITAN VOWEL SIGN LONG AA;Mn;230;NSM;;;;;N;;;;;
0820;SAMARITAN VOWEL SIGN AA;Mn;230;NSM;;;;;N;;;;;
0821;SAMARITAN VOWEL SIGN OVERLONG A;Mn;230;NSM;;;;;N;;;;;
0822;SAMARITAN VOWEL SIGN LONG A;Mn;230;NSM;;;;;N;;;;;
0823;SAMARITAN VOWEL SIGN A;Mn;230;NSM;;;;;N;;;;;
0825;SAMARITAN VOWEL SIGN SHORT A;Mn;230;NSM;;;;;N;;;;;
0826;SAMARITAN VOWEL SIGN LONG U;Mn;230;NSM;;;;;N;;;;;
0827;SAMARITAN VOWEL SIGN U;Mn;230;NSM;;;;;N;;;;;
0829;SAMARITAN VOWEL SIGN LONG I;Mn;230;NSM;;;;;N;;;;;
082A;SAMARITAN VOWEL SIGN I;Mn;230;NSM;;;;;N;;;;;
082B;SAMARITAN VOWEL SIGN O;Mn;230;NSM;;;;;N;;;;;
082C;SAMARITAN VOWEL SIGN SUKUN;Mn;230;NSM;;;;;N;;;;;
082D;SAMARITAN MARK NEQUDAA;Mn;230;NSM;;;;;N;;;;;
0951;DEVANAGARI STRESS SIGN UDATTA;Mn;230;NSM;;;;;N;;;;;
0953;DEVANAGARI GRAVE ACCENT;Mn;230;NSM;;;;;N;;;;;
0954;DEVANAGARI ACUTE ACCENT;Mn;230;NSM;;;;;N;;;;;
0F82;TIBETAN SIGN NYI ZLA NAA DA;Mn;230;NSM;;;;;N;TIBETAN CANDRABINDU WITH ORNAMENT;;;;
0F83;TIBETAN SIGN SNA LDAN;Mn;230;NSM;;;;;N;TIBETAN CANDRABINDU;;;;
0F86;TIBETAN SIGN LCI RTAGS;Mn;230;NSM;;;;;N;;;;;
0F87;TIBETAN SIGN YANG RTAGS;Mn;230;NSM;;;;;N;;;;;
135D;ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK;Mn;230;NSM;;;;;N;;;;;
135E;ETHIOPIC COMBINING VOWEL LENGTH MARK;Mn;230;NSM;;;;;N;;;;;
135F;ETHIOPIC COMBINING GEMINATION MARK;Mn;230;NSM;;;;;N;;;;;
17DD;KHMER SIGN ATTHACAN;Mn;230;NSM;;;;;N;;;;;
193A;LIMBU SIGN KEMPHRENG;Mn;230;NSM;;;;;N;;;;;
1A17;BUGINESE VOWEL SIGN I;Mn;230;NSM;;;;;N;;;;;
1A75;TAI THAM SIGN TONE-1;Mn;230;NSM;;;;;N;;;;;
1A76;TAI THAM SIGN TONE-2;Mn;230;NSM;;;;;N;;;;;
1A77;TAI THAM SIGN KHUEN TONE-3;Mn;230;NSM;;;;;N;;;;;
1A78;TAI THAM SIGN KHUEN TONE-4;Mn;230;NSM;;;;;N;;;;;
1A79;TAI THAM SIGN KHUEN TONE-5;Mn;230;NSM;;;;;N;;;;;
1A7A;TAI THAM SIGN RA HAAM;Mn;230;NSM;;;;;N;;;;;
1A7B;TAI THAM SIGN MAI SAM;Mn;230;NSM;;;;;N;;;;;
1A7C;TAI THAM SIGN KHUEN-LUE KARAN;Mn;230;NSM;;;;;N;;;;;
1B6B;BALINESE MUSICAL SYMBOL COMBINING TEGEH;Mn;230;NSM;;;;;N;;;;;
1B6D;BALINESE MUSICAL SYMBOL COMBINING KEMPUL;Mn;230;NSM;;;;;N;;;;;
1B6E;BALINESE MUSICAL SYMBOL COMBINING KEMPLI;Mn;230;NSM;;;;;N;;;;;
1B6F;BALINESE MUSICAL SYMBOL COMBINING JEGOGAN;Mn;230;NSM;;;;;N;;;;;
1B70;BALINESE MUSICAL SYMBOL COMBINING KEMPUL WITH JEGOGAN;Mn;230;NSM;;;;;N;;;;;
1B71;BALINESE MUSICAL SYMBOL COMBINING KEMPLI WITH JEGOGAN;Mn;230;NSM;;;;;N;;;;;
1B72;BALINESE MUSICAL SYMBOL COMBINING BENDE;Mn;230;NSM;;;;;N;;;;;
1B73;BALINESE MUSICAL SYMBOL COMBINING GONG;Mn;230;NSM;;;;;N;;;;;
1CD0;VEDIC TONE KARSHANA;Mn;230;NSM;;;;;N;;;;;
1CD1;VEDIC TONE SHARA;Mn;230;NSM;;;;;N;;;;;
1CD2;VEDIC TONE PRENKHA;Mn;230;NSM;;;;;N;;;;;
1CDA;VEDIC TONE DOUBLE SVARITA;Mn;230;NSM;;;;;N;;;;;
1CDB;VEDIC TONE TRIPLE SVARITA;Mn;230;NSM;;;;;N;;;;;
1CE0;VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA;Mn;230;NSM;;;;;N;;;;;
1DC0;COMBINING DOTTED GRAVE ACCENT;Mn;230;NSM;;;;;N;;;;;
1DC1;COMBINING DOTTED ACUTE ACCENT;Mn;230;NSM;;;;;N;;;;;
1DC3;COMBINING SUSPENSION MARK;Mn;230;NSM;;;;;N;;;;;
1DC4;COMBINING MACRON-ACUTE;Mn;230;NSM;;;;;N;;;;;
1DC5;COMBINING GRAVE-MACRON;Mn;230;NSM;;;;;N;;;;;
1DC6;COMBINING MACRON-GRAVE;Mn;230;NSM;;;;;N;;;;;
1DC7;COMBINING ACUTE-MACRON;Mn;230;NSM;;;;;N;;;;;
1DC8;COMBINING GRAVE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;;
1DC9;COMBINING ACUTE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;;
1DCB;COMBINING BREVE-MACRON;Mn;230;NSM;;;;;N;;;;;
1DCC;COMBINING MACRON-BREVE;Mn;230;NSM;;;;;N;;;;;
1DD1;COMBINING UR ABOVE;Mn;230;NSM;;;;;N;;;;;
1DD2;COMBINING US ABOVE;Mn;230;NSM;;;;;N;;;;;
1DD3;COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE;Mn;230;NSM;;;;;N;;;;;
1DD4;COMBINING LATIN SMALL LETTER AE;Mn;230;NSM;;;;;N;;;;;
1DD5;COMBINING LATIN SMALL LETTER AO;Mn;230;NSM;;;;;N;;;;;
1DD6;COMBINING LATIN SMALL LETTER AV;Mn;230;NSM;;;;;N;;;;;
1DD7;COMBINING LATIN SMALL LETTER C CEDILLA;Mn;230;NSM;;;;;N;;;;;
1DD8;COMBINING LATIN SMALL LETTER INSULAR D;Mn;230;NSM;;;;;N;;;;;
1DD9;COMBINING LATIN SMALL LETTER ETH;Mn;230;NSM;;;;;N;;;;;
1DDA;COMBINING LATIN SMALL LETTER G;Mn;230;NSM;;;;;N;;;;;
1DDB;COMBINING LATIN LETTER SMALL CAPITAL G;Mn;230;NSM;;;;;N;;;;;
1DDC;COMBINING LATIN SMALL LETTER K;Mn;230;NSM;;;;;N;;;;;
1DDD;COMBINING LATIN SMALL LETTER L;Mn;230;NSM;;;;;N;;;;;
1DDE;COMBINING LATIN LETTER SMALL CAPITAL L;Mn;230;NSM;;;;;N;;;;;
1DDF;COMBINING LATIN LETTER SMALL CAPITAL M;Mn;230;NSM;;;;;N;;;;;
1DE0;COMBINING LATIN SMALL LETTER N;Mn;230;NSM;;;;;N;;;;;
1DE1;COMBINING LATIN LETTER SMALL CAPITAL N;Mn;230;NSM;;;;;N;;;;;
1DE2;COMBINING LATIN LETTER SMALL CAPITAL R;Mn;230;NSM;;;;;N;;;;;
1DE3;COMBINING LATIN SMALL LETTER R ROTUNDA;Mn;230;NSM;;;;;N;;;;;
1DE4;COMBINING LATIN SMALL LETTER S;Mn;230;NSM;;;;;N;;;;;
1DE5;COMBINING LATIN SMALL LETTER LONG S;Mn;230;NSM;;;;;N;;;;;
1DE6;COMBINING LATIN SMALL LETTER Z;Mn;230;NSM;;;;;N;;;;;
1DFE;COMBINING LEFT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;;
20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;;
20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;;
20D4;COMBINING ANTICLOCKWISE ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING ANTICLOCKWISE ARROW ABOVE;;;;
20D5;COMBINING CLOCKWISE ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING CLOCKWISE ARROW ABOVE;;;;
20D6;COMBINING LEFT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT ARROW ABOVE;;;;
20D7;COMBINING RIGHT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT ARROW ABOVE;;;;
20DB;COMBINING THREE DOTS ABOVE;Mn;230;NSM;;;;;N;NON-SPACING THREE DOTS ABOVE;;;;
20DC;COMBINING FOUR DOTS ABOVE;Mn;230;NSM;;;;;N;NON-SPACING FOUR DOTS ABOVE;;;;
20E1;COMBINING LEFT RIGHT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT RIGHT ARROW ABOVE;;;;
20E7;COMBINING ANNUITY SYMBOL;Mn;230;NSM;;;;;N;;;;;
20E9;COMBINING WIDE BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;;
20F0;COMBINING ASTERISK ABOVE;Mn;230;NSM;;;;;N;;;;;
2CEF;COPTIC COMBINING NI ABOVE;Mn;230;NSM;;;;;N;;;;;
2CF0;COPTIC COMBINING SPIRITUS ASPER;Mn;230;NSM;;;;;N;;;;;
2CF1;COPTIC COMBINING SPIRITUS LENIS;Mn;230;NSM;;;;;N;;;;;
2DE0;COMBINING CYRILLIC LETTER BE;Mn;230;NSM;;;;;N;;;;;
2DE1;COMBINING CYRILLIC LETTER VE;Mn;230;NSM;;;;;N;;;;;
2DE2;COMBINING CYRILLIC LETTER GHE;Mn;230;NSM;;;;;N;;;;;
2DE3;COMBINING CYRILLIC LETTER DE;Mn;230;NSM;;;;;N;;;;;
2DE4;COMBINING CYRILLIC LETTER ZHE;Mn;230;NSM;;;;;N;;;;;
2DE5;COMBINING CYRILLIC LETTER ZE;Mn;230;NSM;;;;;N;;;;;
2DE6;COMBINING CYRILLIC LETTER KA;Mn;230;NSM;;;;;N;;;;;
2DE7;COMBINING CYRILLIC LETTER EL;Mn;230;NSM;;;;;N;;;;;
2DE8;COMBINING CYRILLIC LETTER EM;Mn;230;NSM;;;;;N;;;;;
2DE9;COMBINING CYRILLIC LETTER EN;Mn;230;NSM;;;;;N;;;;;
2DEA;COMBINING CYRILLIC LETTER O;Mn;230;NSM;;;;;N;;;;;
2DEB;COMBINING CYRILLIC LETTER PE;Mn;230;NSM;;;;;N;;;;;
2DEC;COMBINING CYRILLIC LETTER ER;Mn;230;NSM;;;;;N;;;;;
2DED;COMBINING CYRILLIC LETTER ES;Mn;230;NSM;;;;;N;;;;;
2DEE;COMBINING CYRILLIC LETTER TE;Mn;230;NSM;;;;;N;;;;;
2DEF;COMBINING CYRILLIC LETTER HA;Mn;230;NSM;;;;;N;;;;;
2DF0;COMBINING CYRILLIC LETTER TSE;Mn;230;NSM;;;;;N;;;;;
2DF1;COMBINING CYRILLIC LETTER CHE;Mn;230;NSM;;;;;N;;;;;
2DF2;COMBINING CYRILLIC LETTER SHA;Mn;230;NSM;;;;;N;;;;;
2DF3;COMBINING CYRILLIC LETTER SHCHA;Mn;230;NSM;;;;;N;;;;;
2DF4;COMBINING CYRILLIC LETTER FITA;Mn;230;NSM;;;;;N;;;;;
2DF5;COMBINING CYRILLIC LETTER ES-TE;Mn;230;NSM;;;;;N;;;;;
2DF6;COMBINING CYRILLIC LETTER A;Mn;230;NSM;;;;;N;;;;;
2DF7;COMBINING CYRILLIC LETTER IE;Mn;230;NSM;;;;;N;;;;;
2DF8;COMBINING CYRILLIC LETTER DJERV;Mn;230;NSM;;;;;N;;;;;
2DF9;COMBINING CYRILLIC LETTER MONOGRAPH UK;Mn;230;NSM;;;;;N;;;;;
2DFA;COMBINING CYRILLIC LETTER YAT;Mn;230;NSM;;;;;N;;;;;
2DFB;COMBINING CYRILLIC LETTER YU;Mn;230;NSM;;;;;N;;;;;
2DFC;COMBINING CYRILLIC LETTER IOTIFIED A;Mn;230;NSM;;;;;N;;;;;
2DFD;COMBINING CYRILLIC LETTER LITTLE YUS;Mn;230;NSM;;;;;N;;;;;
2DFE;COMBINING CYRILLIC LETTER BIG YUS;Mn;230;NSM;;;;;N;;;;;
2DFF;COMBINING CYRILLIC LETTER IOTIFIED BIG YUS;Mn;230;NSM;;;;;N;;;;;
A66F;COMBINING CYRILLIC VZMET;Mn;230;NSM;;;;;N;;;;;
A67C;COMBINING CYRILLIC KAVYKA;Mn;230;NSM;;;;;N;;;;;
A67D;COMBINING CYRILLIC PAYEROK;Mn;230;NSM;;;;;N;;;;;
A6F0;BAMUM COMBINING MARK KOQNDON;Mn;230;NSM;;;;;N;;;;;
A6F1;BAMUM COMBINING MARK TUKWENTIS;Mn;230;NSM;;;;;N;;;;;
A8E0;COMBINING DEVANAGARI DIGIT ZERO;Mn;230;NSM;;;;;N;;;;;
A8E1;COMBINING DEVANAGARI DIGIT ONE;Mn;230;NSM;;;;;N;;;;;
A8E2;COMBINING DEVANAGARI DIGIT TWO;Mn;230;NSM;;;;;N;;;;;
A8E3;COMBINING DEVANAGARI DIGIT THREE;Mn;230;NSM;;;;;N;;;;;
A8E4;COMBINING DEVANAGARI DIGIT FOUR;Mn;230;NSM;;;;;N;;;;;
A8E5;COMBINING DEVANAGARI DIGIT FIVE;Mn;230;NSM;;;;;N;;;;;
A8E6;COMBINING DEVANAGARI DIGIT SIX;Mn;230;NSM;;;;;N;;;;;
A8E7;COMBINING DEVANAGARI DIGIT SEVEN;Mn;230;NSM;;;;;N;;;;;
A8E8;COMBINING DEVANAGARI DIGIT EIGHT;Mn;230;NSM;;;;;N;;;;;
A8E9;COMBINING DEVANAGARI DIGIT NINE;Mn;230;NSM;;;;;N;;;;;
A8EA;COMBINING DEVANAGARI LETTER A;Mn;230;NSM;;;;;N;;;;;
A8EB;COMBINING DEVANAGARI LETTER U;Mn;230;NSM;;;;;N;;;;;
A8EC;COMBINING DEVANAGARI LETTER KA;Mn;230;NSM;;;;;N;;;;;
A8ED;COMBINING DEVANAGARI LETTER NA;Mn;230;NSM;;;;;N;;;;;
A8EE;COMBINING DEVANAGARI LETTER PA;Mn;230;NSM;;;;;N;;;;;
A8EF;COMBINING DEVANAGARI LETTER RA;Mn;230;NSM;;;;;N;;;;;
A8F0;COMBINING DEVANAGARI LETTER VI;Mn;230;NSM;;;;;N;;;;;
A8F1;COMBINING DEVANAGARI SIGN AVAGRAHA;Mn;230;NSM;;;;;N;;;;;
AAB0;TAI VIET MAI KANG;Mn;230;NSM;;;;;N;;;;;
AAB2;TAI VIET VOWEL I;Mn;230;NSM;;;;;N;;;;;
AAB3;TAI VIET VOWEL UE;Mn;230;NSM;;;;;N;;;;;
AAB7;TAI VIET MAI KHIT;Mn;230;NSM;;;;;N;;;;;
AAB8;TAI VIET VOWEL IA;Mn;230;NSM;;;;;N;;;;;
AABE;TAI VIET VOWEL AM;Mn;230;NSM;;;;;N;;;;;
AABF;TAI VIET TONE MAI EK;Mn;230;NSM;;;;;N;;;;;
AAC1;TAI VIET TONE MAI THO;Mn;230;NSM;;;;;N;;;;;
FE20;COMBINING LIGATURE LEFT HALF;Mn;230;NSM;;;;;N;;;;;
FE21;COMBINING LIGATURE RIGHT HALF;Mn;230;NSM;;;;;N;;;;;
FE22;COMBINING DOUBLE TILDE LEFT HALF;Mn;230;NSM;;;;;N;;;;;
FE23;COMBINING DOUBLE TILDE RIGHT HALF;Mn;230;NSM;;;;;N;;;;;
FE24;COMBINING MACRON LEFT HALF;Mn;230;NSM;;;;;N;;;;;
FE25;COMBINING MACRON RIGHT HALF;Mn;230;NSM;;;;;N;;;;;
FE26;COMBINING CONJOINING MACRON;Mn;230;NSM;;;;;N;;;;;
10A0F;KHAROSHTHI SIGN VISARGA;Mn;230;NSM;;;;;N;;;;;
10A38;KHAROSHTHI SIGN BAR ABOVE;Mn;230;NSM;;;;;N;;;;;
1D185;MUSICAL SYMBOL COMBINING DOIT;Mn;230;NSM;;;;;N;;;;;
1D186;MUSICAL SYMBOL COMBINING RIP;Mn;230;NSM;;;;;N;;;;;
1D187;MUSICAL SYMBOL COMBINING FLIP;Mn;230;NSM;;;;;N;;;;;
1D188;MUSICAL SYMBOL COMBINING SMEAR;Mn;230;NSM;;;;;N;;;;;
1D189;MUSICAL SYMBOL COMBINING BEND;Mn;230;NSM;;;;;N;;;;;
1D1AA;MUSICAL SYMBOL COMBINING DOWN BOW;Mn;230;NSM;;;;;N;;;;;
1D1AB;MUSICAL SYMBOL COMBINING UP BOW;Mn;230;NSM;;;;;N;;;;;
1D1AC;MUSICAL SYMBOL COMBINING HARMONIC;Mn;230;NSM;;;;;N;;;;;
1D1AD;MUSICAL SYMBOL COMBINING SNAP PIZZICATO;Mn;230;NSM;;;;;N;;;;;
1D242;COMBINING GREEK MUSICAL TRISEME;Mn;230;NSM;;;;;N;;;;;
1D243;COMBINING GREEK MUSICAL TETRASEME;Mn;230;NSM;;;;;N;;;;;
1D244;COMBINING GREEK MUSICAL PENTASEME;Mn;230;NSM;;;;;N;;;;;