Merge commit 'e5fd3675fab544fd34fe404425ed5287d39d9f24' into harfbuzz
This commit is contained in:
commit
16750433e2
11 changed files with 192 additions and 59 deletions
|
@ -102,7 +102,7 @@ public:
|
|||
if (is_bitmap())
|
||||
return (*bitmap_data_)->width();
|
||||
else if (is_vector())
|
||||
return (*vector_data_)->bounding_box().width();
|
||||
return (*vector_data_)->width();
|
||||
return 0;
|
||||
}
|
||||
inline double height() const
|
||||
|
@ -110,7 +110,7 @@ public:
|
|||
if (is_bitmap())
|
||||
return (*bitmap_data_)->height();
|
||||
else if (is_vector())
|
||||
return (*vector_data_)->bounding_box().height();
|
||||
return (*vector_data_)->height();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include "agg_image_accessors.h"
|
||||
#include "agg_pixfmt_rgba.h"
|
||||
#include "agg_span_image_filter_rgba.h"
|
||||
#include "agg_span_interpolator_linear.h"
|
||||
|
||||
// boost
|
||||
#include <boost/optional.hpp>
|
||||
|
@ -69,7 +70,8 @@ struct vector_markers_rasterizer_dispatch
|
|||
agg::trans_affine const& marker_trans,
|
||||
markers_symbolizer const& sym,
|
||||
Detector & detector,
|
||||
double scale_factor)
|
||||
double scale_factor,
|
||||
bool snap_to_pixels)
|
||||
: buf_(render_buffer),
|
||||
pixf_(buf_),
|
||||
renb_(pixf_),
|
||||
|
@ -79,7 +81,8 @@ struct vector_markers_rasterizer_dispatch
|
|||
marker_trans_(marker_trans),
|
||||
sym_(sym),
|
||||
detector_(detector),
|
||||
scale_factor_(scale_factor)
|
||||
scale_factor_(scale_factor),
|
||||
snap_to_pixels_(snap_to_pixels)
|
||||
{
|
||||
pixf_.comp_op(static_cast<agg::comp_op_e>(sym_.comp_op()));
|
||||
}
|
||||
|
@ -116,6 +119,12 @@ struct vector_markers_rasterizer_dispatch
|
|||
if (sym_.get_allow_overlap() ||
|
||||
detector_.has_placement(transformed_bbox))
|
||||
{
|
||||
if (snap_to_pixels_)
|
||||
{
|
||||
// https://github.com/mapnik/mapnik/issues/1316
|
||||
matrix.tx = std::floor(matrix.tx+.5);
|
||||
matrix.ty = std::floor(matrix.ty+.5);
|
||||
}
|
||||
svg_renderer_.render(ras_, sl_, renb_, matrix, sym_.get_opacity(), bbox_);
|
||||
if (!sym_.get_ignore_placement())
|
||||
{
|
||||
|
@ -153,6 +162,7 @@ private:
|
|||
markers_symbolizer const& sym_;
|
||||
Detector & detector_;
|
||||
double scale_factor_;
|
||||
bool snap_to_pixels_;
|
||||
};
|
||||
|
||||
template <typename BufferType, typename Rasterizer, typename Detector>
|
||||
|
@ -171,7 +181,8 @@ struct raster_markers_rasterizer_dispatch
|
|||
agg::trans_affine const& marker_trans,
|
||||
markers_symbolizer const& sym,
|
||||
Detector & detector,
|
||||
double scale_factor)
|
||||
double scale_factor,
|
||||
bool snap_to_pixels)
|
||||
: buf_(render_buffer),
|
||||
pixf_(buf_),
|
||||
renb_(pixf_),
|
||||
|
@ -180,7 +191,8 @@ struct raster_markers_rasterizer_dispatch
|
|||
marker_trans_(marker_trans),
|
||||
sym_(sym),
|
||||
detector_(detector),
|
||||
scale_factor_(scale_factor)
|
||||
scale_factor_(scale_factor),
|
||||
snap_to_pixels_(snap_to_pixels)
|
||||
{
|
||||
pixf_.comp_op(static_cast<agg::comp_op_e>(sym_.comp_op()));
|
||||
}
|
||||
|
@ -245,41 +257,66 @@ struct raster_markers_rasterizer_dispatch
|
|||
void render_raster_marker(agg::trans_affine const& marker_tr,
|
||||
double opacity)
|
||||
{
|
||||
typedef agg::pixfmt_rgba32_pre pixfmt_pre;
|
||||
double width = src_.width();
|
||||
double height = src_.height();
|
||||
double p[8];
|
||||
p[0] = 0; p[1] = 0;
|
||||
p[2] = width; p[3] = 0;
|
||||
p[4] = width; p[5] = height;
|
||||
p[6] = 0; p[7] = height;
|
||||
marker_tr.transform(&p[0], &p[1]);
|
||||
marker_tr.transform(&p[2], &p[3]);
|
||||
marker_tr.transform(&p[4], &p[5]);
|
||||
marker_tr.transform(&p[6], &p[7]);
|
||||
ras_.move_to_d(p[0],p[1]);
|
||||
ras_.line_to_d(p[2],p[3]);
|
||||
ras_.line_to_d(p[4],p[5]);
|
||||
ras_.line_to_d(p[6],p[7]);
|
||||
agg::span_allocator<color_type> sa;
|
||||
agg::image_filter_bilinear filter_kernel;
|
||||
agg::image_filter_lut filter(filter_kernel, false);
|
||||
agg::rendering_buffer marker_buf((unsigned char *)src_.getBytes(),
|
||||
src_.width(),
|
||||
src_.height(),
|
||||
src_.width()*4);
|
||||
agg::pixfmt_rgba32_pre pixf(marker_buf);
|
||||
typedef agg::image_accessor_clone<agg::pixfmt_rgba32_pre> img_accessor_type;
|
||||
typedef agg::span_interpolator_linear<agg::trans_affine> interpolator_type;
|
||||
typedef agg::span_image_filter_rgba_2x2<img_accessor_type,
|
||||
interpolator_type> span_gen_type;
|
||||
typedef agg::renderer_scanline_aa_alpha<renderer_base,
|
||||
agg::span_allocator<color_type>,
|
||||
span_gen_type> renderer_type;
|
||||
img_accessor_type ia(pixf);
|
||||
interpolator_type interpolator(agg::trans_affine(p, 0, 0, width, height) );
|
||||
span_gen_type sg(ia, interpolator, filter);
|
||||
renderer_type rp(renb_,sa, sg, unsigned(opacity*255));
|
||||
agg::render_scanlines(ras_, sl_, rp);
|
||||
if (std::fabs(1.0 - scale_factor_) < 0.001
|
||||
&& (std::fabs(1.0 - marker_tr.sx) < agg::affine_epsilon)
|
||||
&& (std::fabs(0.0 - marker_tr.shy) < agg::affine_epsilon)
|
||||
&& (std::fabs(0.0 - marker_tr.shx) < agg::affine_epsilon)
|
||||
&& (std::fabs(1.0 - marker_tr.sy) < agg::affine_epsilon))
|
||||
{
|
||||
agg::rendering_buffer src_buffer((unsigned char *)src_.getBytes(),src_.width(),src_.height(),src_.width() * 4);
|
||||
pixfmt_pre pixf_mask(src_buffer);
|
||||
renb_.blend_from(pixf_mask,
|
||||
0,
|
||||
std::floor(marker_tr.tx + .5),
|
||||
std::floor(marker_tr.ty + .5),
|
||||
unsigned(255*sym_.get_opacity()));
|
||||
}
|
||||
else
|
||||
{
|
||||
typedef agg::image_accessor_clone<pixfmt_pre> img_accessor_type;
|
||||
typedef agg::span_interpolator_linear<> interpolator_type;
|
||||
//typedef agg::span_image_filter_rgba_2x2<img_accessor_type,interpolator_type> span_gen_type;
|
||||
typedef agg::span_image_resample_rgba_affine<img_accessor_type> span_gen_type;
|
||||
typedef agg::renderer_scanline_aa_alpha<renderer_base,
|
||||
agg::span_allocator<color_type>,
|
||||
span_gen_type> renderer_type;
|
||||
|
||||
double p[8];
|
||||
p[0] = 0; p[1] = 0;
|
||||
p[2] = width; p[3] = 0;
|
||||
p[4] = width; p[5] = height;
|
||||
p[6] = 0; p[7] = height;
|
||||
marker_tr.transform(&p[0], &p[1]);
|
||||
marker_tr.transform(&p[2], &p[3]);
|
||||
marker_tr.transform(&p[4], &p[5]);
|
||||
marker_tr.transform(&p[6], &p[7]);
|
||||
agg::span_allocator<color_type> sa;
|
||||
agg::image_filter_lut filter;
|
||||
filter.calculate(agg::image_filter_bilinear(), true);
|
||||
agg::rendering_buffer marker_buf((unsigned char *)src_.getBytes(),
|
||||
src_.width(),
|
||||
src_.height(),
|
||||
src_.width()*4);
|
||||
pixfmt_pre pixf(marker_buf);
|
||||
img_accessor_type ia(pixf);
|
||||
agg::trans_affine final_tr(p, 0, 0, width, height);
|
||||
if (snap_to_pixels_)
|
||||
{
|
||||
final_tr.tx = std::floor(final_tr.tx+.5);
|
||||
final_tr.ty = std::floor(final_tr.ty+.5);
|
||||
}
|
||||
interpolator_type interpolator(final_tr);
|
||||
span_gen_type sg(ia, interpolator, filter);
|
||||
renderer_type rp(renb_,sa, sg, unsigned(opacity*255));
|
||||
ras_.move_to_d(p[0],p[1]);
|
||||
ras_.line_to_d(p[2],p[3]);
|
||||
ras_.line_to_d(p[4],p[5]);
|
||||
ras_.line_to_d(p[6],p[7]);
|
||||
agg::render_scanlines(ras_, sl_, rp);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -293,6 +330,7 @@ private:
|
|||
markers_symbolizer const& sym_;
|
||||
Detector & detector_;
|
||||
double scale_factor_;
|
||||
bool snap_to_pixels_;
|
||||
};
|
||||
|
||||
|
||||
|
@ -327,6 +365,8 @@ void build_ellipse(T const& sym, mapnik::feature_impl const& feature, svg_storag
|
|||
styled_svg.pop_attr();
|
||||
double lox,loy,hix,hiy;
|
||||
styled_svg.bounding_rect(&lox, &loy, &hix, &hiy);
|
||||
styled_svg.set_dimensions(width,height);
|
||||
marker_ellipse.set_dimensions(width,height);
|
||||
marker_ellipse.set_bounding_box(lox,loy,hix,hiy);
|
||||
}
|
||||
|
||||
|
@ -381,7 +421,11 @@ bool push_explicit_style(Attr const& src, Attr & dst, markers_symbolizer const&
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
void setup_transform_scaling(agg::trans_affine & tr, box2d<double> const& bbox, mapnik::feature_impl const& feature, T const& sym)
|
||||
void setup_transform_scaling(agg::trans_affine & tr,
|
||||
double svg_width,
|
||||
double svg_height,
|
||||
mapnik::feature_impl const& feature,
|
||||
T const& sym)
|
||||
{
|
||||
double width = 0;
|
||||
double height = 0;
|
||||
|
@ -396,18 +440,18 @@ void setup_transform_scaling(agg::trans_affine & tr, box2d<double> const& bbox,
|
|||
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
double sx = width/bbox.width();
|
||||
double sy = height/bbox.height();
|
||||
double sx = width/svg_width;
|
||||
double sy = height/svg_height;
|
||||
tr *= agg::trans_affine_scaling(sx,sy);
|
||||
}
|
||||
else if (width > 0)
|
||||
{
|
||||
double sx = width/bbox.width();
|
||||
double sx = width/svg_width;
|
||||
tr *= agg::trans_affine_scaling(sx);
|
||||
}
|
||||
else if (height > 0)
|
||||
{
|
||||
double sy = height/bbox.height();
|
||||
double sy = height/svg_height;
|
||||
tr *= agg::trans_affine_scaling(sy);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -308,6 +308,22 @@ public:
|
|||
agg::bounding_rect(trans, *this, 0, attributes_.size(), x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
void set_dimensions(double w, double h)
|
||||
{
|
||||
svg_width_ = w;
|
||||
svg_height_ = h;
|
||||
}
|
||||
|
||||
double width()
|
||||
{
|
||||
return svg_width_;
|
||||
}
|
||||
|
||||
double height()
|
||||
{
|
||||
return svg_height_;
|
||||
}
|
||||
|
||||
VertexSource & storage()
|
||||
{
|
||||
return source_;
|
||||
|
@ -333,6 +349,8 @@ private:
|
|||
AttributeSource & attributes_;
|
||||
AttributeSource attr_stack_;
|
||||
agg::trans_affine transform_;
|
||||
double svg_width_;
|
||||
double svg_height_;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ namespace mapnik { namespace svg {
|
|||
void start_element(xmlTextReaderPtr reader);
|
||||
void end_element(xmlTextReaderPtr reader);
|
||||
void parse_path(xmlTextReaderPtr reader);
|
||||
void parse_dimensions(xmlTextReaderPtr reader);
|
||||
void parse_polygon(xmlTextReaderPtr reader);
|
||||
void parse_polyline(xmlTextReaderPtr reader);
|
||||
void parse_line(xmlTextReaderPtr reader);
|
||||
|
|
|
@ -34,7 +34,9 @@ template <typename VertexSource ,typename AttributeSource>
|
|||
class svg_storage : mapnik::noncopyable
|
||||
{
|
||||
public:
|
||||
svg_storage() {}
|
||||
svg_storage() :
|
||||
svg_width_(0),
|
||||
svg_height_(0) {}
|
||||
|
||||
VertexSource & source() // FIXME!! make const
|
||||
{
|
||||
|
@ -61,11 +63,29 @@ public:
|
|||
return bounding_box_;
|
||||
}
|
||||
|
||||
double width() const
|
||||
{
|
||||
return svg_width_;
|
||||
}
|
||||
|
||||
double height() const
|
||||
{
|
||||
return svg_height_;
|
||||
}
|
||||
|
||||
void set_dimensions(double w, double h)
|
||||
{
|
||||
svg_width_ = w;
|
||||
svg_height_ = h;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
VertexSource source_;
|
||||
AttributeSource attributes_;
|
||||
box2d<double> bounding_box_;
|
||||
double svg_width_;
|
||||
double svg_height_;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -74,6 +74,9 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
|
||||
std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature);
|
||||
|
||||
// https://github.com/mapnik/mapnik/issues/1316
|
||||
bool snap_pixels = !mapnik::marker_cache::instance().is_uri(filename);
|
||||
|
||||
if (!filename.empty())
|
||||
{
|
||||
boost::optional<marker_ptr> mark = mapnik::marker_cache::instance().find(filename, true);
|
||||
|
@ -120,8 +123,15 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
agg::trans_affine_translation recenter(-center.x, -center.y);
|
||||
agg::trans_affine marker_trans = recenter * tr;
|
||||
buf_type render_buffer(current_buffer_->raw_data(), width_, height_, width_ * 4);
|
||||
dispatch_type rasterizer_dispatch(render_buffer,svg_renderer,*ras_ptr,
|
||||
bbox, marker_trans, sym, *detector_, scale_factor_);
|
||||
dispatch_type rasterizer_dispatch(render_buffer,
|
||||
svg_renderer,
|
||||
*ras_ptr,
|
||||
bbox,
|
||||
marker_trans,
|
||||
sym,
|
||||
*detector_,
|
||||
scale_factor_,
|
||||
snap_pixels);
|
||||
vertex_converter<box2d<double>, dispatch_type, markers_symbolizer,
|
||||
CoordTransform, proj_transform, agg::trans_affine, conv_types>
|
||||
converter(query_extent_, rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_);
|
||||
|
@ -142,7 +152,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
else
|
||||
{
|
||||
box2d<double> const& bbox = (*mark)->bounding_box();
|
||||
setup_transform_scaling(tr, bbox, feature, sym);
|
||||
setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym);
|
||||
evaluate_transform(tr, feature, sym.get_image_transform());
|
||||
coord2d center = bbox.center();
|
||||
agg::trans_affine_translation recenter(-center.x, -center.y);
|
||||
|
@ -153,8 +163,15 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
bool result = push_explicit_style( (*stock_vector_marker)->attributes(), attributes, sym);
|
||||
svg_renderer_type svg_renderer(svg_path, result ? attributes : (*stock_vector_marker)->attributes());
|
||||
buf_type render_buffer(current_buffer_->raw_data(), width_, height_, width_ * 4);
|
||||
dispatch_type rasterizer_dispatch(render_buffer,svg_renderer,*ras_ptr,
|
||||
bbox, marker_trans, sym, *detector_, scale_factor_);
|
||||
dispatch_type rasterizer_dispatch(render_buffer,
|
||||
svg_renderer,
|
||||
*ras_ptr,
|
||||
bbox,
|
||||
marker_trans,
|
||||
sym,
|
||||
*detector_,
|
||||
scale_factor_,
|
||||
snap_pixels);
|
||||
vertex_converter<box2d<double>, dispatch_type, markers_symbolizer,
|
||||
CoordTransform, proj_transform, agg::trans_affine, conv_types>
|
||||
converter(query_extent_, rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_);
|
||||
|
@ -176,7 +193,7 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
else // raster markers
|
||||
{
|
||||
box2d<double> const& bbox = (*mark)->bounding_box();
|
||||
setup_transform_scaling(tr, bbox, feature, sym);
|
||||
setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym);
|
||||
evaluate_transform(tr, feature, sym.get_image_transform());
|
||||
coord2d center = bbox.center();
|
||||
agg::trans_affine_translation recenter(-center.x, -center.y);
|
||||
|
@ -184,8 +201,14 @@ void agg_renderer<T>::process(markers_symbolizer const& sym,
|
|||
boost::optional<mapnik::image_ptr> marker = (*mark)->get_bitmap_data();
|
||||
typedef raster_markers_rasterizer_dispatch<buf_type,rasterizer, detector_type> dispatch_type;
|
||||
buf_type render_buffer(current_buffer_->raw_data(), width_, height_, width_ * 4);
|
||||
dispatch_type rasterizer_dispatch(render_buffer,*ras_ptr, **marker,
|
||||
marker_trans, sym, *detector_, scale_factor_);
|
||||
dispatch_type rasterizer_dispatch(render_buffer,
|
||||
*ras_ptr,
|
||||
**marker,
|
||||
marker_trans,
|
||||
sym,
|
||||
*detector_,
|
||||
scale_factor_,
|
||||
true /*snap rasters no matter what*/);
|
||||
vertex_converter<box2d<double>, dispatch_type, markers_symbolizer,
|
||||
CoordTransform, proj_transform, agg::trans_affine, conv_types>
|
||||
converter(query_extent_, rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_);
|
||||
|
|
|
@ -1160,7 +1160,7 @@ void cairo_renderer_base::process(markers_symbolizer const& sym,
|
|||
agg::trans_affine geom_tr;
|
||||
evaluate_transform(geom_tr, feature, sym.get_transform());
|
||||
box2d<double> const& bbox = (*mark)->bounding_box();
|
||||
setup_transform_scaling(tr, bbox, feature, sym);
|
||||
setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym);
|
||||
evaluate_transform(tr, feature, sym.get_image_transform());
|
||||
|
||||
if ((*mark)->is_vector())
|
||||
|
|
|
@ -167,7 +167,7 @@ void grid_renderer<T>::process(markers_symbolizer const& sym,
|
|||
else
|
||||
{
|
||||
box2d<double> const& bbox = (*mark)->bounding_box();
|
||||
setup_transform_scaling(tr, bbox, feature, sym);
|
||||
setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym);
|
||||
evaluate_transform(tr, feature, sym.get_image_transform());
|
||||
// TODO - clamping to >= 4 pixels
|
||||
coord2d center = bbox.center();
|
||||
|
@ -210,7 +210,7 @@ void grid_renderer<T>::process(markers_symbolizer const& sym,
|
|||
else // raster markers
|
||||
{
|
||||
box2d<double> const& bbox = (*mark)->bounding_box();
|
||||
setup_transform_scaling(tr, bbox, feature, sym);
|
||||
setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym);
|
||||
evaluate_transform(tr, feature, sym.get_image_transform());
|
||||
// - clamp sizes to > 4 pixels of interactivity
|
||||
coord2d center = bbox.center();
|
||||
|
|
|
@ -151,6 +151,7 @@ boost::optional<marker_ptr> marker_cache::find(std::string const& uri,
|
|||
double lox,loy,hix,hiy;
|
||||
svg.bounding_rect(&lox, &loy, &hix, &hiy);
|
||||
marker_path->set_bounding_box(lox,loy,hix,hiy);
|
||||
marker_path->set_dimensions(svg.width(),svg.height());
|
||||
marker_ptr mark(boost::make_shared<marker>(marker_path));
|
||||
result.reset(mark);
|
||||
if (update_cache)
|
||||
|
|
|
@ -265,6 +265,10 @@ void svg_parser::start_element(xmlTextReaderPtr reader)
|
|||
{
|
||||
parse_ellipse(reader);
|
||||
}
|
||||
else if (xmlStrEqual(name, BAD_CAST "svg"))
|
||||
{
|
||||
parse_dimensions(reader);
|
||||
}
|
||||
#ifdef MAPNIK_LOG
|
||||
else if (!xmlStrEqual(name, BAD_CAST "svg"))
|
||||
{
|
||||
|
@ -451,6 +455,28 @@ void svg_parser::parse_attr(xmlTextReaderPtr reader)
|
|||
}
|
||||
xmlTextReaderMoveToElement(reader);
|
||||
}
|
||||
|
||||
void svg_parser::parse_dimensions(xmlTextReaderPtr reader)
|
||||
{
|
||||
xmlChar *value;
|
||||
double width = 0;
|
||||
double height = 0;
|
||||
value = xmlTextReaderGetAttribute(reader, BAD_CAST "width");
|
||||
if (value)
|
||||
{
|
||||
width = parse_double((const char*)value);
|
||||
xmlFree(value);
|
||||
}
|
||||
xmlChar *value2;
|
||||
value2 = xmlTextReaderGetAttribute(reader, BAD_CAST "width");
|
||||
if (value2)
|
||||
{
|
||||
height = parse_double((const char*)value2);
|
||||
xmlFree(value2);
|
||||
}
|
||||
path_.set_dimensions(width,height);
|
||||
|
||||
}
|
||||
void svg_parser::parse_path(xmlTextReaderPtr reader)
|
||||
{
|
||||
xmlChar *value;
|
||||
|
|
|
@ -73,12 +73,12 @@ def compare_grids(actual, expected, threshold=0, alpha=True):
|
|||
errors.append((None, actual, expected))
|
||||
return -1
|
||||
equal = (im1 == im2)
|
||||
diff = 0
|
||||
# TODO - real diffing
|
||||
if not equal:
|
||||
errors.append((1, actual, expected))
|
||||
errors.append((9999, actual, expected))
|
||||
return 9999
|
||||
passed += 1
|
||||
return diff
|
||||
return 0
|
||||
|
||||
def summary(generate=False):
|
||||
global errors
|
||||
|
|
Loading…
Reference in a new issue