Atlas padding WIP

This commit is contained in:
Chlumsky 2024-04-28 16:47:41 +02:00
parent b67fb967c3
commit 37e031b2b5
13 changed files with 482 additions and 121 deletions

View File

@ -15,8 +15,8 @@ bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, msdfge
codepoint = 0;
advance *= geometryScale;
#ifdef MSDFGEN_USE_SKIA
if (preprocessGeometry)
msdfgen::resolveShapeGeometry(shape);
if (preprocessGeometry)
msdfgen::resolveShapeGeometry(shape);
#endif
shape.normalize();
bounds = shape.getBounds();
@ -51,22 +51,21 @@ void GlyphGeometry::edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned l
fn(shape, angleThreshold, seed);
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin) {
wrapBox(scale, range, miterLimit, pxAlignOrigin, pxAlignOrigin);
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY) {
scale *= geometryScale;
range /= geometryScale;
void GlyphGeometry::wrapBox(const GlyphAttributes &glyphAttributes) {
double scale = glyphAttributes.scale*geometryScale;
msdfgen::Range range = glyphAttributes.range/geometryScale;
Padding fullPadding = 1/geometryScale*(glyphAttributes.innerPadding+glyphAttributes.outerPadding);
box.range = range;
box.scale = scale;
if (bounds.l < bounds.r && bounds.b < bounds.t) {
double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
l -= .5*range, b -= .5*range;
r += .5*range, t += .5*range;
if (miterLimit > 0)
shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1);
if (pxAlignOriginX) {
l += range.lower, b += range.lower;
r -= range.lower, t -= range.lower;
if (glyphAttributes.miterLimit > 0)
shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1);
l -= fullPadding.l, b -= fullPadding.b;
r += fullPadding.r, t += fullPadding.t;
if (glyphAttributes.pxAlignOriginX) {
int sl = (int) floor(scale*l-.5);
int sr = (int) ceil(scale*r+.5);
box.rect.w = sr-sl;
@ -76,7 +75,7 @@ void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool
box.rect.w = (int) ceil(w)+1;
box.translate.x = -l+.5*(box.rect.w-w)/scale;
}
if (pxAlignOriginY) {
if (glyphAttributes.pxAlignOriginY) {
int sb = (int) floor(scale*b-.5);
int st = (int) ceil(scale*t+.5);
box.rect.h = st-sb;
@ -86,19 +85,37 @@ void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool
box.rect.h = (int) ceil(h)+1;
box.translate.y = -b+.5*(box.rect.h-h)/scale;
}
box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding;
} else {
box.rect.w = 0, box.rect.h = 0;
box.translate = msdfgen::Vector2();
}
}
void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin) {
frameBox(scale, range, miterLimit, width, height, fixedX, fixedY, pxAlignOrigin, pxAlignOrigin);
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOrigin;
attribs.pxAlignOriginY = pxAlignOrigin;
wrapBox(attribs);
}
void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY) {
scale *= geometryScale;
range /= geometryScale;
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
wrapBox(attribs);
}
void GlyphGeometry::frameBox(const GlyphAttributes &glyphAttributes, int width, int height, const double *fixedX, const double *fixedY) {
double scale = glyphAttributes.scale*geometryScale;
msdfgen::Range range = glyphAttributes.range/geometryScale;
Padding fullPadding = 1/geometryScale*(glyphAttributes.innerPadding+glyphAttributes.outerPadding);
box.range = range;
box.scale = scale;
box.rect.w = width;
@ -108,13 +125,15 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int
box.translate.y = *fixedY/geometryScale;
} else {
double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
l -= .5*range, b -= .5*range;
r += .5*range, t += .5*range;
if (miterLimit > 0)
shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1);
l += range.lower, b += range.lower;
r -= range.lower, t -= range.lower;
if (glyphAttributes.miterLimit > 0)
shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1);
l -= fullPadding.l, b -= fullPadding.b;
r += fullPadding.r, t += fullPadding.t;
if (fixedX)
box.translate.x = *fixedX/geometryScale;
else if (pxAlignOriginX) {
else if (glyphAttributes.pxAlignOriginX) {
int sl = (int) floor(scale*l-.5);
int sr = (int) ceil(scale*r+.5);
box.translate.x = (-sl+(box.rect.w-(sr-sl))/2)/scale;
@ -124,7 +143,7 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int
}
if (fixedY)
box.translate.y = *fixedY/geometryScale;
else if (pxAlignOriginY) {
else if (glyphAttributes.pxAlignOriginY) {
int sb = (int) floor(scale*b-.5);
int st = (int) ceil(scale*t+.5);
box.translate.y = (-sb+(box.rect.h-(st-sb))/2)/scale;
@ -133,6 +152,27 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int
box.translate.y = -b+.5*(box.rect.h-h)/scale;
}
}
box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding;
}
void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOrigin;
attribs.pxAlignOriginY = pxAlignOrigin;
frameBox(attribs, width, height, fixedX, fixedY);
}
void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
frameBox(attribs, width, height, fixedX, fixedY);
}
void GlyphGeometry::placeBox(int x, int y) {
@ -194,7 +234,7 @@ void GlyphGeometry::getBoxSize(int &w, int &h) const {
w = box.rect.w, h = box.rect.h;
}
double GlyphGeometry::getBoxRange() const {
msdfgen::Range GlyphGeometry::getBoxRange() const {
return box.range;
}
@ -213,20 +253,20 @@ msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const {
void GlyphGeometry::getQuadPlaneBounds(double &l, double &b, double &r, double &t) const {
if (box.rect.w > 0 && box.rect.h > 0) {
double invBoxScale = 1/box.scale;
l = geometryScale*(-box.translate.x+.5*invBoxScale);
b = geometryScale*(-box.translate.y+.5*invBoxScale);
r = geometryScale*(-box.translate.x+(box.rect.w-.5)*invBoxScale);
t = geometryScale*(-box.translate.y+(box.rect.h-.5)*invBoxScale);
l = geometryScale*(-box.translate.x+(box.outerPadding.l+.5)*invBoxScale);
b = geometryScale*(-box.translate.y+(box.outerPadding.b+.5)*invBoxScale);
r = geometryScale*(-box.translate.x+(-box.outerPadding.r+box.rect.w-.5)*invBoxScale);
t = geometryScale*(-box.translate.y+(-box.outerPadding.t+box.rect.h-.5)*invBoxScale);
} else
l = 0, b = 0, r = 0, t = 0;
}
void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const {
if (box.rect.w > 0 && box.rect.h > 0) {
l = box.rect.x+.5;
b = box.rect.y+.5;
r = box.rect.x+box.rect.w-.5;
t = box.rect.y+box.rect.h-.5;
l = box.rect.x+box.outerPadding.l+.5;
b = box.rect.y+box.outerPadding.b+.5;
r = box.rect.x-box.outerPadding.r+box.rect.w-.5;
t = box.rect.y-box.outerPadding.t+box.rect.h-.5;
} else
l = 0, b = 0, r = 0, t = 0;
}
@ -244,4 +284,26 @@ GlyphGeometry::operator GlyphBox() const {
return box;
}
msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b) {
return msdfgen::Range(a.lower+b.lower, a.upper+b.upper);
}
Padding operator+(const Padding &a, const Padding &b) {
Padding result;
result.l = a.l+b.l;
result.b = a.b+b.b;
result.r = a.r+b.r;
result.t = a.t+b.t;
return result;
}
Padding operator*(double a, const Padding &b) {
Padding result;
result.l = a*b.l;
result.b = a*b.b;
result.r = a*b.r;
result.t = a*b.t;
return result;
}
}

View File

@ -9,10 +9,20 @@
namespace msdf_atlas {
typedef msdfgen::Shape::Bounds Padding;
/// Represents the shape geometry of a single glyph as well as its configuration
class GlyphGeometry {
public:
struct GlyphAttributes {
double scale;
msdfgen::Range range;
Padding innerPadding, outerPadding;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
};
GlyphGeometry();
/// Loads glyph geometry from font
bool load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
@ -20,9 +30,11 @@ public:
/// Applies edge coloring to glyph shape
void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed);
/// Computes the dimensions of the glyph's box as well as the transformation for the generator function
void wrapBox(const GlyphAttributes &glyphAttributes);
void wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin = false);
void wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY);
/// Computes the glyph's transformation and alignment (unless specified) for given dimensions
void frameBox(const GlyphAttributes &glyphAttributes, int width, int height, const double *fixedX, const double *fixedY);
void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin = false);
void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY);
/// Sets the glyph's box's position in the atlas
@ -52,7 +64,7 @@ public:
/// Outputs the dimensions of the glyph's box in the atlas
void getBoxSize(int &w, int &h) const;
/// Returns the range needed to generate the glyph's SDF
double getBoxRange() const;
msdfgen::Range getBoxRange() const;
/// Returns the projection needed to generate the glyph's bitmap
msdfgen::Projection getBoxProjection() const;
/// Returns the scale needed to generate the glyph's bitmap
@ -77,11 +89,17 @@ private:
double advance;
struct {
Rectangle rect;
double range;
msdfgen::Range range;
double scale;
msdfgen::Vector2 translate;
Padding outerPadding;
} box;
};
msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b);
Padding operator+(const Padding &a, const Padding &b);
Padding operator*(double a, const Padding &b);
}

View File

@ -102,25 +102,27 @@ GridAtlasPacker::GridAtlasPacker() :
pxRange(0),
miterLimit(0),
pxAlignOriginX(false), pxAlignOriginY(false),
innerUnitPadding(), outerUnitPadding(),
innerPxPadding(), outerPxPadding(),
scaleMaximizationTolerance(.001),
alignedColumnsBias(.125),
cutoff(false)
{ }
msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double range) const {
msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double outerRange) const {
static const double LARGE_VALUE = 1e240;
msdfgen::Shape::Bounds maxBounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE };
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
double geometryScale = glyph->getGeometryScale();
double shapeRange = range/geometryScale;
double shapeOuterRange = outerRange/geometryScale;
geometryScale *= scale;
const msdfgen::Shape::Bounds &shapeBounds = glyph->getShapeBounds();
double l = shapeBounds.l, b = shapeBounds.b, r = shapeBounds.r, t = shapeBounds.t;
l -= .5*shapeRange, b -= .5*shapeRange;
r += .5*shapeRange, t += .5*shapeRange;
l -= shapeOuterRange, b -= shapeOuterRange;
r += shapeOuterRange, t += shapeOuterRange;
if (miterLimit > 0)
glyph->getShape().boundMiters(l, b, r, t, .5*shapeRange, miterLimit, 1);
glyph->getShape().boundMiters(l, b, r, t, shapeOuterRange, miterLimit, 1);
l *= geometryScale, b *= geometryScale;
r *= geometryScale, t *= geometryScale;
maxBounds.l = std::min(maxBounds.l, l);
@ -133,6 +135,11 @@ msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &m
}
if (maxBounds.l >= maxBounds.r || maxBounds.b >= maxBounds.t)
maxBounds = msdfgen::Shape::Bounds();
Padding fullPadding = scale*(innerUnitPadding+outerUnitPadding)+innerPxPadding+outerPxPadding;
maxBounds.l -= fullPadding.l, maxBounds.b -= fullPadding.b;
maxBounds.r += fullPadding.r, maxBounds.t += fullPadding.t;
maxWidth += fullPadding.l+fullPadding.r;
maxHeight += fullPadding.b+fullPadding.t;
// If origin is pixel-aligned but not fixed, one pixel has to be added to max dimensions to allow for aligning the origin by shifting by < 1 pixel
if (hFixed)
maxWidth = maxBounds.r-maxBounds.l;
@ -154,7 +161,7 @@ double GridAtlasPacker::scaleToFit(GlyphGeometry *glyphs, int count, int cellWid
--cellWidth, --cellHeight; // Implicit half-pixel padding from each side to make sure that no representable values are beyond outermost pixel centers
cellWidth -= spacing, cellHeight -= spacing;
bool lastResult = false;
#define TRY_FIT(scale) (maxWidth = 0, maxHeight = 0, maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, (scale), unitRange+pxRange/(scale)), lastResult = maxWidth <= cellWidth && maxHeight <= cellHeight)
#define TRY_FIT(scale) (maxWidth = 0, maxHeight = 0, maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, (scale), -(unitRange.lower+pxRange.lower/(scale))), lastResult = maxWidth <= cellWidth && maxHeight <= cellHeight)
double minScale = 1, maxScale = 1;
if (TRY_FIT(1)) {
while (maxScale < 1e+32 && ((maxScale = 2*minScale), TRY_FIT(maxScale)))
@ -220,7 +227,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
return -1;
}
if ((cellWidth > 0 && cellWidth-spacing-1 <= pxRange) || (cellHeight > 0 && cellHeight-spacing-1 <= pxRange)) // cells definitely too small
if ((cellWidth > 0 && cellWidth-spacing-1 <= -2*pxRange.lower) || (cellHeight > 0 && cellHeight-spacing-1 <= -2*pxRange.lower)) // cells definitely too small
return -1;
msdfgen::Shape::Bounds maxBounds = { };
@ -229,14 +236,14 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
if (scale <= 0) {
// If both pxRange and miterLimit is non-zero, miter bounds have to be computed for all potential scales
if (pxRange && miterLimit > 0) {
if (pxRange.lower != pxRange.upper && miterLimit > 0) {
if (cellWidth > 0 || cellHeight > 0) {
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
if (scale < minScale) {
scale = minScale;
cutoff = true;
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale);
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, -(unitRange.lower+pxRange.lower/scale));
}
}
@ -296,13 +303,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
}
if (scale <= 0) {
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, minScale, unitRange+pxRange/minScale);
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, minScale, -(unitRange.lower+pxRange.lower/minScale));
cellWidth = (int) ceil(maxWidth)+spacing+1;
cellHeight = (int) ceil(maxHeight)+spacing+1;
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
if (scale < minScale)
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale = minScale, unitRange+pxRange/minScale);
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale = minScale, -(unitRange.lower+pxRange.lower/minScale));
}
if (initial.rows < 0 && initial.cellHeight < 0) {
@ -316,7 +323,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
} else {
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, 1, unitRange);
Padding pxPadding = innerPxPadding+outerPxPadding;
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, 1, -unitRange.lower);
// Undo pxPadding added by getMaxBounds before pixel scale is known
maxBounds.l += pxPadding.l, maxBounds.b += pxPadding.b;
maxBounds.r -= pxPadding.r, maxBounds.t -= pxPadding.t;
maxWidth -= pxPadding.l+pxPadding.r;
maxHeight -= pxPadding.b+pxPadding.t;
int hSlack = 0, vSlack = 0;
if (pxAlignOriginX && !hFixed) {
maxWidth -= 1; // Added by getMaxBounds
@ -327,11 +340,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
vSlack = 1;
}
double extraPxWidth = -2*pxRange.lower+pxPadding.l+pxPadding.r;
double extraPxHeight = -2*pxRange.lower+pxPadding.b+pxPadding.t;
double hScale = 0, vScale = 0;
if (cellWidth > 0)
hScale = (cellWidth-hSlack-spacing-1-pxRange)/maxWidth;
hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
if (cellHeight > 0)
vScale = (cellHeight-vSlack-spacing-1-pxRange)/maxHeight;
vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
if (hScale || vScale) {
if (hScale && vScale)
scale = std::min(hScale, vScale);
@ -353,8 +368,8 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
int tHeight = (height+spacing)/rows;
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
if (tWidth > 0 && tHeight > 0) {
hScale = (tWidth-hSlack-spacing-1-pxRange)/maxWidth;
vScale = (tHeight-vSlack-spacing-1-pxRange)/maxHeight;
hScale = (tWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
vScale = (tHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
double curScale = std::min(hScale, vScale);
if (curScale > scale) {
scale = curScale;
@ -384,16 +399,16 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
}
if (scale <= 0) {
cellWidth = (int) ceil(minScale*maxWidth+pxRange)+hSlack+spacing+1;
cellHeight = (int) ceil(minScale*maxHeight+pxRange)+vSlack+spacing+1;
cellWidth = (int) ceil(minScale*maxWidth+extraPxWidth)+hSlack+spacing+1;
cellHeight = (int) ceil(minScale*maxHeight+extraPxHeight)+vSlack+spacing+1;
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
hScale = (cellWidth-hSlack-spacing-1-pxRange)/maxWidth;
vScale = (cellHeight-vSlack-spacing-1-pxRange)/maxHeight;
hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
scale = std::min(hScale, vScale);
}
if (initial.rows < 0 && initial.cellHeight < 0) {
int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(scale*maxHeight+pxRange)+vSlack+spacing+1;
int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(scale*maxHeight+extraPxHeight)+vSlack+spacing+1;
raiseToConstraint(optimalCellWidth, optimalCellHeight, cellDimensionsConstraint);
if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) {
cellWidth = optimalCellWidth;
@ -403,11 +418,16 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
maxBounds.l *= scale, maxBounds.b *= scale;
maxBounds.r *= scale, maxBounds.t *= scale;
maxWidth *= scale, maxHeight *= scale;
// Redo addition of pxPadding once scale is known
maxBounds.l -= pxPadding.l, maxBounds.b -= pxPadding.b;
maxBounds.r += pxPadding.r, maxBounds.t += pxPadding.t;
maxWidth += pxPadding.l+pxPadding.r;
maxHeight += pxPadding.b+pxPadding.t;
}
} else {
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale);
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, -(unitRange.lower+pxRange.lower/scale));
int optimalCellWidth = (int) ceil(maxWidth)+spacing+1;
int optimalCellHeight = (int) ceil(maxHeight)+spacing+1;
if (cellWidth < 0 || cellHeight < 0) {
@ -481,10 +501,18 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
if (rows*cellHeight > height)
rows = height/cellHeight;
GlyphGeometry::GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = unitRange+pxRange/scale;
attribs.innerPadding = innerUnitPadding+1/scale*innerPxPadding;
attribs.outerPadding = outerUnitPadding+1/scale*outerPxPadding;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
int col = 0, row = 0;
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
glyph->frameBox(scale, unitRange+pxRange/scale, miterLimit, cellWidth-spacing, cellHeight-spacing, hFixed ? &fixedX : nullptr, vFixed ? &fixedY : nullptr, pxAlignOriginX, pxAlignOriginY);
glyph->frameBox(attribs, cellWidth-spacing, cellHeight-spacing, hFixed ? &fixedX : nullptr, vFixed ? &fixedY : nullptr);
glyph->placeBox(col*cellWidth, height-(row+1)*cellHeight);
if (++col >= columns) {
if (++row >= rows) {
@ -554,11 +582,11 @@ void GridAtlasPacker::setMinimumScale(double minScale) {
this->minScale = minScale;
}
void GridAtlasPacker::setUnitRange(double unitRange) {
void GridAtlasPacker::setUnitRange(msdfgen::Range unitRange) {
this->unitRange = unitRange;
}
void GridAtlasPacker::setPixelRange(double pxRange) {
void GridAtlasPacker::setPixelRange(msdfgen::Range pxRange) {
this->pxRange = pxRange;
}
@ -574,6 +602,44 @@ void GridAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) {
pxAlignOriginX = alignX, pxAlignOriginY = alignY;
}
static Padding makeUniformPadding(double width) {
Padding p;
p.l = width, p.b = width, p.r = width, p.t = width;
return p;
}
void GridAtlasPacker::setInnerUnitPadding(const Padding &padding) {
innerUnitPadding = padding;
}
void GridAtlasPacker::setInnerUnitPadding(double uniformPadding) {
innerUnitPadding = makeUniformPadding(uniformPadding);
}
void GridAtlasPacker::setOuterUnitPadding(const Padding &padding) {
outerUnitPadding = padding;
}
void GridAtlasPacker::setOuterUnitPadding(double uniformPadding) {
outerUnitPadding = makeUniformPadding(uniformPadding);
}
void GridAtlasPacker::setInnerPixelPadding(const Padding &padding) {
innerPxPadding = padding;
}
void GridAtlasPacker::setInnerPixelPadding(double uniformPadding) {
innerPxPadding = makeUniformPadding(uniformPadding);
}
void GridAtlasPacker::setOuterPixelPadding(const Padding &padding) {
outerPxPadding = padding;
}
void GridAtlasPacker::setOuterPixelPadding(double uniformPadding) {
outerPxPadding = makeUniformPadding(uniformPadding);
}
void GridAtlasPacker::getDimensions(int &width, int &height) const {
width = this->width, height = this->height;
}
@ -594,7 +660,7 @@ double GridAtlasPacker::getScale() const {
return scale;
}
double GridAtlasPacker::getPixelRange() const {
msdfgen::Range GridAtlasPacker::getPixelRange() const {
return pxRange+scale*unitRange;
}

View File

@ -40,14 +40,26 @@ public:
/// Sets the minimum glyph scale
void setMinimumScale(double minScale);
/// Sets the unit component of the total distance range
void setUnitRange(double unitRange);
void setUnitRange(msdfgen::Range unitRange);
/// Sets the pixel component of the total distance range
void setPixelRange(double pxRange);
void setPixelRange(msdfgen::Range pxRange);
/// Sets the miter limit for bounds computation
void setMiterLimit(double miterLimit);
/// Sets whether each glyph's origin point should stay aligned with the pixel grid
void setOriginPixelAlignment(bool align);
void setOriginPixelAlignment(bool alignX, bool alignY);
/// Sets the unit component of width of additional padding that is part of each glyph quad
void setInnerUnitPadding(const Padding &padding);
void setInnerUnitPadding(double uniformPadding);
/// Sets the unit component of width of additional padding around each glyph quad
void setOuterUnitPadding(const Padding &padding);
void setOuterUnitPadding(double uniformPadding);
/// Sets the pixel component of width of additional padding that is part of each glyph quad
void setInnerPixelPadding(const Padding &padding);
void setInnerPixelPadding(double uniformPadding);
/// Sets the pixel component of width of additional padding around each glyph quad
void setOuterPixelPadding(const Padding &padding);
void setOuterPixelPadding(double uniformPadding);
/// Outputs the atlas's final dimensions
void getDimensions(int &width, int &height) const;
@ -60,7 +72,7 @@ public:
/// Returns the final glyph scale
double getScale() const;
/// Returns the final combined pixel range (including converted unit range)
double getPixelRange() const;
msdfgen::Range getPixelRange() const;
/// Outputs the position of the origin within each cell, each value is only valid if the origin is fixed in the respective dimension
void getFixedOrigin(double &x, double &y);
/// Returns true if the explicitly constrained cell dimensions aren't large enough to fit each glyph fully
@ -77,10 +89,12 @@ private:
double scale;
double minScale;
double fixedX, fixedY;
double unitRange;
double pxRange;
msdfgen::Range unitRange;
msdfgen::Range pxRange;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
Padding innerUnitPadding, outerUnitPadding;
Padding innerPxPadding, outerPxPadding;
double scaleMaximizationTolerance;
double alignedColumnsBias;
bool cutoff;
@ -89,7 +103,7 @@ private:
static void raiseToConstraint(int &width, int &height, DimensionsConstraint constraint);
double dimensionsRating(int width, int height, bool aligned) const;
msdfgen::Shape::Bounds getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double range) const;
msdfgen::Shape::Bounds getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double outerRange) const;
double scaleToFit(GlyphGeometry *glyphs, int count, int cellWidth, int cellHeight, msdfgen::Shape::Bounds &maxBounds, double &maxWidth, double &maxHeight) const;
};

View File

@ -18,20 +18,29 @@ TightAtlasPacker::TightAtlasPacker() :
pxRange(0),
miterLimit(0),
pxAlignOriginX(false), pxAlignOriginY(false),
innerUnitPadding(), outerUnitPadding(),
innerPxPadding(), outerPxPadding(),
scaleMaximizationTolerance(.001)
{ }
int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const {
double range = unitRange+pxRange/scale;
// Wrap glyphs into boxes
std::vector<Rectangle> rectangles;
std::vector<GlyphGeometry *> rectangleGlyphs;
rectangles.reserve(count);
rectangleGlyphs.reserve(count);
GlyphGeometry::GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = unitRange+pxRange/scale;
attribs.innerPadding = innerUnitPadding+1/scale*innerPxPadding;
attribs.outerPadding = outerUnitPadding+1/scale*outerPxPadding;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
Rectangle rect = { };
glyph->wrapBox(scale, range, miterLimit, pxAlignOriginX, pxAlignOriginY);
glyph->wrapBox(attribs);
glyph->getBoxSize(rect.w, rect.h);
if (rect.w > 0 && rect.h > 0) {
rectangles.push_back(rect);
@ -143,11 +152,11 @@ void TightAtlasPacker::setMinimumScale(double minScale) {
this->minScale = minScale;
}
void TightAtlasPacker::setUnitRange(double unitRange) {
void TightAtlasPacker::setUnitRange(msdfgen::Range unitRange) {
this->unitRange = unitRange;
}
void TightAtlasPacker::setPixelRange(double pxRange) {
void TightAtlasPacker::setPixelRange(msdfgen::Range pxRange) {
this->pxRange = pxRange;
}
@ -163,6 +172,44 @@ void TightAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) {
pxAlignOriginX = alignX, pxAlignOriginY = alignY;
}
static Padding makeUniformPadding(double width) {
Padding p;
p.l = width, p.b = width, p.r = width, p.t = width;
return p;
}
void TightAtlasPacker::setInnerUnitPadding(const Padding &padding) {
innerUnitPadding = padding;
}
void TightAtlasPacker::setInnerUnitPadding(double uniformPadding) {
innerUnitPadding = makeUniformPadding(uniformPadding);
}
void TightAtlasPacker::setOuterUnitPadding(const Padding &padding) {
outerUnitPadding = padding;
}
void TightAtlasPacker::setOuterUnitPadding(double uniformPadding) {
outerUnitPadding = makeUniformPadding(uniformPadding);
}
void TightAtlasPacker::setInnerPixelPadding(const Padding &padding) {
innerPxPadding = padding;
}
void TightAtlasPacker::setInnerPixelPadding(double uniformPadding) {
innerPxPadding = makeUniformPadding(uniformPadding);
}
void TightAtlasPacker::setOuterPixelPadding(const Padding &padding) {
outerPxPadding = padding;
}
void TightAtlasPacker::setOuterPixelPadding(double uniformPadding) {
outerPxPadding = makeUniformPadding(uniformPadding);
}
void TightAtlasPacker::getDimensions(int &width, int &height) const {
width = this->width, height = this->height;
}
@ -171,7 +218,7 @@ double TightAtlasPacker::getScale() const {
return scale;
}
double TightAtlasPacker::getPixelRange() const {
msdfgen::Range TightAtlasPacker::getPixelRange() const {
return pxRange+scale*unitRange;
}

View File

@ -31,21 +31,33 @@ public:
/// Sets the minimum glyph scale
void setMinimumScale(double minScale);
/// Sets the unit component of the total distance range
void setUnitRange(double unitRange);
void setUnitRange(msdfgen::Range unitRange);
/// Sets the pixel component of the total distance range
void setPixelRange(double pxRange);
void setPixelRange(msdfgen::Range pxRange);
/// Sets the miter limit for bounds computation
void setMiterLimit(double miterLimit);
/// Sets whether each glyph's origin point should stay aligned with the pixel grid
void setOriginPixelAlignment(bool align);
void setOriginPixelAlignment(bool alignX, bool alignY);
/// Sets the unit component of width of additional padding that is part of each glyph quad
void setInnerUnitPadding(const Padding &padding);
void setInnerUnitPadding(double uniformPadding);
/// Sets the unit component of width of additional padding around each glyph quad
void setOuterUnitPadding(const Padding &padding);
void setOuterUnitPadding(double uniformPadding);
/// Sets the pixel component of width of additional padding that is part of each glyph quad
void setInnerPixelPadding(const Padding &padding);
void setInnerPixelPadding(double uniformPadding);
/// Sets the pixel component of width of additional padding around each glyph quad
void setOuterPixelPadding(const Padding &padding);
void setOuterPixelPadding(double uniformPadding);
/// Outputs the atlas's final dimensions
void getDimensions(int &width, int &height) const;
/// Returns the final glyph scale
double getScale() const;
/// Returns the final combined pixel range (including converted unit range)
double getPixelRange() const;
msdfgen::Range getPixelRange() const;
private:
int width, height;
@ -53,10 +65,12 @@ private:
DimensionsConstraint dimensionsConstraint;
double scale;
double minScale;
double unitRange;
double pxRange;
msdfgen::Range unitRange;
msdfgen::Range pxRange;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
Padding innerUnitPadding, outerUnitPadding;
Padding innerPxPadding, outerPxPadding;
double scaleMaximizationTolerance;
int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const;

View File

@ -70,7 +70,7 @@ bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::B
fontVariant.imageType = convertImageType(properties.imageType);
fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize);
if (properties.imageType != ImageType::HARD_MASK)
fontVariant.metrics.distanceRange = REAL(properties.pxRange);
fontVariant.metrics.distanceRange = REAL(properties.pxRange.upper-properties.pxRange.lower);
fontVariant.metrics.emSize = REAL(fontMetrics.emSize);
fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY);
fontVariant.metrics.descender = REAL(fontMetrics.descenderY);

View File

@ -12,7 +12,7 @@ namespace msdf_atlas {
struct ArteryFontExportProperties {
double fontSize;
double pxRange;
msdfgen::Range pxRange;
ImageType imageType;
ImageFormat imageFormat;
YDirection yDirection;

View File

@ -67,8 +67,10 @@ bool exportJSON(const FontGeometry *fonts, int fontCount, ImageType imageType, c
// Atlas properties
fputs("\"atlas\":{", f); {
fprintf(f, "\"type\":\"%s\",", imageTypeString(imageType));
if (imageType == ImageType::SDF || imageType == ImageType::PSDF || imageType == ImageType::MSDF || imageType == ImageType::MTSDF)
fprintf(f, "\"distanceRange\":%.17g,", metrics.distanceRange);
if (imageType == ImageType::SDF || imageType == ImageType::PSDF || imageType == ImageType::MSDF || imageType == ImageType::MTSDF) {
fprintf(f, "\"distanceRange\":%.17g,", metrics.distanceRange.upper-metrics.distanceRange.lower);
fprintf(f, "\"zeroDistanceValue\":%.17g,", -metrics.distanceRange.lower/(metrics.distanceRange.upper-metrics.distanceRange.lower));
}
fprintf(f, "\"size\":%.17g,", metrics.size);
fprintf(f, "\"width\":%d,", metrics.width);
fprintf(f, "\"height\":%d,", metrics.height);

View File

@ -15,7 +15,7 @@ struct JsonAtlasMetrics {
const double *originX, *originY;
int spacing;
};
double distanceRange;
msdfgen::Range distanceRange;
double size;
int width, height;
YDirection yDirection;

View File

@ -128,14 +128,28 @@ GLYPH CONFIGURATION
Specifies the size of the glyphs in the atlas bitmap in pixels per em.
-minsize <em size>
Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used.
-emrange <em range>
Specifies the SDF distance range in em's.
-pxrange <pixel range>
Specifies the SDF distance range in output pixels. The default value is 2.
-emrange <em range width>
Specifies the width of the representable SDF distance range in ems.
-pxrange <pixel range width>
Specifies the width of the SDF distance range in output pixels. The default value is 2.
-aemrange <outermost distance> <innermost distance>
Specifies the outermost (negative) and innermost representable distance in ems.
-apxrange <outermost distance> <innermost distance>
Specifies the outermost (negative) and innermost representable distance in pixels.
-pxalign <off / on / horizontal / vertical>
Specifies whether each glyph's origin point should be aligned with the pixel grid.
-nokerning
Disables inclusion of kerning pair table in output files.
To specify additional inner / outer padding for each glyph in ems / pixels:
-empadding <width>
-pxpadding <width>
-outerempadding <width>
-outerpxpadding <width>
Or asymmetrical padding with a separate value for each side:
-aempadding <left> <bottom> <right> <top>
-apxpadding <left> <bottom> <right> <top>
-aouterempadding <left> <bottom> <right> <top>
-aouterpxpadding <left> <bottom> <right> <top>
DISTANCE FIELD GENERATOR SETTINGS
-angle <angle>
@ -239,6 +253,12 @@ static bool strStartsWith(const char *str, const char *prefix) {
return true;
}
static Padding makeUniformPadding(double width) {
Padding p;
p.l = width, p.b = width, p.r = width, p.t = width;
return p;
}
#ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
static msdfgen::FontHandle *loadVarFont(msdfgen::FreetypeHandle *library, const char *filename) {
std::string buffer;
@ -264,6 +284,13 @@ static msdfgen::FontHandle *loadVarFont(msdfgen::FreetypeHandle *library, const
}
#endif
enum class Units {
/// Value is specified in ems
EMS,
/// Value is specified in pixels
PIXELS
};
struct FontInput {
const char *fontFilename;
bool variableFont;
@ -279,7 +306,7 @@ struct Configuration {
YDirection yDirection;
int width, height;
double emSize;
double pxRange;
msdfgen::Range pxRange;
double angleThreshold;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
@ -370,13 +397,12 @@ int main(int argc, const char *const *argv) {
config.generatorAttributes.config.overlapSupport = !config.preprocessGeometry;
config.generatorAttributes.scanlinePass = !config.preprocessGeometry;
double minEmSize = 0;
enum {
/// Range specified in ems
RANGE_EM,
/// Range specified in output pixels
RANGE_PIXEL,
} rangeMode = RANGE_PIXEL;
double rangeValue = 0;
Units rangeUnits = Units::PIXELS;
msdfgen::Range rangeValue = 0;
Padding innerPadding = { };
Padding outerPadding = { };
Units innerPaddingUnits = Units::EMS;
Units outerPaddingUnits = Units::EMS;
PackingStyle packingStyle = PackingStyle::TIGHT;
DimensionsConstraint atlasSizeConstraint = DimensionsConstraint::NONE;
DimensionsConstraint cellSizeConstraint = DimensionsConstraint::NONE;
@ -577,20 +603,40 @@ int main(int argc, const char *const *argv) {
}
ARG_CASE("-emrange", 1) {
double r;
if (!(parseDouble(r, argv[argPos++]) && r >= 0))
ABORT("Invalid range argument. Use -emrange <em range> with a positive real number.");
rangeMode = RANGE_EM;
if (!(parseDouble(r, argv[argPos++]) && r != 0))
ABORT("Invalid range argument. Use -emrange <em range> with a non-zero real number.");
rangeUnits = Units::EMS;
rangeValue = r;
continue;
}
ARG_CASE("-pxrange", 1) {
double r;
if (!(parseDouble(r, argv[argPos++]) && r >= 0))
ABORT("Invalid range argument. Use -pxrange <pixel range> with a positive real number.");
rangeMode = RANGE_PIXEL;
if (!(parseDouble(r, argv[argPos++]) && r != 0))
ABORT("Invalid range argument. Use -pxrange <pixel range> with a non-zero real number.");
rangeUnits = Units::PIXELS;
rangeValue = r;
continue;
}
ARG_CASE("-aemrange", 2) {
double r0, r1;
if (!(parseDouble(r0, argv[argPos++]) && parseDouble(r1, argv[argPos++])))
ABORT("Invalid range arguments. Use -aemrange <minimum> <maximum> with two real numbers.");
if (r0 == r1)
ABORT("Range must be non-empty.");
rangeUnits = Units::EMS;
rangeValue = msdfgen::Range(r0, r1);
continue;
}
ARG_CASE("-apxrange", 2) {
double r0, r1;
if (!(parseDouble(r0, argv[argPos++]) && parseDouble(r1, argv[argPos++])))
ABORT("Invalid range arguments. Use -apxrange <minimum> <maximum> with two real numbers.");
if (r0 == r1)
ABORT("Range must be non-empty.");
rangeUnits = Units::PIXELS;
rangeValue = msdfgen::Range(r0, r1);
continue;
}
ARG_CASE("-pxalign", 1) {
if (ARG_IS("off") || ARG_PREFIX("disable") || ARG_IS("0") || ARG_IS("false") || ARG_PREFIX("n"))
config.pxAlignOriginX = false, config.pxAlignOriginY = false;
@ -605,6 +651,70 @@ int main(int argc, const char *const *argv) {
++argPos;
continue;
}
ARG_CASE("-empadding", 1) {
double p;
if (!parseDouble(p, argv[argPos++]))
ABORT("Invalid padding argument. Use -empadding <padding> with a real number.");
innerPaddingUnits = Units::EMS;
innerPadding = makeUniformPadding(p);
continue;
}
ARG_CASE("-pxpadding", 1) {
double p;
if (!parseDouble(p, argv[argPos++]))
ABORT("Invalid padding argument. Use -pxpadding <padding> with a real number.");
innerPaddingUnits = Units::PIXELS;
innerPadding = makeUniformPadding(p);
continue;
}
ARG_CASE("-outerempadding", 1) {
double p;
if (!parseDouble(p, argv[argPos++]))
ABORT("Invalid padding argument. Use -outerempadding <padding> with a real number.");
outerPaddingUnits = Units::EMS;
outerPadding = makeUniformPadding(p);
continue;
}
ARG_CASE("-outerpxpadding", 1) {
double p;
if (!parseDouble(p, argv[argPos++]))
ABORT("Invalid padding argument. Use -outerpxpadding <padding> with a real number.");
outerPaddingUnits = Units::PIXELS;
outerPadding = makeUniformPadding(p);
continue;
}
ARG_CASE("-aempadding", 4) {
double l, b, r, t;
if (!(parseDouble(l, argv[argPos++]) && parseDouble(b, argv[argPos++]) && parseDouble(r, argv[argPos++]) && parseDouble(t, argv[argPos++])))
ABORT("Invalid padding arguments. Use -aempadding <left> <bottom> <right> <top> with 4 real numbers.");
innerPaddingUnits = Units::EMS;
innerPadding.l = l, innerPadding.b = b, innerPadding.r = r, innerPadding.t = t;
continue;
}
ARG_CASE("-apxpadding", 4) {
double l, b, r, t;
if (!(parseDouble(l, argv[argPos++]) && parseDouble(b, argv[argPos++]) && parseDouble(r, argv[argPos++]) && parseDouble(t, argv[argPos++])))
ABORT("Invalid padding arguments. Use -apxpadding <left> <bottom> <right> <top> with 4 real numbers.");
innerPaddingUnits = Units::PIXELS;
innerPadding.l = l, innerPadding.b = b, innerPadding.r = r, innerPadding.t = t;
continue;
}
ARG_CASE("-aouterempadding", 4) {
double l, b, r, t;
if (!(parseDouble(l, argv[argPos++]) && parseDouble(b, argv[argPos++]) && parseDouble(r, argv[argPos++]) && parseDouble(t, argv[argPos++])))
ABORT("Invalid padding arguments. Use -aouterempadding <left> <bottom> <right> <top> with 4 real numbers.");
outerPaddingUnits = Units::EMS;
outerPadding.l = l, outerPadding.b = b, outerPadding.r = r, outerPadding.t = t;
continue;
}
ARG_CASE("-aouterpxpadding", 4) {
double l, b, r, t;
if (!(parseDouble(l, argv[argPos++]) && parseDouble(b, argv[argPos++]) && parseDouble(r, argv[argPos++]) && parseDouble(t, argv[argPos++])))
ABORT("Invalid padding arguments. Use -aouterpxpadding <left> <bottom> <right> <top> with 4 real numbers.");
outerPaddingUnits = Units::PIXELS;
outerPadding.l = l, outerPadding.b = b, outerPadding.r = r, outerPadding.t = t;
continue;
}
ARG_CASE("-angle", 1) {
double at;
if (!parseAngle(at, argv[argPos++]))
@ -841,10 +951,10 @@ int main(int argc, const char *const *argv) {
minEmSize = DEFAULT_SIZE;
}
if (config.imageType == ImageType::HARD_MASK || config.imageType == ImageType::SOFT_MASK) {
rangeMode = RANGE_PIXEL;
rangeUnits = Units::PIXELS;
rangeValue = 1;
} else if (rangeValue <= 0) {
rangeMode = RANGE_PIXEL;
} else if (rangeValue.lower == rangeValue.upper) {
rangeUnits = Units::PIXELS;
rangeValue = DEFAULT_PIXEL_RANGE;
}
if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename))
@ -1052,15 +1162,33 @@ int main(int argc, const char *const *argv) {
// Determine final atlas dimensions, scale and range, pack glyphs
{
double unitRange = 0, pxRange = 0;
switch (rangeMode) {
case RANGE_EM:
unitRange = rangeValue;
msdfgen::Range emRange = 0, pxRange = 0;
switch (rangeUnits) {
case Units::EMS:
emRange = rangeValue;
break;
case RANGE_PIXEL:
case Units::PIXELS:
pxRange = rangeValue;
break;
}
Padding innerEmPadding = { }, outerEmPadding = { };
Padding innerPxPadding = { }, outerPxPadding = { };
switch (innerPaddingUnits) {
case Units::EMS:
innerEmPadding = innerPadding;
break;
case Units::PIXELS:
innerPxPadding = innerPadding;
break;
}
switch (outerPaddingUnits) {
case Units::EMS:
outerEmPadding = outerPadding;
break;
case Units::PIXELS:
outerPxPadding = outerPadding;
break;
}
bool fixedDimensions = fixedWidth >= 0 && fixedHeight >= 0;
bool fixedScale = config.emSize > 0;
switch (packingStyle) {
@ -1077,9 +1205,13 @@ int main(int argc, const char *const *argv) {
else
atlasPacker.setMinimumScale(minEmSize);
atlasPacker.setPixelRange(pxRange);
atlasPacker.setUnitRange(unitRange);
atlasPacker.setUnitRange(emRange);
atlasPacker.setMiterLimit(config.miterLimit);
atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY);
atlasPacker.setInnerUnitPadding(innerEmPadding);
atlasPacker.setOuterUnitPadding(outerEmPadding);
atlasPacker.setInnerPixelPadding(innerPxPadding);
atlasPacker.setOuterPixelPadding(outerPxPadding);
if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) {
if (remaining < 0) {
ABORT("Failed to pack glyphs into atlas.");
@ -1119,9 +1251,13 @@ int main(int argc, const char *const *argv) {
else
atlasPacker.setMinimumScale(minEmSize);
atlasPacker.setPixelRange(pxRange);
atlasPacker.setUnitRange(unitRange);
atlasPacker.setUnitRange(emRange);
atlasPacker.setMiterLimit(config.miterLimit);
atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY);
atlasPacker.setInnerUnitPadding(innerEmPadding);
atlasPacker.setOuterUnitPadding(outerEmPadding);
atlasPacker.setInnerPixelPadding(innerPxPadding);
atlasPacker.setOuterPixelPadding(outerPxPadding);
if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) {
if (remaining < 0) {
ABORT("Failed to pack glyphs into atlas.");

View File

@ -7,7 +7,7 @@
namespace msdf_atlas {
static const char *const shadronFillGlyphMask = R"(
template <ATLAS, RANGE, COLOR>
template <ATLAS, RANGE, ZERO_DIST, COLOR>
glsl vec4 fillGlyph(vec2 texCoord) {
float fill = texture((ATLAS), texCoord).r;
return vec4(vec3(COLOR), fill);
@ -15,10 +15,10 @@ glsl vec4 fillGlyph(vec2 texCoord) {
)";
static const char *const shadronFillGlyphSdf = R"(
template <ATLAS, RANGE, COLOR>
template <ATLAS, RANGE, ZERO_DIST, COLOR>
glsl vec4 fillGlyph(vec2 texCoord) {
vec3 s = texture((ATLAS), texCoord).rgb;
float sd = dot(vec2(RANGE), 0.5/fwidth(texCoord))*(median(s.r, s.g, s.b)-0.5);
float sd = dot(vec2(RANGE), 0.5/fwidth(texCoord))*(median(s.r, s.g, s.b)-ZERO_DIST);
float fill = clamp(sd+0.5, 0.0, 1.0);
return vec4(vec3(COLOR), fill);
}
@ -43,11 +43,11 @@ glsl vec4 projectVertex(out vec2 texCoord, in GlyphVertex vertex) {
return vec4(coord, 0.0, 1.0);
}
%s
#define PREVIEW_IMAGE(NAME, ATLAS, RANGE, COLOR, VERTEX_LIST, TEXT_SIZE, DIMENSIONS) model image NAME : \
#define PREVIEW_IMAGE(NAME, ATLAS, RANGE, ZERO_DIST, COLOR, VERTEX_LIST, TEXT_SIZE, DIMENSIONS) model image NAME : \
vertex_data(GlyphVertex), \
fragment_data(vec2), \
vertex(projectVertex<TEXT_SIZE>, triangles, VERTEX_LIST), \
fragment(fillGlyph<ATLAS, RANGE, COLOR>), \
fragment(fillGlyph<ATLAS, RANGE, ZERO_DIST, COLOR>), \
depth(false), \
blend(transparency), \
background(vec4(vec3(COLOR), 0.0)), \
@ -94,7 +94,7 @@ static std::string escapeString(const std::string &str) {
return output;
}
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) {
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, msdfgen::Range pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) {
if (fontCount <= 0)
return false;
double texelWidth = 1./atlasWidth;
@ -109,7 +109,9 @@ bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType
else
fprintf(file, "image Atlas = file()");
fprintf(file, " : %sfilter(%s), map(repeat);\n", fullRange ? "full_range(true), " : "", atlasType == ImageType::HARD_MASK ? "nearest" : "linear");
fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n\n", pxRange*texelWidth, pxRange*texelHeight);
double pxRangeWidth = pxRange.upper-pxRange.lower;
fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n", pxRangeWidth*texelWidth, pxRangeWidth*texelHeight);
fprintf(file, "const float zeroDistanceValue = %.9g;\n\n", -pxRange.lower/(pxRange.upper-pxRange.lower));
{
msdfgen::FontMetrics fontMetrics = fonts->getMetrics();
for (int i = 1; i < fontCount; ++i) {
@ -163,7 +165,7 @@ bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType
fputs("};\n", file);
fprintf(file, "const vec2 textSize = vec2(%.9g, %.9g);\n\n", textWidth, -y);
}
fputs("PREVIEW_IMAGE(Preview, Atlas, txRange, vec3(1.0), textQuadVertices, textSize, ivec2(1200, 400));\n", file);
fputs("PREVIEW_IMAGE(Preview, Atlas, txRange, zeroDistanceValue, vec3(1.0), textQuadVertices, textSize, ivec2(1200, 400));\n", file);
fputs("export png(Preview, \"preview.png\");\n", file);
fclose(file);
return anyGlyphs;

View File

@ -9,6 +9,6 @@
namespace msdf_atlas {
/// Generates a Shadron script that displays a string using the generated atlas
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename);
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, msdfgen::Range pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename);
}