mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-22 05:33:45 -04:00
commit
f19e1e4c36
6 changed files with 98 additions and 50 deletions
|
|
@ -53,6 +53,8 @@
|
||||||
(https://codeberg.org/dnkl/foot/issues/363).
|
(https://codeberg.org/dnkl/foot/issues/363).
|
||||||
* Man page **foot-ctlseqs**(7), documenting all supported escape
|
* Man page **foot-ctlseqs**(7), documenting all supported escape
|
||||||
sequences (https://codeberg.org/dnkl/foot/issues/235).
|
sequences (https://codeberg.org/dnkl/foot/issues/235).
|
||||||
|
* Support for transparent sixels (DCS parameter `P2=1`)
|
||||||
|
(https://codeberg.org/dnkl/foot/issues/391).
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
@ -83,6 +85,9 @@
|
||||||
* The minimum version requirement for the libxkbcommon dependency is
|
* The minimum version requirement for the libxkbcommon dependency is
|
||||||
now 1.0.0.
|
now 1.0.0.
|
||||||
* Empty pixel rows at the bottom of a sixel is now trimmed.
|
* Empty pixel rows at the bottom of a sixel is now trimmed.
|
||||||
|
* Sixels with DCS parameter `P2=0|2` now use the _current_ ANSI
|
||||||
|
background color for empty pixels instead of the default background
|
||||||
|
color (https://codeberg.org/dnkl/foot/issues/391).
|
||||||
|
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
|
||||||
9
dcs.c
9
dcs.c
|
|
@ -42,12 +42,17 @@ dcs_hook(struct terminal *term, uint8_t final)
|
||||||
switch (term->vt.private) {
|
switch (term->vt.private) {
|
||||||
case 0:
|
case 0:
|
||||||
switch (final) {
|
switch (final) {
|
||||||
case 'q':
|
case 'q': {
|
||||||
sixel_init(term);
|
int p1 = vt_param_get(term, 0, 0);
|
||||||
|
int p2 = vt_param_get(term, 1,0);
|
||||||
|
int p3 = vt_param_get(term, 2, 0);
|
||||||
|
|
||||||
|
sixel_init(term, p1, p2, p3);
|
||||||
term->vt.dcs.put_handler = &sixel_put;
|
term->vt.dcs.put_handler = &sixel_put;
|
||||||
term->vt.dcs.unhook_handler = &sixel_unhook;
|
term->vt.dcs.unhook_handler = &sixel_unhook;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '=':
|
case '=':
|
||||||
|
|
|
||||||
76
render.c
76
render.c
|
|
@ -637,6 +637,14 @@ draw_cursor:
|
||||||
return cell_cols;
|
return cell_cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
render_row(struct terminal *term, pixman_image_t *pix, struct row *row,
|
||||||
|
int row_no, int cursor_col)
|
||||||
|
{
|
||||||
|
for (int col = term->cols - 1; col >= 0; col--)
|
||||||
|
render_cell(term, pix, row, col, row_no, cursor_col == col);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
render_urgency(struct terminal *term, struct buffer *buf)
|
render_urgency(struct terminal *term, struct buffer *buf)
|
||||||
{
|
{
|
||||||
|
|
@ -924,7 +932,7 @@ render_sixel_chunk(struct terminal *term, pixman_image_t *pix, const struct sixe
|
||||||
//LOG_DBG("sixel chunk: %dx%d %dx%d", x, y, width, height);
|
//LOG_DBG("sixel chunk: %dx%d %dx%d", x, y, width, height);
|
||||||
|
|
||||||
pixman_image_composite32(
|
pixman_image_composite32(
|
||||||
PIXMAN_OP_SRC,
|
PIXMAN_OP_OVER,
|
||||||
sixel->pix,
|
sixel->pix,
|
||||||
NULL,
|
NULL,
|
||||||
pix,
|
pix,
|
||||||
|
|
@ -938,7 +946,7 @@ render_sixel_chunk(struct terminal *term, pixman_image_t *pix, const struct sixe
|
||||||
|
|
||||||
static void
|
static void
|
||||||
render_sixel(struct terminal *term, pixman_image_t *pix,
|
render_sixel(struct terminal *term, pixman_image_t *pix,
|
||||||
const struct sixel *sixel)
|
const struct coord *cursor, const struct sixel *sixel)
|
||||||
{
|
{
|
||||||
const int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1);
|
const int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1);
|
||||||
const bool last_row_needs_erase = sixel->height % term->cell_height != 0;
|
const bool last_row_needs_erase = sixel->height % term->cell_height != 0;
|
||||||
|
|
@ -1010,29 +1018,44 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cursor_col = cursor->row == term_row_no ? cursor->col : -1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loop cells and set their 'clean' bit, to prevent the grid
|
* If image contains transparent parts, render all (dirty)
|
||||||
* rendered from overwriting the sixel
|
* cells beneath it.
|
||||||
|
*
|
||||||
|
* If image is opaque, loop cells and set their 'clean' bit,
|
||||||
|
* to prevent the grid rendered from overwriting the sixel
|
||||||
*
|
*
|
||||||
* If the last sixel row only partially covers the cell row,
|
* If the last sixel row only partially covers the cell row,
|
||||||
* 'erase' the cell by rendering them.
|
* 'erase' the cell by rendering them.
|
||||||
|
*
|
||||||
|
* In all cases, do *not* clear the ‘dirty’ bit on the row, to
|
||||||
|
* ensure the regular renderer includes them in the damage
|
||||||
|
* rect.
|
||||||
*/
|
*/
|
||||||
for (int col = sixel->pos.col;
|
if (!sixel->opaque) {
|
||||||
col < min(sixel->pos.col + sixel->cols, term->cols);
|
/* TODO: multithreading */
|
||||||
col++)
|
int cursor_col = cursor->row == term_row_no ? cursor->col : -1;
|
||||||
{
|
render_row(term, pix, row, term_row_no, cursor_col);
|
||||||
struct cell *cell = &row->cells[col];
|
} else {
|
||||||
|
for (int col = sixel->pos.col;
|
||||||
|
col < min(sixel->pos.col + sixel->cols, term->cols);
|
||||||
|
col++)
|
||||||
|
{
|
||||||
|
struct cell *cell = &row->cells[col];
|
||||||
|
|
||||||
if (!cell->attrs.clean) {
|
if (!cell->attrs.clean) {
|
||||||
bool last_row = abs_row_no == sixel->pos.row + sixel->rows - 1;
|
bool last_row = abs_row_no == sixel->pos.row + sixel->rows - 1;
|
||||||
bool last_col = col == sixel->pos.col + sixel->cols - 1;
|
bool last_col = col == sixel->pos.col + sixel->cols - 1;
|
||||||
|
|
||||||
if ((last_row_needs_erase && last_row) ||
|
if ((last_row_needs_erase && last_row) ||
|
||||||
(last_col_needs_erase && last_col))
|
(last_col_needs_erase && last_col))
|
||||||
{
|
{
|
||||||
render_cell(term, pix, row, col, term_row_no, false);
|
render_cell(term, pix, row, col, term_row_no, cursor_col == col);
|
||||||
} else
|
} else
|
||||||
cell->attrs.clean = 1;
|
cell->attrs.clean = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1050,7 +1073,8 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
render_sixel_images(struct terminal *term, pixman_image_t *pix)
|
render_sixel_images(struct terminal *term, pixman_image_t *pix,
|
||||||
|
const struct coord *cursor)
|
||||||
{
|
{
|
||||||
if (likely(tll_length(term->grid->sixel_images)) == 0)
|
if (likely(tll_length(term->grid->sixel_images)) == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -1086,7 +1110,7 @@ render_sixel_images(struct terminal *term, pixman_image_t *pix)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
render_sixel(term, pix, &it->item);
|
render_sixel(term, pix, cursor, &it->item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1250,14 +1274,6 @@ render_ime_preedit(struct terminal *term, struct buffer *buf)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
render_row(struct terminal *term, pixman_image_t *pix, struct row *row,
|
|
||||||
int row_no, int cursor_col)
|
|
||||||
{
|
|
||||||
for (int col = term->cols - 1; col >= 0; col--)
|
|
||||||
render_cell(term, pix, row, col, row_no, cursor_col == col);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
render_worker_thread(void *_ctx)
|
render_worker_thread(void *_ctx)
|
||||||
{
|
{
|
||||||
|
|
@ -2063,8 +2079,6 @@ grid_render(struct terminal *term)
|
||||||
cursor.row &= term->grid->num_rows - 1;
|
cursor.row &= term->grid->num_rows - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
render_sixel_images(term, buf->pix[0]);
|
|
||||||
|
|
||||||
if (term->render.workers.count > 0) {
|
if (term->render.workers.count > 0) {
|
||||||
mtx_lock(&term->render.workers.lock);
|
mtx_lock(&term->render.workers.lock);
|
||||||
term->render.workers.buf = buf;
|
term->render.workers.buf = buf;
|
||||||
|
|
@ -2074,6 +2088,8 @@ grid_render(struct terminal *term)
|
||||||
xassert(tll_length(term->render.workers.queue) == 0);
|
xassert(tll_length(term->render.workers.queue) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_sixel_images(term, buf->pix[0], &cursor);
|
||||||
|
|
||||||
int first_dirty_row = -1;
|
int first_dirty_row = -1;
|
||||||
for (int r = 0; r < term->rows; r++) {
|
for (int r = 0; r < term->rows; r++) {
|
||||||
struct row *row = grid_row_in_view(term->grid, r);
|
struct row *row = grid_row_in_view(term->grid, r);
|
||||||
|
|
|
||||||
53
sixel.c
53
sixel.c
|
|
@ -15,6 +15,16 @@
|
||||||
|
|
||||||
static size_t count;
|
static size_t count;
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
get_bg(const struct terminal *term)
|
||||||
|
{
|
||||||
|
return term->sixel.transparent_bg
|
||||||
|
? 0x00000000u
|
||||||
|
: 0xffu << 24 | (term->vt.attrs.have_bg
|
||||||
|
? term->vt.attrs.bg
|
||||||
|
: term->colors.bg);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sixel_fini(struct terminal *term)
|
sixel_fini(struct terminal *term)
|
||||||
{
|
{
|
||||||
|
|
@ -22,16 +32,17 @@ sixel_fini(struct terminal *term)
|
||||||
free(term->sixel.shared_palette);
|
free(term->sixel.shared_palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
|
||||||
color_with_alpha(const struct terminal *term, uint32_t color)
|
|
||||||
{
|
|
||||||
uint16_t alpha = color == term->colors.bg ? term->colors.alpha : 0xffff;
|
|
||||||
return (alpha / 256u) << 24 | color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sixel_init(struct terminal *term)
|
sixel_init(struct terminal *term, int p1, int p2, int p3)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* P1: pixel aspect ratio - unimplemented
|
||||||
|
* P2: background color mode
|
||||||
|
* - 0|2: empty pixels use current background color
|
||||||
|
* - 1: empty pixels remain at their current color (i.e. transparent)
|
||||||
|
* P3: horizontal grid size - ignored
|
||||||
|
*/
|
||||||
|
|
||||||
xassert(term->sixel.image.data == NULL);
|
xassert(term->sixel.image.data == NULL);
|
||||||
xassert(term->sixel.palette_size <= SIXEL_MAX_COLORS);
|
xassert(term->sixel.palette_size <= SIXEL_MAX_COLORS);
|
||||||
|
|
||||||
|
|
@ -43,6 +54,7 @@ sixel_init(struct terminal *term)
|
||||||
term->sixel.param = 0;
|
term->sixel.param = 0;
|
||||||
term->sixel.param_idx = 0;
|
term->sixel.param_idx = 0;
|
||||||
memset(term->sixel.params, 0, sizeof(term->sixel.params));
|
memset(term->sixel.params, 0, sizeof(term->sixel.params));
|
||||||
|
term->sixel.transparent_bg = p2 == 1;
|
||||||
term->sixel.image.data = xmalloc(1 * 6 * sizeof(term->sixel.image.data[0]));
|
term->sixel.image.data = xmalloc(1 * 6 * sizeof(term->sixel.image.data[0]));
|
||||||
term->sixel.image.width = 1;
|
term->sixel.image.width = 1;
|
||||||
term->sixel.image.height = 6;
|
term->sixel.image.height = 6;
|
||||||
|
|
@ -65,8 +77,9 @@ sixel_init(struct terminal *term)
|
||||||
term->sixel.palette = term->sixel.shared_palette;
|
term->sixel.palette = term->sixel.shared_palette;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t bg = get_bg(term);
|
||||||
for (size_t i = 0; i < 1 * 6; i++)
|
for (size_t i = 0; i < 1 * 6; i++)
|
||||||
term->sixel.image.data[i] = color_with_alpha(term, term->colors.bg);
|
term->sixel.image.data[i] = bg;
|
||||||
|
|
||||||
count = 0;
|
count = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +119,7 @@ sixel_erase(struct terminal *term, struct sixel *sixel)
|
||||||
|
|
||||||
row->dirty = true;
|
row->dirty = true;
|
||||||
|
|
||||||
for (int c = 0; c < term->grid->num_cols; c++)
|
for (int c = sixel->pos.col; c < min(sixel->cols, term->cols); c++)
|
||||||
row->cells[c].attrs.clean = 0;
|
row->cells[c].attrs.clean = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,6 +434,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six,
|
||||||
.pos = {.col = new_col, .row = new_row},
|
.pos = {.col = new_col, .row = new_row},
|
||||||
.cols = (new_width + term->cell_width - 1) / term->cell_width,
|
.cols = (new_width + term->cell_width - 1) / term->cell_width,
|
||||||
.rows = (new_height + term->cell_height - 1) / term->cell_height,
|
.rows = (new_height + term->cell_height - 1) / term->cell_height,
|
||||||
|
.opaque = six->opaque,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(_DEBUG)
|
#if defined(_DEBUG)
|
||||||
|
|
@ -767,6 +781,7 @@ sixel_unhook(struct terminal *term)
|
||||||
.rows = (height + term->cell_height - 1) / term->cell_height,
|
.rows = (height + term->cell_height - 1) / term->cell_height,
|
||||||
.cols = (width + term->cell_width - 1) / term->cell_width,
|
.cols = (width + term->cell_width - 1) / term->cell_width,
|
||||||
.pos = (struct coord){start_col, cur_row},
|
.pos = (struct coord){start_col, cur_row},
|
||||||
|
.opaque = !term->sixel.transparent_bg,
|
||||||
};
|
};
|
||||||
|
|
||||||
xassert(image.rows < term->grid->num_rows);
|
xassert(image.rows < term->grid->num_rows);
|
||||||
|
|
@ -868,6 +883,8 @@ resize_horizontally(struct terminal *term, int new_width)
|
||||||
/* Width (and thus stride) change - need to allocate a new buffer */
|
/* Width (and thus stride) change - need to allocate a new buffer */
|
||||||
uint32_t *new_data = xmalloc(new_width * alloc_height * sizeof(uint32_t));
|
uint32_t *new_data = xmalloc(new_width * alloc_height * sizeof(uint32_t));
|
||||||
|
|
||||||
|
uint32_t bg = get_bg(term);
|
||||||
|
|
||||||
/* Copy old rows, and initialize new columns to background color */
|
/* Copy old rows, and initialize new columns to background color */
|
||||||
for (int r = 0; r < height; r++) {
|
for (int r = 0; r < height; r++) {
|
||||||
memcpy(&new_data[r * new_width],
|
memcpy(&new_data[r * new_width],
|
||||||
|
|
@ -875,7 +892,7 @@ resize_horizontally(struct terminal *term, int new_width)
|
||||||
old_width * sizeof(uint32_t));
|
old_width * sizeof(uint32_t));
|
||||||
|
|
||||||
for (int c = old_width; c < new_width; c++)
|
for (int c = old_width; c < new_width; c++)
|
||||||
new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg);
|
new_data[r * new_width + c] = bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(old_data);
|
free(old_data);
|
||||||
|
|
@ -915,10 +932,12 @@ resize_vertically(struct terminal *term, int new_height)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t bg = get_bg(term);
|
||||||
|
|
||||||
/* Initialize new rows to background color */
|
/* Initialize new rows to background color */
|
||||||
for (int r = old_height; r < new_height; r++) {
|
for (int r = old_height; r < new_height; r++) {
|
||||||
for (int c = 0; c < width; c++)
|
for (int c = 0; c < width; c++)
|
||||||
new_data[r * width + c] = color_with_alpha(term, term->colors.bg);
|
new_data[r * width + c] = bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
term->sixel.image.data = new_data;
|
term->sixel.image.data = new_data;
|
||||||
|
|
@ -950,6 +969,7 @@ resize(struct terminal *term, int new_width, int new_height)
|
||||||
xassert(alloc_new_height - new_height < 6);
|
xassert(alloc_new_height - new_height < 6);
|
||||||
|
|
||||||
uint32_t *new_data = NULL;
|
uint32_t *new_data = NULL;
|
||||||
|
uint32_t bg = get_bg(term);
|
||||||
|
|
||||||
if (new_width == old_width) {
|
if (new_width == old_width) {
|
||||||
/* Width (and thus stride) is the same, so we can simply
|
/* Width (and thus stride) is the same, so we can simply
|
||||||
|
|
@ -973,7 +993,7 @@ resize(struct terminal *term, int new_width, int new_height)
|
||||||
memcpy(&new_data[r * new_width], &old_data[r * old_width], old_width * sizeof(uint32_t));
|
memcpy(&new_data[r * new_width], &old_data[r * old_width], old_width * sizeof(uint32_t));
|
||||||
|
|
||||||
for (int c = old_width; c < new_width; c++)
|
for (int c = old_width; c < new_width; c++)
|
||||||
new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg);
|
new_data[r * new_width + c] = bg;
|
||||||
}
|
}
|
||||||
free(old_data);
|
free(old_data);
|
||||||
}
|
}
|
||||||
|
|
@ -981,7 +1001,7 @@ resize(struct terminal *term, int new_width, int new_height)
|
||||||
/* Initialize new rows to background color */
|
/* Initialize new rows to background color */
|
||||||
for (int r = old_height; r < new_height; r++) {
|
for (int r = old_height; r < new_height; r++) {
|
||||||
for (int c = 0; c < new_width; c++)
|
for (int c = 0; c < new_width; c++)
|
||||||
new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg);
|
new_data[r * new_width + c] = bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
xassert(new_data != NULL);
|
xassert(new_data != NULL);
|
||||||
|
|
@ -1239,8 +1259,7 @@ decgci(struct terminal *term, uint8_t c)
|
||||||
LOG_DBG("setting palette #%d = HLS %hhu/%hhu/%hhu (0x%06x)",
|
LOG_DBG("setting palette #%d = HLS %hhu/%hhu/%hhu (0x%06x)",
|
||||||
term->sixel.color_idx, hue, lum, sat, rgb);
|
term->sixel.color_idx, hue, lum, sat, rgb);
|
||||||
|
|
||||||
term->sixel.palette[term->sixel.color_idx] =
|
term->sixel.palette[term->sixel.color_idx] = 0xffu << 24 | rgb;
|
||||||
color_with_alpha(term, rgb);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1253,7 +1272,7 @@ decgci(struct terminal *term, uint8_t c)
|
||||||
term->sixel.color_idx, r, g, b);
|
term->sixel.color_idx, r, g, b);
|
||||||
|
|
||||||
term->sixel.palette[term->sixel.color_idx] =
|
term->sixel.palette[term->sixel.color_idx] =
|
||||||
color_with_alpha(term, r << 16 | g << 8 | b);
|
0xffu << 24 | r << 16 | g << 8 | b;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
sixel.h
2
sixel.h
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
void sixel_fini(struct terminal *term);
|
void sixel_fini(struct terminal *term);
|
||||||
|
|
||||||
void sixel_init(struct terminal *term);
|
void sixel_init(struct terminal *term, int p1, int p2, int p3);
|
||||||
void sixel_put(struct terminal *term, uint8_t c);
|
void sixel_put(struct terminal *term, uint8_t c);
|
||||||
void sixel_unhook(struct terminal *term);
|
void sixel_unhook(struct terminal *term);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ struct sixel {
|
||||||
int rows;
|
int rows;
|
||||||
int cols;
|
int cols;
|
||||||
struct coord pos;
|
struct coord pos;
|
||||||
|
bool opaque;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct grid {
|
struct grid {
|
||||||
|
|
@ -542,6 +543,8 @@ struct terminal {
|
||||||
unsigned param; /* Currently collecting parameter, for RASTER, COLOR_SPEC and REPEAT */
|
unsigned param; /* Currently collecting parameter, for RASTER, COLOR_SPEC and REPEAT */
|
||||||
unsigned param_idx; /* Parameters seen */
|
unsigned param_idx; /* Parameters seen */
|
||||||
|
|
||||||
|
bool transparent_bg;
|
||||||
|
|
||||||
/* Application configurable */
|
/* Application configurable */
|
||||||
unsigned palette_size; /* Number of colors in palette */
|
unsigned palette_size; /* Number of colors in palette */
|
||||||
unsigned max_width; /* Maximum image width, in pixels */
|
unsigned max_width; /* Maximum image width, in pixels */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue