Dep update
This commit is contained in:
parent
40baf6bca9
commit
49bc8e5302
8 changed files with 117 additions and 75 deletions
|
@ -1,12 +1,12 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const autoRef = require("./autoref");
|
const autoRef = require("./autoref");
|
||||||
const caryllShapeOps = require("caryll-shapeops");
|
const TypoGeom = require("typo-geom");
|
||||||
const curveUtil = require("../../support/curve-util");
|
const CurveUtil = require("../../support/curve-util");
|
||||||
const { fairifyQuad } = require("../../support/fairify");
|
const { fairifyQuad } = require("../../support/fairify");
|
||||||
const Transform = require("../../support/transform");
|
|
||||||
const { AnyCv } = require("../../support/gr");
|
const { AnyCv } = require("../../support/gr");
|
||||||
const gcFont = require("./gc");
|
const gcFont = require("./gc");
|
||||||
|
const { SpiroContourContext } = require("../../support/spiroexpand");
|
||||||
|
|
||||||
function regulateGlyph(g, skew) {
|
function regulateGlyph(g, skew) {
|
||||||
if (!g.contours) return;
|
if (!g.contours) return;
|
||||||
|
@ -29,18 +29,20 @@ function regulateGlyph(g, skew) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifyContours(contours) {
|
function simplifyContours(source) {
|
||||||
const gizmo = Transform.Id();
|
const simplifiedArcs = TypoGeom.Boolean.removeOverlap(
|
||||||
const source = [];
|
CurveUtil.convertShapeToArcs(source),
|
||||||
for (const contour of contours) {
|
TypoGeom.Boolean.PolyFillType.pftNonZero,
|
||||||
if (contour.length > 2) source.push(curveUtil.convertContourToCubic(contour));
|
1 << 17
|
||||||
}
|
);
|
||||||
const simplified = caryllShapeOps.removeOverlap(source, 1, 1 << 17, true);
|
|
||||||
|
const sc = new SpiroContourContext();
|
||||||
|
TypoGeom.transferBezArcShape(simplifiedArcs, sc);
|
||||||
|
|
||||||
const result = [];
|
const result = [];
|
||||||
for (const contour of simplified) {
|
for (const contour of sc.contours) {
|
||||||
if (contour.length <= 2) continue;
|
if (contour.length <= 2) continue;
|
||||||
result.push(curveUtil.cleanupQuadContour(fairifyQuad(contour, gizmo)));
|
result.push(CurveUtil.cleanupQuadContour(fairifyQuad(contour)));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -99,8 +101,8 @@ function extractGlyfCmap(glyphList, font) {
|
||||||
const cmap = {};
|
const cmap = {};
|
||||||
for (let g of glyphList) {
|
for (let g of glyphList) {
|
||||||
glyf[g.name] = g;
|
glyf[g.name] = g;
|
||||||
|
|
||||||
if (!g.unicode) continue;
|
if (!g.unicode) continue;
|
||||||
|
|
||||||
for (let u of g.unicode) {
|
for (let u of g.unicode) {
|
||||||
if (isFinite(u - 0)) cmap[u] = g.name;
|
if (isFinite(u - 0)) cmap[u] = g.name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -730,6 +730,17 @@ glyph-block CommonShapes : begin
|
||||||
define overlay : create-glyph : glyph-construction : include fnOverlay AS_BASE ALSO_METRICS
|
define overlay : create-glyph : glyph-construction : include fnOverlay AS_BASE ALSO_METRICS
|
||||||
define background : create-glyph : glyph-construction : include fnBackground AS_BASE ALSO_METRICS
|
define background : create-glyph : glyph-construction : include fnBackground AS_BASE ALSO_METRICS
|
||||||
|
|
||||||
|
candidates.push : glyph-construction
|
||||||
|
set this.gizmo : Translate 0 0
|
||||||
|
foreach [c : items-of overlay.contours] : foreach [z : items-of c] : if z.on : do
|
||||||
|
define x z.x
|
||||||
|
define y z.y
|
||||||
|
include : spiro-outline
|
||||||
|
corner (x - sw) (y - sw)
|
||||||
|
corner (x + sw) (y - sw)
|
||||||
|
corner (x + sw) (y + sw)
|
||||||
|
corner (x - sw) (y + sw)
|
||||||
|
|
||||||
foreach [r : range (0 - segs) till (segs)] : foreach [c : range (0 - segs) till (segs)] : do
|
foreach [r : range (0 - segs) till (segs)] : foreach [c : range (0 - segs) till (segs)] : do
|
||||||
define dx : r / segs * sw
|
define dx : r / segs * sw
|
||||||
define dy : c / segs * sw
|
define dy : c / segs * sw
|
||||||
|
@ -737,17 +748,6 @@ glyph-block CommonShapes : begin
|
||||||
include overlay
|
include overlay
|
||||||
apply-transform : Translate dx dy
|
apply-transform : Translate dx dy
|
||||||
|
|
||||||
foreach [c : items-of overlay.contours] : foreach [z : items-of c] : if z.on : do
|
|
||||||
define x z.x
|
|
||||||
define y z.y
|
|
||||||
candidates.push : glyph-construction
|
|
||||||
set this.gizmo : Translate 0 0
|
|
||||||
include : spiro-outline
|
|
||||||
corner (x - sw) (y - sw)
|
|
||||||
corner (x + sw) (y - sw)
|
|
||||||
corner (x + sw) (y + sw)
|
|
||||||
corner (x - sw) (y + sw)
|
|
||||||
|
|
||||||
include : difference background [union.apply null candidates]
|
include : difference background [union.apply null candidates]
|
||||||
include overlay
|
include overlay
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import '../support/transform' as : Transform && [object [transformPoint tp]]
|
||||||
import [curveToContour OffsetCurve] from '../support/curve-util'
|
import [curveToContour OffsetCurve] from '../support/curve-util'
|
||||||
import [mix linreg clamp fallback] from '../support/utils'
|
import [mix linreg clamp fallback] from '../support/utils'
|
||||||
import [designParameters] from '../meta/aesthetics'
|
import [designParameters] from '../meta/aesthetics'
|
||||||
import [Arc Quadify] from "typo-geom"
|
import [Arcs Quadify] from "typo-geom"
|
||||||
import [TieMark TieGlyph] from "../support/gr"
|
import [TieMark TieGlyph] from "../support/gr"
|
||||||
|
|
||||||
glyph-module
|
glyph-module
|
||||||
|
@ -225,7 +225,7 @@ glyph-block Overmarks : begin
|
||||||
define z2 : tp currentGlyph.gizmo : object [x : mix leftEnd rightEnd tildeWaveX] [y : mix tbot ttop tildeWave]
|
define z2 : tp currentGlyph.gizmo : object [x : mix leftEnd rightEnd tildeWaveX] [y : mix tbot ttop tildeWave]
|
||||||
define z3 : tp currentGlyph.gizmo : object [x : mix leftEnd rightEnd (1 - tildeWaveX)] [y : mix tbot ttop (1 - tildeWave)]
|
define z3 : tp currentGlyph.gizmo : object [x : mix leftEnd rightEnd (1 - tildeWaveX)] [y : mix tbot ttop (1 - tildeWave)]
|
||||||
define z4 : tp currentGlyph.gizmo : object [x rightEnd] [y ttop]
|
define z4 : tp currentGlyph.gizmo : object [x rightEnd] [y ttop]
|
||||||
define bone : new Arc.Bez3 z1 z2 z3 z4
|
define bone : new Arcs.Bez3 z1 z2 z3 z4
|
||||||
|
|
||||||
define inner : curveToContour [new OffsetCurve bone (+hs) HVContrast] 32
|
define inner : curveToContour [new OffsetCurve bone (+hs) HVContrast] 32
|
||||||
define outer : curveToContour [new OffsetCurve bone (-hs) HVContrast] 32
|
define outer : curveToContour [new OffsetCurve bone (-hs) HVContrast] 32
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
"install": "node utility/check-env"
|
"install": "node utility/check-env"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caryll-shapeops": "^0.3.1",
|
|
||||||
"cldr": "^5.5.4",
|
"cldr": "^5.5.4",
|
||||||
"ejs": "^3.1.3",
|
"ejs": "^3.1.3",
|
||||||
"fs-extra": "^9.0.0",
|
"fs-extra": "^9.0.0",
|
||||||
|
@ -21,7 +20,7 @@
|
||||||
"topsort": "^0.0.2",
|
"topsort": "^0.0.2",
|
||||||
"ttf2woff": "^2.0.1",
|
"ttf2woff": "^2.0.1",
|
||||||
"ttf2woff2": "^3.0.0",
|
"ttf2woff2": "^3.0.0",
|
||||||
"typo-geom": "^0.6.0",
|
"typo-geom": "^0.7.0",
|
||||||
"unicode-13.0.0": "^0.8.0",
|
"unicode-13.0.0": "^0.8.0",
|
||||||
"unorm": "^1.6.0",
|
"unorm": "^1.6.0",
|
||||||
"verda": "^1.0.1",
|
"verda": "^1.0.1",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'caryll-shapeops' as ShapeOps
|
import 'typo-geom' as TypoGeom
|
||||||
import './curve-util' as CurveUtil
|
import './curve-util' as CurveUtil
|
||||||
|
import './spiroexpand' as [object SpiroContourContext]
|
||||||
|
|
||||||
export : define [SetupBuilders args] : begin
|
export : define [SetupBuilders args] : begin
|
||||||
define [object Glyph globalTransform] args
|
define [object Glyph globalTransform] args
|
||||||
|
@ -15,13 +16,15 @@ export : define [SetupBuilders args] : begin
|
||||||
set g1.gizmo : this.gizmo || globalTransform
|
set g1.gizmo : this.gizmo || globalTransform
|
||||||
g1.include item
|
g1.include item
|
||||||
set g1.contours : g1.contours.map CurveUtil.convertContourToCubic
|
set g1.contours : g1.contours.map CurveUtil.convertContourToCubic
|
||||||
local c1 : ShapeOps.boole operator g.contours g1.contours ShapeOps.fillRules.nonzero ShapeOps.fillRules.nonzero 16384
|
local c1 : TypoGeom.Boolean.combine operator [CurveUtil.convertShapeToArcs g.contours] [CurveUtil.convertShapeToArcs g1.contours] TypoGeom.Boolean.PolyFillType.pftNonZero TypoGeom.Boolean.PolyFillType.pftNonZero 16384
|
||||||
set g.contours : c1.map CurveUtil.convertContourToCubicRev
|
local ctx : new SpiroContourContext
|
||||||
|
TypoGeom.transferBezArcShape c1 ctx
|
||||||
|
set g.contours ctx.contours
|
||||||
this.includeGlyph g
|
this.includeGlyph g
|
||||||
return g
|
return g
|
||||||
|
|
||||||
define union : Boole ShapeOps.ops.union
|
define union : Boole TypoGeom.Boolean.ClipType.ctUnion
|
||||||
define intersection : Boole ShapeOps.ops.intersection
|
define intersection : Boole TypoGeom.Boolean.ClipType.ctIntersection
|
||||||
define difference : Boole ShapeOps.ops.difference
|
define difference : Boole TypoGeom.Boolean.ClipType.ctDifference
|
||||||
|
|
||||||
return [object union intersection difference]
|
return [object union intersection difference]
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const typoGeom = require("typo-geom");
|
const TypoGeom = require("typo-geom");
|
||||||
const Point = require("./point");
|
const Point = require("./point");
|
||||||
const { mix } = require("./utils");
|
const { mix } = require("./utils");
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ function autoCubify(arc, err) {
|
||||||
if (s > 0) offPoints.push(z0);
|
if (s > 0) offPoints.push(z0);
|
||||||
offPoints.push(z1, z2);
|
offPoints.push(z1, z2);
|
||||||
|
|
||||||
const bezArc = new typoGeom.Arc.Bez3(z0, z1, z2, z3);
|
const bezArc = new TypoGeom.Arcs.Bez3(z0, z1, z2, z3);
|
||||||
|
|
||||||
for (let k = 1; k < perSegHits; k++) {
|
for (let k = 1; k < perSegHits; k++) {
|
||||||
const tk = k / perSegHits;
|
const tk = k / perSegHits;
|
||||||
|
@ -215,8 +215,65 @@ function fixedCubify(arc, nSeg) {
|
||||||
return offPoints;
|
return offPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertContourToArcs(contour) {
|
||||||
|
if (!contour || !contour.length) return [];
|
||||||
|
|
||||||
|
const newContour = [];
|
||||||
|
let z0 = Point.cornerFrom(contour[0]);
|
||||||
|
|
||||||
|
for (let j = 1; j < contour.length; j++) {
|
||||||
|
const z = contour[j];
|
||||||
|
if (z.on) {
|
||||||
|
newContour.push(
|
||||||
|
TypoGeom.Arcs.Bez3.fromStraightSegment(
|
||||||
|
new TypoGeom.Arcs.StraightSegment(z0, Point.cornerFrom(z))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
z0 = z;
|
||||||
|
} else if (z.cubic) {
|
||||||
|
const z1 = z;
|
||||||
|
const z2 = contour[j + 1];
|
||||||
|
const z3 = contour[j + 2];
|
||||||
|
newContour.push(
|
||||||
|
new TypoGeom.Arcs.Bez3(
|
||||||
|
z0,
|
||||||
|
Point.cubicOffFrom(z1),
|
||||||
|
Point.cubicOffFrom(z2),
|
||||||
|
Point.cornerFrom(z3)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
z0 = z3;
|
||||||
|
j += 2;
|
||||||
|
} else {
|
||||||
|
const zc = z;
|
||||||
|
let zf = contour[j + 1] || contour[0];
|
||||||
|
const zfIsCorner = zf.on;
|
||||||
|
if (!zfIsCorner) zf = Point.cornerFrom(zc).mix(0.5, zf);
|
||||||
|
|
||||||
|
newContour.push(
|
||||||
|
new TypoGeom.Arcs.Bez3(
|
||||||
|
z0,
|
||||||
|
Point.cubicOffFrom(z0).mix(2 / 3, zc),
|
||||||
|
Point.cubicOffFrom(zf).mix(2 / 3, zc),
|
||||||
|
Point.cornerFrom(zf)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
z0 = zf;
|
||||||
|
if (zfIsCorner) j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newContour;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertShapeToArcs(shape) {
|
||||||
|
return shape.map(convertContourToArcs);
|
||||||
|
}
|
||||||
|
|
||||||
exports.cleanupQuadContour = cleanupQuadContour;
|
exports.cleanupQuadContour = cleanupQuadContour;
|
||||||
exports.convertContourToCubic = convertContourToCubic;
|
exports.convertContourToCubic = convertContourToCubic;
|
||||||
exports.convertContourToCubicRev = convertContourToCubicRev;
|
exports.convertContourToCubicRev = convertContourToCubicRev;
|
||||||
exports.autoCubify = autoCubify;
|
exports.autoCubify = autoCubify;
|
||||||
exports.fixedCubify = fixedCubify;
|
exports.fixedCubify = fixedCubify;
|
||||||
|
exports.convertShapeToArcs = convertShapeToArcs;
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Transform = require("./transform");
|
|
||||||
const typoGeom = require("typo-geom");
|
const typoGeom = require("typo-geom");
|
||||||
const Point = require("./point");
|
const Point = require("./point");
|
||||||
const curveUtil = require("./curve-util");
|
|
||||||
|
|
||||||
const SMALL = 1e-6;
|
const SMALL = 1e-6;
|
||||||
|
|
||||||
|
@ -201,7 +199,7 @@ class BezierCurveCluster {
|
||||||
if (zs[j].on) {
|
if (zs[j].on) {
|
||||||
const z1 = last,
|
const z1 = last,
|
||||||
z4 = zs[j];
|
z4 = zs[j];
|
||||||
const seg = new typoGeom.Arc.StraightSegment(z1, z4);
|
const seg = new typoGeom.Arcs.StraightSegment(z1, z4);
|
||||||
segments.push(seg);
|
segments.push(seg);
|
||||||
lengths.push(this.measureLength(seg));
|
lengths.push(this.measureLength(seg));
|
||||||
last = z4;
|
last = z4;
|
||||||
|
@ -210,7 +208,7 @@ class BezierCurveCluster {
|
||||||
z2 = zs[j],
|
z2 = zs[j],
|
||||||
z3 = zs[j + 1],
|
z3 = zs[j + 1],
|
||||||
z4 = zs[j + 2];
|
z4 = zs[j + 2];
|
||||||
const seg = new typoGeom.Arc.Bez3(z1, z2, z3, z4);
|
const seg = new typoGeom.Arcs.Bez3(z1, z2, z3, z4);
|
||||||
segments.push(seg);
|
segments.push(seg);
|
||||||
lengths.push(this.measureLength(seg));
|
lengths.push(this.measureLength(seg));
|
||||||
last = z4;
|
last = z4;
|
||||||
|
@ -288,15 +286,15 @@ class BezierCurveCluster {
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuadBuilder = {
|
const QuadBuilder = {
|
||||||
corner(sink, gizmo, z) {
|
corner(sink, z) {
|
||||||
sink.push(Transform.transformPoint(gizmo, Point.cornerFrom(z)).round(1024));
|
sink.push(Point.cornerFrom(z).round(1024));
|
||||||
},
|
},
|
||||||
arc(sink, gizmo, arc) {
|
arc(sink, arc) {
|
||||||
if (arc.isAlmostLinear(1 / 4)) return;
|
if (arc.isAlmostLinear(1 / 4)) return;
|
||||||
const offPoints = typoGeom.Quadify.auto(arc, 1 / 4);
|
const offPoints = typoGeom.Quadify.auto(arc, 1 / 4);
|
||||||
if (!offPoints) return;
|
if (!offPoints) return;
|
||||||
for (const z of offPoints) {
|
for (const z of offPoints) {
|
||||||
sink.push(Transform.transformPoint(gizmo, Point.offFrom(z)).round(1024));
|
sink.push(Point.offFrom(z).round(1024));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
split: true,
|
split: true,
|
||||||
|
@ -304,55 +302,31 @@ const QuadBuilder = {
|
||||||
duplicateStart: true
|
duplicateStart: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpiroBuilder = {
|
function buildCurve(curve, builder) {
|
||||||
corner(sink, gizmo, z) {
|
|
||||||
sink.push(Transform.transformPoint(gizmo, Point.cornerFrom(z)));
|
|
||||||
},
|
|
||||||
arc(sink, gizmo, arc) {
|
|
||||||
if (arc.isAlmostLinear(1 / 4)) return;
|
|
||||||
const offPoints = curveUtil.fixedCubify(arc, 12);
|
|
||||||
for (const z of offPoints) {
|
|
||||||
sink.push(Transform.transformPoint(gizmo, z));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
split: true,
|
|
||||||
canonicalStart: false,
|
|
||||||
duplicateStart: false
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildCurve(curve, gizmo, builder) {
|
|
||||||
let sink = [];
|
let sink = [];
|
||||||
for (let j = 0; j < curve.length; j++) {
|
for (let j = 0; j < curve.length; j++) {
|
||||||
if (!curve[j].mark) continue;
|
if (!curve[j].mark) continue;
|
||||||
builder.corner(sink, gizmo, curve[j]);
|
builder.corner(sink, curve[j]);
|
||||||
|
|
||||||
let k = j;
|
let k = j;
|
||||||
for (; k < curve.length && (k === j || !curve[k].mark); k++);
|
for (; k < curve.length && (k === j || !curve[k].mark); k++);
|
||||||
const pts = curve.slice(j, k + 1);
|
const pts = curve.slice(j, k + 1);
|
||||||
if (pts.length > 1) builder.arc(sink, gizmo, new BezierCurveCluster(pts));
|
if (pts.length > 1) builder.arc(sink, new BezierCurveCluster(pts));
|
||||||
j = k - 1;
|
j = k - 1;
|
||||||
}
|
}
|
||||||
return sink;
|
return sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fairifyImpl(sourceCubicContour, gizmo, builder) {
|
function fairifyImpl(sourceCubicContour, builder) {
|
||||||
for (let j = 0; j < sourceCubicContour.length; j++) {
|
|
||||||
if (!isFinite(sourceCubicContour[j].x)) sourceCubicContour[j].x = 0;
|
|
||||||
if (!isFinite(sourceCubicContour[j].y)) sourceCubicContour[j].y = 0;
|
|
||||||
sourceCubicContour[j] = Transform.unTransform(gizmo, sourceCubicContour[j]);
|
|
||||||
}
|
|
||||||
let splitContour = toSpansForm(sourceCubicContour, builder.split);
|
let splitContour = toSpansForm(sourceCubicContour, builder.split);
|
||||||
markCorners(splitContour);
|
markCorners(splitContour);
|
||||||
if (builder.canonicalStart) {
|
if (builder.canonicalStart) {
|
||||||
splitContour = canonicalStart(splitContour);
|
splitContour = canonicalStart(splitContour);
|
||||||
markCorners(splitContour);
|
markCorners(splitContour);
|
||||||
}
|
}
|
||||||
return buildCurve(splitContour, gizmo, builder);
|
return buildCurve(splitContour, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fairifyQuad = function (sourceCubicContour, gizmo) {
|
exports.fairifyQuad = function (sourceCubicContour) {
|
||||||
return fairifyImpl(sourceCubicContour, gizmo, QuadBuilder);
|
return fairifyImpl(sourceCubicContour, QuadBuilder);
|
||||||
};
|
|
||||||
exports.fairifySpiro = function (sourceCubicContour, gizmo) {
|
|
||||||
return fairifyImpl(sourceCubicContour, gizmo, SpiroBuilder);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,9 @@ class SpiroExpansionContext
|
||||||
set this.defaultd1 0
|
set this.defaultd1 0
|
||||||
set this.defaultd2 0
|
set this.defaultd2 0
|
||||||
|
|
||||||
|
public [beginShape] : begin
|
||||||
|
public [endShape] : begin
|
||||||
|
|
||||||
public [moveTo x y unimportant] : begin
|
public [moveTo x y unimportant] : begin
|
||||||
if unimportant : return nothing
|
if unimportant : return nothing
|
||||||
# Transform incoming knots using gizmo
|
# Transform incoming knots using gizmo
|
||||||
|
@ -156,6 +159,10 @@ class SpiroContourContext
|
||||||
set this.contours { }
|
set this.contours { }
|
||||||
set this.defaultTag null
|
set this.defaultTag null
|
||||||
|
|
||||||
|
public [beginShape] : begin
|
||||||
|
|
||||||
|
public [endShape] : begin
|
||||||
|
|
||||||
public [moveTo x y] : begin
|
public [moveTo x y] : begin
|
||||||
local contour {[Point.transformed this.gizmo x y true]}
|
local contour {[Point.transformed this.gizmo x y true]}
|
||||||
set contour.tag this.defaultTag
|
set contour.tag this.defaultTag
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue