This commit is contained in:
Viktor Chlumský 2024-05-01 23:25:00 +02:00 committed by GitHub
parent b67fb967c3
commit 2588a2aae3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 468 additions and 124 deletions

View File

@ -120,7 +120,11 @@ Any non-empty subset of the following may be specified:
- `-minsize <em size>` &ndash; sets the minimum size. The largest possible size that fits the same atlas dimensions will be used - `-minsize <em size>` &ndash; sets the minimum size. The largest possible size that fits the same atlas dimensions will be used
- `-emrange <em range>` &ndash; sets the distance field range in em's - `-emrange <em range>` &ndash; sets the distance field range in em's
- `-pxrange <pixel range>` (default = 2) &ndash; sets the distance field range in output pixels - `-pxrange <pixel range>` (default = 2) &ndash; sets the distance field range in output pixels
- `-aemrange` / `-apxrange <outermost distance> <innermost distance>` &ndash; sets the distance field range asymmetrically by specifying the minimum and maximum representable signed distances (outside distances are negative!)
- `-pxalign <off / on / horizontal / vertical>` (default = vertical) &ndash; enables or disables alignment of glyph's origin point with the pixel grid - `-pxalign <off / on / horizontal / vertical>` (default = vertical) &ndash; enables or disables alignment of glyph's origin point with the pixel grid
- `-empadding` / `-pxpadding <width>` &ndash; sets additional padding within each glyph's box (in em's / pixels)
- `-outerempadding` / `-outerpxpadding <width>` &ndash; sets additional padding around each glyph's box
- `-aempadding` / `-apxpadding` / `-aouterempadding` / `-aouterpxpadding <left> <bottom> <right> <top>` &ndash; sets additional padding (see above) asymmetrically with a separate width value for each side
### Distance field generator settings ### Distance field generator settings

@ -1 +1 @@
Subproject commit 34134bde3cea35a93c2ae5703fa8d3d463793400 Subproject commit 888674220216d1d326c6f29cf89165b545279c1f

View File

@ -51,22 +51,21 @@ void GlyphGeometry::edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned l
fn(shape, angleThreshold, seed); fn(shape, angleThreshold, seed);
} }
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin) { void GlyphGeometry::wrapBox(const GlyphAttributes &glyphAttributes) {
wrapBox(scale, range, miterLimit, pxAlignOrigin, pxAlignOrigin); double scale = glyphAttributes.scale*geometryScale;
} msdfgen::Range range = glyphAttributes.range/geometryScale;
Padding fullPadding = (glyphAttributes.innerPadding+glyphAttributes.outerPadding)/geometryScale;
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY) {
scale *= geometryScale;
range /= geometryScale;
box.range = range; box.range = range;
box.scale = scale; box.scale = scale;
if (bounds.l < bounds.r && bounds.b < bounds.t) { if (bounds.l < bounds.r && bounds.b < bounds.t) {
double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
l -= .5*range, b -= .5*range; l += range.lower, b += range.lower;
r += .5*range, t += .5*range; r -= range.lower, t -= range.lower;
if (miterLimit > 0) if (glyphAttributes.miterLimit > 0)
shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1);
if (pxAlignOriginX) { l -= fullPadding.l, b -= fullPadding.b;
r += fullPadding.r, t += fullPadding.t;
if (glyphAttributes.pxAlignOriginX) {
int sl = (int) floor(scale*l-.5); int sl = (int) floor(scale*l-.5);
int sr = (int) ceil(scale*r+.5); int sr = (int) ceil(scale*r+.5);
box.rect.w = sr-sl; 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.rect.w = (int) ceil(w)+1;
box.translate.x = -l+.5*(box.rect.w-w)/scale; box.translate.x = -l+.5*(box.rect.w-w)/scale;
} }
if (pxAlignOriginY) { if (glyphAttributes.pxAlignOriginY) {
int sb = (int) floor(scale*b-.5); int sb = (int) floor(scale*b-.5);
int st = (int) ceil(scale*t+.5); int st = (int) ceil(scale*t+.5);
box.rect.h = st-sb; 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.rect.h = (int) ceil(h)+1;
box.translate.y = -b+.5*(box.rect.h-h)/scale; box.translate.y = -b+.5*(box.rect.h-h)/scale;
} }
box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding;
} else { } else {
box.rect.w = 0, box.rect.h = 0; box.rect.w = 0, box.rect.h = 0;
box.translate = msdfgen::Vector2(); 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) { void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin) {
frameBox(scale, range, miterLimit, width, height, fixedX, fixedY, pxAlignOrigin, 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) { void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY) {
scale *= geometryScale; GlyphAttributes attribs = { };
range /= geometryScale; 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 = (glyphAttributes.innerPadding+glyphAttributes.outerPadding)/geometryScale;
box.range = range; box.range = range;
box.scale = scale; box.scale = scale;
box.rect.w = width; box.rect.w = width;
@ -108,13 +125,15 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int
box.translate.y = *fixedY/geometryScale; box.translate.y = *fixedY/geometryScale;
} else { } else {
double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
l -= .5*range, b -= .5*range; l += range.lower, b += range.lower;
r += .5*range, t += .5*range; r -= range.lower, t -= range.lower;
if (miterLimit > 0) if (glyphAttributes.miterLimit > 0)
shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); 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) if (fixedX)
box.translate.x = *fixedX/geometryScale; box.translate.x = *fixedX/geometryScale;
else if (pxAlignOriginX) { else if (glyphAttributes.pxAlignOriginX) {
int sl = (int) floor(scale*l-.5); int sl = (int) floor(scale*l-.5);
int sr = (int) ceil(scale*r+.5); int sr = (int) ceil(scale*r+.5);
box.translate.x = (-sl+(box.rect.w-(sr-sl))/2)/scale; 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) if (fixedY)
box.translate.y = *fixedY/geometryScale; box.translate.y = *fixedY/geometryScale;
else if (pxAlignOriginY) { else if (glyphAttributes.pxAlignOriginY) {
int sb = (int) floor(scale*b-.5); int sb = (int) floor(scale*b-.5);
int st = (int) ceil(scale*t+.5); int st = (int) ceil(scale*t+.5);
box.translate.y = (-sb+(box.rect.h-(st-sb))/2)/scale; 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.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) { 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; w = box.rect.w, h = box.rect.h;
} }
double GlyphGeometry::getBoxRange() const { msdfgen::Range GlyphGeometry::getBoxRange() const {
return box.range; return box.range;
} }
@ -213,20 +253,20 @@ msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const {
void GlyphGeometry::getQuadPlaneBounds(double &l, double &b, double &r, double &t) const { void GlyphGeometry::getQuadPlaneBounds(double &l, double &b, double &r, double &t) const {
if (box.rect.w > 0 && box.rect.h > 0) { if (box.rect.w > 0 && box.rect.h > 0) {
double invBoxScale = 1/box.scale; double invBoxScale = 1/box.scale;
l = geometryScale*(-box.translate.x+.5*invBoxScale); l = geometryScale*(-box.translate.x+(box.outerPadding.l+.5)*invBoxScale);
b = geometryScale*(-box.translate.y+.5*invBoxScale); b = geometryScale*(-box.translate.y+(box.outerPadding.b+.5)*invBoxScale);
r = geometryScale*(-box.translate.x+(box.rect.w-.5)*invBoxScale); r = geometryScale*(-box.translate.x+(-box.outerPadding.r+box.rect.w-.5)*invBoxScale);
t = geometryScale*(-box.translate.y+(box.rect.h-.5)*invBoxScale); t = geometryScale*(-box.translate.y+(-box.outerPadding.t+box.rect.h-.5)*invBoxScale);
} else } else
l = 0, b = 0, r = 0, t = 0; l = 0, b = 0, r = 0, t = 0;
} }
void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const { void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const {
if (box.rect.w > 0 && box.rect.h > 0) { if (box.rect.w > 0 && box.rect.h > 0) {
l = box.rect.x+.5; l = box.rect.x+box.outerPadding.l+.5;
b = box.rect.y+.5; b = box.rect.y+box.outerPadding.b+.5;
r = box.rect.x+box.rect.w-.5; r = box.rect.x-box.outerPadding.r+box.rect.w-.5;
t = box.rect.y+box.rect.h-.5; t = box.rect.y-box.outerPadding.t+box.rect.h-.5;
} else } else
l = 0, b = 0, r = 0, t = 0; l = 0, b = 0, r = 0, t = 0;
} }
@ -244,4 +284,8 @@ GlyphGeometry::operator GlyphBox() const {
return box; return box;
} }
msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b) {
return msdfgen::Range(a.lower+b.lower, a.upper+b.upper);
}
} }

View File

@ -5,6 +5,7 @@
#include <msdfgen-ext.h> #include <msdfgen-ext.h>
#include "types.h" #include "types.h"
#include "Rectangle.h" #include "Rectangle.h"
#include "Padding.h"
#include "GlyphBox.h" #include "GlyphBox.h"
namespace msdf_atlas { namespace msdf_atlas {
@ -13,6 +14,14 @@ namespace msdf_atlas {
class GlyphGeometry { class GlyphGeometry {
public: public:
struct GlyphAttributes {
double scale;
msdfgen::Range range;
Padding innerPadding, outerPadding;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
};
GlyphGeometry(); GlyphGeometry();
/// Loads glyph geometry from font /// Loads glyph geometry from font
bool load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true); bool load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
@ -20,9 +29,11 @@ public:
/// Applies edge coloring to glyph shape /// Applies edge coloring to glyph shape
void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed); 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 /// 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 pxAlignOrigin = false);
void wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY); void wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY);
/// Computes the glyph's transformation and alignment (unless specified) for given dimensions /// 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 pxAlignOrigin = false);
void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY); 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 /// Sets the glyph's box's position in the atlas
@ -52,7 +63,7 @@ public:
/// Outputs the dimensions of the glyph's box in the atlas /// Outputs the dimensions of the glyph's box in the atlas
void getBoxSize(int &w, int &h) const; void getBoxSize(int &w, int &h) const;
/// Returns the range needed to generate the glyph's SDF /// 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 /// Returns the projection needed to generate the glyph's bitmap
msdfgen::Projection getBoxProjection() const; msdfgen::Projection getBoxProjection() const;
/// Returns the scale needed to generate the glyph's bitmap /// Returns the scale needed to generate the glyph's bitmap
@ -77,11 +88,14 @@ private:
double advance; double advance;
struct { struct {
Rectangle rect; Rectangle rect;
double range; msdfgen::Range range;
double scale; double scale;
msdfgen::Vector2 translate; msdfgen::Vector2 translate;
Padding outerPadding;
} box; } box;
}; };
msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b);
} }

View File

@ -107,20 +107,20 @@ GridAtlasPacker::GridAtlasPacker() :
cutoff(false) 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; static const double LARGE_VALUE = 1e240;
msdfgen::Shape::Bounds maxBounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE }; msdfgen::Shape::Bounds maxBounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE };
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) { if (!glyph->isWhitespace()) {
double geometryScale = glyph->getGeometryScale(); double geometryScale = glyph->getGeometryScale();
double shapeRange = range/geometryScale; double shapeOuterRange = outerRange/geometryScale;
geometryScale *= scale; geometryScale *= scale;
const msdfgen::Shape::Bounds &shapeBounds = glyph->getShapeBounds(); const msdfgen::Shape::Bounds &shapeBounds = glyph->getShapeBounds();
double l = shapeBounds.l, b = shapeBounds.b, r = shapeBounds.r, t = shapeBounds.t; double l = shapeBounds.l, b = shapeBounds.b, r = shapeBounds.r, t = shapeBounds.t;
l -= .5*shapeRange, b -= .5*shapeRange; l -= shapeOuterRange, b -= shapeOuterRange;
r += .5*shapeRange, t += .5*shapeRange; r += shapeOuterRange, t += shapeOuterRange;
if (miterLimit > 0) 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; l *= geometryScale, b *= geometryScale;
r *= geometryScale, t *= geometryScale; r *= geometryScale, t *= geometryScale;
maxBounds.l = std::min(maxBounds.l, l); maxBounds.l = std::min(maxBounds.l, l);
@ -133,6 +133,10 @@ msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &m
} }
if (maxBounds.l >= maxBounds.r || maxBounds.b >= maxBounds.t) if (maxBounds.l >= maxBounds.r || maxBounds.b >= maxBounds.t)
maxBounds = msdfgen::Shape::Bounds(); maxBounds = msdfgen::Shape::Bounds();
Padding fullPadding = scale*(innerUnitPadding+outerUnitPadding)+innerPxPadding+outerPxPadding;
pad(maxBounds, fullPadding);
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 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) if (hFixed)
maxWidth = maxBounds.r-maxBounds.l; maxWidth = maxBounds.r-maxBounds.l;
@ -154,7 +158,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, --cellHeight; // Implicit half-pixel padding from each side to make sure that no representable values are beyond outermost pixel centers
cellWidth -= spacing, cellHeight -= spacing; cellWidth -= spacing, cellHeight -= spacing;
bool lastResult = false; 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; double minScale = 1, maxScale = 1;
if (TRY_FIT(1)) { if (TRY_FIT(1)) {
while (maxScale < 1e+32 && ((maxScale = 2*minScale), TRY_FIT(maxScale))) while (maxScale < 1e+32 && ((maxScale = 2*minScale), TRY_FIT(maxScale)))
@ -220,7 +224,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
return -1; 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; return -1;
msdfgen::Shape::Bounds maxBounds = { }; msdfgen::Shape::Bounds maxBounds = { };
@ -229,14 +233,14 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
if (scale <= 0) { if (scale <= 0) {
// If both pxRange and miterLimit is non-zero, miter bounds have to be computed for all potential scales // 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) { if (cellWidth > 0 || cellHeight > 0) {
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight); scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
if (scale < minScale) { if (scale < minScale) {
scale = minScale; scale = minScale;
cutoff = true; 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 +300,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
} }
if (scale <= 0) { 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; cellWidth = (int) ceil(maxWidth)+spacing+1;
cellHeight = (int) ceil(maxHeight)+spacing+1; cellHeight = (int) ceil(maxHeight)+spacing+1;
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight); scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
if (scale < minScale) 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) { if (initial.rows < 0 && initial.cellHeight < 0) {
@ -316,7 +320,12 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
} else { } 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
pad(maxBounds, -pxPadding);
maxWidth -= pxPadding.l+pxPadding.r;
maxHeight -= pxPadding.b+pxPadding.t;
int hSlack = 0, vSlack = 0; int hSlack = 0, vSlack = 0;
if (pxAlignOriginX && !hFixed) { if (pxAlignOriginX && !hFixed) {
maxWidth -= 1; // Added by getMaxBounds maxWidth -= 1; // Added by getMaxBounds
@ -327,11 +336,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
vSlack = 1; 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; double hScale = 0, vScale = 0;
if (cellWidth > 0) if (cellWidth > 0)
hScale = (cellWidth-hSlack-spacing-1-pxRange)/maxWidth; hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
if (cellHeight > 0) if (cellHeight > 0)
vScale = (cellHeight-vSlack-spacing-1-pxRange)/maxHeight; vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
if (hScale || vScale) { if (hScale || vScale) {
if (hScale && vScale) if (hScale && vScale)
scale = std::min(hScale, vScale); scale = std::min(hScale, vScale);
@ -353,8 +364,8 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
int tHeight = (height+spacing)/rows; int tHeight = (height+spacing)/rows;
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint); lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
if (tWidth > 0 && tHeight > 0) { if (tWidth > 0 && tHeight > 0) {
hScale = (tWidth-hSlack-spacing-1-pxRange)/maxWidth; hScale = (tWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
vScale = (tHeight-vSlack-spacing-1-pxRange)/maxHeight; vScale = (tHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
double curScale = std::min(hScale, vScale); double curScale = std::min(hScale, vScale);
if (curScale > scale) { if (curScale > scale) {
scale = curScale; scale = curScale;
@ -384,16 +395,16 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
} }
if (scale <= 0) { if (scale <= 0) {
cellWidth = (int) ceil(minScale*maxWidth+pxRange)+hSlack+spacing+1; cellWidth = (int) ceil(minScale*maxWidth+extraPxWidth)+hSlack+spacing+1;
cellHeight = (int) ceil(minScale*maxHeight+pxRange)+vSlack+spacing+1; cellHeight = (int) ceil(minScale*maxHeight+extraPxHeight)+vSlack+spacing+1;
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
hScale = (cellWidth-hSlack-spacing-1-pxRange)/maxWidth; hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
vScale = (cellHeight-vSlack-spacing-1-pxRange)/maxHeight; vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
scale = std::min(hScale, vScale); scale = std::min(hScale, vScale);
} }
if (initial.rows < 0 && initial.cellHeight < 0) { 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); raiseToConstraint(optimalCellWidth, optimalCellHeight, cellDimensionsConstraint);
if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) { if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) {
cellWidth = optimalCellWidth; cellWidth = optimalCellWidth;
@ -403,11 +414,15 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
maxBounds.l *= scale, maxBounds.b *= scale; maxBounds.l *= scale, maxBounds.b *= scale;
maxBounds.r *= scale, maxBounds.t *= scale; maxBounds.r *= scale, maxBounds.t *= scale;
maxWidth *= scale, maxHeight *= scale; maxWidth *= scale, maxHeight *= scale;
// Redo addition of pxPadding once scale is known
pad(maxBounds, pxPadding);
maxWidth += pxPadding.l+pxPadding.r;
maxHeight += pxPadding.b+pxPadding.t;
} }
} else { } 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 optimalCellWidth = (int) ceil(maxWidth)+spacing+1;
int optimalCellHeight = (int) ceil(maxHeight)+spacing+1; int optimalCellHeight = (int) ceil(maxHeight)+spacing+1;
if (cellWidth < 0 || cellHeight < 0) { if (cellWidth < 0 || cellHeight < 0) {
@ -481,10 +496,18 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
if (rows*cellHeight > height) if (rows*cellHeight > height)
rows = height/cellHeight; 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; int col = 0, row = 0;
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) { 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); glyph->placeBox(col*cellWidth, height-(row+1)*cellHeight);
if (++col >= columns) { if (++col >= columns) {
if (++row >= rows) { if (++row >= rows) {
@ -554,11 +577,11 @@ void GridAtlasPacker::setMinimumScale(double minScale) {
this->minScale = minScale; this->minScale = minScale;
} }
void GridAtlasPacker::setUnitRange(double unitRange) { void GridAtlasPacker::setUnitRange(msdfgen::Range unitRange) {
this->unitRange = unitRange; this->unitRange = unitRange;
} }
void GridAtlasPacker::setPixelRange(double pxRange) { void GridAtlasPacker::setPixelRange(msdfgen::Range pxRange) {
this->pxRange = pxRange; this->pxRange = pxRange;
} }
@ -574,6 +597,22 @@ void GridAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) {
pxAlignOriginX = alignX, pxAlignOriginY = alignY; pxAlignOriginX = alignX, pxAlignOriginY = alignY;
} }
void GridAtlasPacker::setInnerUnitPadding(const Padding &padding) {
innerUnitPadding = padding;
}
void GridAtlasPacker::setOuterUnitPadding(const Padding &padding) {
outerUnitPadding = padding;
}
void GridAtlasPacker::setInnerPixelPadding(const Padding &padding) {
innerPxPadding = padding;
}
void GridAtlasPacker::setOuterPixelPadding(const Padding &padding) {
outerPxPadding = padding;
}
void GridAtlasPacker::getDimensions(int &width, int &height) const { void GridAtlasPacker::getDimensions(int &width, int &height) const {
width = this->width, height = this->height; width = this->width, height = this->height;
} }
@ -594,7 +633,7 @@ double GridAtlasPacker::getScale() const {
return scale; return scale;
} }
double GridAtlasPacker::getPixelRange() const { msdfgen::Range GridAtlasPacker::getPixelRange() const {
return pxRange+scale*unitRange; return pxRange+scale*unitRange;
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "Padding.h"
#include "GlyphGeometry.h" #include "GlyphGeometry.h"
namespace msdf_atlas { namespace msdf_atlas {
@ -40,14 +41,22 @@ public:
/// Sets the minimum glyph scale /// Sets the minimum glyph scale
void setMinimumScale(double minScale); void setMinimumScale(double minScale);
/// Sets the unit component of the total distance range /// 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 /// 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 /// Sets the miter limit for bounds computation
void setMiterLimit(double miterLimit); void setMiterLimit(double miterLimit);
/// Sets whether each glyph's origin point should stay aligned with the pixel grid /// Sets whether each glyph's origin point should stay aligned with the pixel grid
void setOriginPixelAlignment(bool align); void setOriginPixelAlignment(bool align);
void setOriginPixelAlignment(bool alignX, bool alignY); 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);
/// Sets the unit component of width of additional padding around each glyph quad
void setOuterUnitPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding that is part of each glyph quad
void setInnerPixelPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding around each glyph quad
void setOuterPixelPadding(const Padding &padding);
/// Outputs the atlas's final dimensions /// Outputs the atlas's final dimensions
void getDimensions(int &width, int &height) const; void getDimensions(int &width, int &height) const;
@ -60,7 +69,7 @@ public:
/// Returns the final glyph scale /// Returns the final glyph scale
double getScale() const; double getScale() const;
/// Returns the final combined pixel range (including converted unit range) /// 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 /// 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); void getFixedOrigin(double &x, double &y);
/// Returns true if the explicitly constrained cell dimensions aren't large enough to fit each glyph fully /// Returns true if the explicitly constrained cell dimensions aren't large enough to fit each glyph fully
@ -77,10 +86,12 @@ private:
double scale; double scale;
double minScale; double minScale;
double fixedX, fixedY; double fixedX, fixedY;
double unitRange; msdfgen::Range unitRange;
double pxRange; msdfgen::Range pxRange;
double miterLimit; double miterLimit;
bool pxAlignOriginX, pxAlignOriginY; bool pxAlignOriginX, pxAlignOriginY;
Padding innerUnitPadding, outerUnitPadding;
Padding innerPxPadding, outerPxPadding;
double scaleMaximizationTolerance; double scaleMaximizationTolerance;
double alignedColumnsBias; double alignedColumnsBias;
bool cutoff; bool cutoff;
@ -89,7 +100,7 @@ private:
static void raiseToConstraint(int &width, int &height, DimensionsConstraint constraint); static void raiseToConstraint(int &width, int &height, DimensionsConstraint constraint);
double dimensionsRating(int width, int height, bool aligned) const; 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; double scaleToFit(GlyphGeometry *glyphs, int count, int cellWidth, int cellHeight, msdfgen::Shape::Bounds &maxBounds, double &maxWidth, double &maxHeight) const;
}; };

View File

@ -0,0 +1,37 @@
#include "Padding.h"
namespace msdf_atlas {
void pad(msdfgen::Shape::Bounds &bounds, const Padding &padding) {
bounds.l -= padding.l;
bounds.b -= padding.b;
bounds.r += padding.r;
bounds.t += padding.t;
}
Padding operator-(const Padding &padding) {
return Padding(-padding.l, -padding.b, -padding.r, -padding.t);
}
Padding operator+(const Padding &a, const Padding &b) {
return Padding(a.l+b.l, a.b+b.b, a.r+b.r, a.t+b.t);
}
Padding operator-(const Padding &a, const Padding &b) {
return Padding(a.l-b.l, a.b-b.b, a.r-b.r, a.t-b.t);
}
Padding operator*(double factor, const Padding &padding) {
return Padding(factor*padding.l, factor*padding.b, factor*padding.r, factor*padding.t);
}
Padding operator*(const Padding &padding, double factor) {
return Padding(padding.l*factor, padding.b*factor, padding.r*factor, padding.t*factor);
}
Padding operator/(const Padding &padding, double divisor) {
return Padding(padding.l/divisor, padding.b/divisor, padding.r/divisor, padding.t/divisor);
}
}

24
msdf-atlas-gen/Padding.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <msdfgen.h>
namespace msdf_atlas {
struct Padding {
double l, b, r, t;
inline Padding(double uniformPadding = 0) : l(uniformPadding), b(uniformPadding), r(uniformPadding), t(uniformPadding) { }
inline Padding(double l, double b, double r, double t) : l(l), b(b), r(r), t(t) { }
};
void pad(msdfgen::Shape::Bounds &bounds, const Padding &padding);
Padding operator-(const Padding &padding);
Padding operator+(const Padding &a, const Padding &b);
Padding operator-(const Padding &a, const Padding &b);
Padding operator*(double factor, const Padding &padding);
Padding operator*(const Padding &padding, double factor);
Padding operator/(const Padding &padding, double divisor);
}

View File

@ -22,16 +22,23 @@ TightAtlasPacker::TightAtlasPacker() :
{ } { }
int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const { 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 // Wrap glyphs into boxes
std::vector<Rectangle> rectangles; std::vector<Rectangle> rectangles;
std::vector<GlyphGeometry *> rectangleGlyphs; std::vector<GlyphGeometry *> rectangleGlyphs;
rectangles.reserve(count); rectangles.reserve(count);
rectangleGlyphs.reserve(count); rectangleGlyphs.reserve(count);
GlyphGeometry::GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = unitRange+pxRange/scale;
attribs.innerPadding = innerUnitPadding+innerPxPadding/scale;
attribs.outerPadding = outerUnitPadding+outerPxPadding/scale;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) { if (!glyph->isWhitespace()) {
Rectangle rect = { }; Rectangle rect = { };
glyph->wrapBox(scale, range, miterLimit, pxAlignOriginX, pxAlignOriginY); glyph->wrapBox(attribs);
glyph->getBoxSize(rect.w, rect.h); glyph->getBoxSize(rect.w, rect.h);
if (rect.w > 0 && rect.h > 0) { if (rect.w > 0 && rect.h > 0) {
rectangles.push_back(rect); rectangles.push_back(rect);
@ -143,11 +150,11 @@ void TightAtlasPacker::setMinimumScale(double minScale) {
this->minScale = minScale; this->minScale = minScale;
} }
void TightAtlasPacker::setUnitRange(double unitRange) { void TightAtlasPacker::setUnitRange(msdfgen::Range unitRange) {
this->unitRange = unitRange; this->unitRange = unitRange;
} }
void TightAtlasPacker::setPixelRange(double pxRange) { void TightAtlasPacker::setPixelRange(msdfgen::Range pxRange) {
this->pxRange = pxRange; this->pxRange = pxRange;
} }
@ -163,6 +170,22 @@ void TightAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) {
pxAlignOriginX = alignX, pxAlignOriginY = alignY; pxAlignOriginX = alignX, pxAlignOriginY = alignY;
} }
void TightAtlasPacker::setInnerUnitPadding(const Padding &padding) {
innerUnitPadding = padding;
}
void TightAtlasPacker::setOuterUnitPadding(const Padding &padding) {
outerUnitPadding = padding;
}
void TightAtlasPacker::setInnerPixelPadding(const Padding &padding) {
innerPxPadding = padding;
}
void TightAtlasPacker::setOuterPixelPadding(const Padding &padding) {
outerPxPadding = padding;
}
void TightAtlasPacker::getDimensions(int &width, int &height) const { void TightAtlasPacker::getDimensions(int &width, int &height) const {
width = this->width, height = this->height; width = this->width, height = this->height;
} }
@ -171,7 +194,7 @@ double TightAtlasPacker::getScale() const {
return scale; return scale;
} }
double TightAtlasPacker::getPixelRange() const { msdfgen::Range TightAtlasPacker::getPixelRange() const {
return pxRange+scale*unitRange; return pxRange+scale*unitRange;
} }

View File

@ -2,6 +2,7 @@
#pragma once #pragma once
#include "types.h" #include "types.h"
#include "Padding.h"
#include "GlyphGeometry.h" #include "GlyphGeometry.h"
namespace msdf_atlas { namespace msdf_atlas {
@ -31,21 +32,29 @@ public:
/// Sets the minimum glyph scale /// Sets the minimum glyph scale
void setMinimumScale(double minScale); void setMinimumScale(double minScale);
/// Sets the unit component of the total distance range /// 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 /// 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 /// Sets the miter limit for bounds computation
void setMiterLimit(double miterLimit); void setMiterLimit(double miterLimit);
/// Sets whether each glyph's origin point should stay aligned with the pixel grid /// Sets whether each glyph's origin point should stay aligned with the pixel grid
void setOriginPixelAlignment(bool align); void setOriginPixelAlignment(bool align);
void setOriginPixelAlignment(bool alignX, bool alignY); 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);
/// Sets the unit component of width of additional padding around each glyph quad
void setOuterUnitPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding that is part of each glyph quad
void setInnerPixelPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding around each glyph quad
void setOuterPixelPadding(const Padding &padding);
/// Outputs the atlas's final dimensions /// Outputs the atlas's final dimensions
void getDimensions(int &width, int &height) const; void getDimensions(int &width, int &height) const;
/// Returns the final glyph scale /// Returns the final glyph scale
double getScale() const; double getScale() const;
/// Returns the final combined pixel range (including converted unit range) /// Returns the final combined pixel range (including converted unit range)
double getPixelRange() const; msdfgen::Range getPixelRange() const;
private: private:
int width, height; int width, height;
@ -53,10 +62,12 @@ private:
DimensionsConstraint dimensionsConstraint; DimensionsConstraint dimensionsConstraint;
double scale; double scale;
double minScale; double minScale;
double unitRange; msdfgen::Range unitRange;
double pxRange; msdfgen::Range pxRange;
double miterLimit; double miterLimit;
bool pxAlignOriginX, pxAlignOriginY; bool pxAlignOriginX, pxAlignOriginY;
Padding innerUnitPadding, outerUnitPadding;
Padding innerPxPadding, outerPxPadding;
double scaleMaximizationTolerance; double scaleMaximizationTolerance;
int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const; int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const;

View File

@ -69,8 +69,10 @@ bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::B
fontVariant.codepointType = convertCodepointType(identifierType); fontVariant.codepointType = convertCodepointType(identifierType);
fontVariant.imageType = convertImageType(properties.imageType); fontVariant.imageType = convertImageType(properties.imageType);
fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize); fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize);
if (properties.imageType != ImageType::HARD_MASK) if (properties.imageType != ImageType::HARD_MASK) {
fontVariant.metrics.distanceRange = REAL(properties.pxRange); fontVariant.metrics.distanceRange = REAL(properties.pxRange.upper-properties.pxRange.lower);
fontVariant.metrics.distanceRangeMiddle = REAL(.5*(properties.pxRange.lower+properties.pxRange.upper));
}
fontVariant.metrics.emSize = REAL(fontMetrics.emSize); fontVariant.metrics.emSize = REAL(fontMetrics.emSize);
fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY); fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY);
fontVariant.metrics.descender = REAL(fontMetrics.descenderY); fontVariant.metrics.descender = REAL(fontMetrics.descenderY);

View File

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

View File

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

View File

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

View File

@ -128,14 +128,28 @@ GLYPH CONFIGURATION
Specifies the size of the glyphs in the atlas bitmap in pixels per em. Specifies the size of the glyphs in the atlas bitmap in pixels per em.
-minsize <em size> -minsize <em size>
Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used. Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used.
-emrange <em range> -emrange <em range width>
Specifies the SDF distance range in em's. Specifies the width of the representable SDF distance range in ems.
-pxrange <pixel range> -pxrange <pixel range width>
Specifies the SDF distance range in output pixels. The default value is 2. 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> -pxalign <off / on / horizontal / vertical>
Specifies whether each glyph's origin point should be aligned with the pixel grid. Specifies whether each glyph's origin point should be aligned with the pixel grid.
-nokerning -nokerning
Disables inclusion of kerning pair table in output files. 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 DISTANCE FIELD GENERATOR SETTINGS
-angle <angle> -angle <angle>
@ -264,6 +278,13 @@ static msdfgen::FontHandle *loadVarFont(msdfgen::FreetypeHandle *library, const
} }
#endif #endif
enum class Units {
/// Value is specified in ems
EMS,
/// Value is specified in pixels
PIXELS
};
struct FontInput { struct FontInput {
const char *fontFilename; const char *fontFilename;
bool variableFont; bool variableFont;
@ -279,7 +300,7 @@ struct Configuration {
YDirection yDirection; YDirection yDirection;
int width, height; int width, height;
double emSize; double emSize;
double pxRange; msdfgen::Range pxRange;
double angleThreshold; double angleThreshold;
double miterLimit; double miterLimit;
bool pxAlignOriginX, pxAlignOriginY; bool pxAlignOriginX, pxAlignOriginY;
@ -370,13 +391,12 @@ int main(int argc, const char *const *argv) {
config.generatorAttributes.config.overlapSupport = !config.preprocessGeometry; config.generatorAttributes.config.overlapSupport = !config.preprocessGeometry;
config.generatorAttributes.scanlinePass = !config.preprocessGeometry; config.generatorAttributes.scanlinePass = !config.preprocessGeometry;
double minEmSize = 0; double minEmSize = 0;
enum { Units rangeUnits = Units::PIXELS;
/// Range specified in ems msdfgen::Range rangeValue = 0;
RANGE_EM, Padding innerPadding;
/// Range specified in output pixels Padding outerPadding;
RANGE_PIXEL, Units innerPaddingUnits = Units::EMS;
} rangeMode = RANGE_PIXEL; Units outerPaddingUnits = Units::EMS;
double rangeValue = 0;
PackingStyle packingStyle = PackingStyle::TIGHT; PackingStyle packingStyle = PackingStyle::TIGHT;
DimensionsConstraint atlasSizeConstraint = DimensionsConstraint::NONE; DimensionsConstraint atlasSizeConstraint = DimensionsConstraint::NONE;
DimensionsConstraint cellSizeConstraint = DimensionsConstraint::NONE; DimensionsConstraint cellSizeConstraint = DimensionsConstraint::NONE;
@ -577,20 +597,40 @@ int main(int argc, const char *const *argv) {
} }
ARG_CASE("-emrange", 1) { ARG_CASE("-emrange", 1) {
double r; double r;
if (!(parseDouble(r, argv[argPos++]) && r >= 0)) if (!(parseDouble(r, argv[argPos++]) && r != 0))
ABORT("Invalid range argument. Use -emrange <em range> with a positive real number."); ABORT("Invalid range argument. Use -emrange <em range> with a non-zero real number.");
rangeMode = RANGE_EM; rangeUnits = Units::EMS;
rangeValue = r; rangeValue = r;
continue; continue;
} }
ARG_CASE("-pxrange", 1) { ARG_CASE("-pxrange", 1) {
double r; double r;
if (!(parseDouble(r, argv[argPos++]) && r >= 0)) if (!(parseDouble(r, argv[argPos++]) && r != 0))
ABORT("Invalid range argument. Use -pxrange <pixel range> with a positive real number."); ABORT("Invalid range argument. Use -pxrange <pixel range> with a non-zero real number.");
rangeMode = RANGE_PIXEL; rangeUnits = Units::PIXELS;
rangeValue = r; rangeValue = r;
continue; 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) { ARG_CASE("-pxalign", 1) {
if (ARG_IS("off") || ARG_PREFIX("disable") || ARG_IS("0") || ARG_IS("false") || ARG_PREFIX("n")) if (ARG_IS("off") || ARG_PREFIX("disable") || ARG_IS("0") || ARG_IS("false") || ARG_PREFIX("n"))
config.pxAlignOriginX = false, config.pxAlignOriginY = false; config.pxAlignOriginX = false, config.pxAlignOriginY = false;
@ -605,6 +645,70 @@ int main(int argc, const char *const *argv) {
++argPos; ++argPos;
continue; 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 = Padding(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 = Padding(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 = Padding(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 = Padding(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) { ARG_CASE("-angle", 1) {
double at; double at;
if (!parseAngle(at, argv[argPos++])) if (!parseAngle(at, argv[argPos++]))
@ -841,10 +945,10 @@ int main(int argc, const char *const *argv) {
minEmSize = DEFAULT_SIZE; minEmSize = DEFAULT_SIZE;
} }
if (config.imageType == ImageType::HARD_MASK || config.imageType == ImageType::SOFT_MASK) { if (config.imageType == ImageType::HARD_MASK || config.imageType == ImageType::SOFT_MASK) {
rangeMode = RANGE_PIXEL; rangeUnits = Units::PIXELS;
rangeValue = 1; rangeValue = 1;
} else if (rangeValue <= 0) { } else if (rangeValue.lower == rangeValue.upper) {
rangeMode = RANGE_PIXEL; rangeUnits = Units::PIXELS;
rangeValue = DEFAULT_PIXEL_RANGE; rangeValue = DEFAULT_PIXEL_RANGE;
} }
if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename)) if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename))
@ -1052,15 +1156,33 @@ int main(int argc, const char *const *argv) {
// Determine final atlas dimensions, scale and range, pack glyphs // Determine final atlas dimensions, scale and range, pack glyphs
{ {
double unitRange = 0, pxRange = 0; msdfgen::Range emRange = 0, pxRange = 0;
switch (rangeMode) { switch (rangeUnits) {
case RANGE_EM: case Units::EMS:
unitRange = rangeValue; emRange = rangeValue;
break; break;
case RANGE_PIXEL: case Units::PIXELS:
pxRange = rangeValue; pxRange = rangeValue;
break; 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 fixedDimensions = fixedWidth >= 0 && fixedHeight >= 0;
bool fixedScale = config.emSize > 0; bool fixedScale = config.emSize > 0;
switch (packingStyle) { switch (packingStyle) {
@ -1077,9 +1199,13 @@ int main(int argc, const char *const *argv) {
else else
atlasPacker.setMinimumScale(minEmSize); atlasPacker.setMinimumScale(minEmSize);
atlasPacker.setPixelRange(pxRange); atlasPacker.setPixelRange(pxRange);
atlasPacker.setUnitRange(unitRange); atlasPacker.setUnitRange(emRange);
atlasPacker.setMiterLimit(config.miterLimit); atlasPacker.setMiterLimit(config.miterLimit);
atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY); 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 (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) {
if (remaining < 0) { if (remaining < 0) {
ABORT("Failed to pack glyphs into atlas."); ABORT("Failed to pack glyphs into atlas.");
@ -1119,9 +1245,13 @@ int main(int argc, const char *const *argv) {
else else
atlasPacker.setMinimumScale(minEmSize); atlasPacker.setMinimumScale(minEmSize);
atlasPacker.setPixelRange(pxRange); atlasPacker.setPixelRange(pxRange);
atlasPacker.setUnitRange(unitRange); atlasPacker.setUnitRange(emRange);
atlasPacker.setMiterLimit(config.miterLimit); atlasPacker.setMiterLimit(config.miterLimit);
atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY); 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 (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) {
if (remaining < 0) { if (remaining < 0) {
ABORT("Failed to pack glyphs into atlas."); ABORT("Failed to pack glyphs into atlas.");

View File

@ -14,6 +14,7 @@
#include "types.h" #include "types.h"
#include "utf8.h" #include "utf8.h"
#include "Rectangle.h" #include "Rectangle.h"
#include "Padding.h"
#include "Charset.h" #include "Charset.h"
#include "GlyphBox.h" #include "GlyphBox.h"
#include "GlyphGeometry.h" #include "GlyphGeometry.h"

View File

@ -7,7 +7,7 @@
namespace msdf_atlas { namespace msdf_atlas {
static const char *const shadronFillGlyphMask = R"( static const char *const shadronFillGlyphMask = R"(
template <ATLAS, RANGE, COLOR> template <ATLAS, RANGE, ZERO_DIST, COLOR>
glsl vec4 fillGlyph(vec2 texCoord) { glsl vec4 fillGlyph(vec2 texCoord) {
float fill = texture((ATLAS), texCoord).r; float fill = texture((ATLAS), texCoord).r;
return vec4(vec3(COLOR), fill); return vec4(vec3(COLOR), fill);
@ -15,10 +15,10 @@ glsl vec4 fillGlyph(vec2 texCoord) {
)"; )";
static const char *const shadronFillGlyphSdf = R"( static const char *const shadronFillGlyphSdf = R"(
template <ATLAS, RANGE, COLOR> template <ATLAS, RANGE, ZERO_DIST, COLOR>
glsl vec4 fillGlyph(vec2 texCoord) { glsl vec4 fillGlyph(vec2 texCoord) {
vec3 s = texture((ATLAS), texCoord).rgb; 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); float fill = clamp(sd+0.5, 0.0, 1.0);
return vec4(vec3(COLOR), fill); 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); return vec4(coord, 0.0, 1.0);
} }
%s %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), \ vertex_data(GlyphVertex), \
fragment_data(vec2), \ fragment_data(vec2), \
vertex(projectVertex<TEXT_SIZE>, triangles, VERTEX_LIST), \ vertex(projectVertex<TEXT_SIZE>, triangles, VERTEX_LIST), \
fragment(fillGlyph<ATLAS, RANGE, COLOR>), \ fragment(fillGlyph<ATLAS, RANGE, ZERO_DIST, COLOR>), \
depth(false), \ depth(false), \
blend(transparency), \ blend(transparency), \
background(vec4(vec3(COLOR), 0.0)), \ background(vec4(vec3(COLOR), 0.0)), \
@ -94,7 +94,7 @@ static std::string escapeString(const std::string &str) {
return output; 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) if (fontCount <= 0)
return false; return false;
double texelWidth = 1./atlasWidth; double texelWidth = 1./atlasWidth;
@ -109,7 +109,9 @@ bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType
else else
fprintf(file, "image Atlas = file()"); fprintf(file, "image Atlas = file()");
fprintf(file, " : %sfilter(%s), map(repeat);\n", fullRange ? "full_range(true), " : "", atlasType == ImageType::HARD_MASK ? "nearest" : "linear"); 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(); msdfgen::FontMetrics fontMetrics = fonts->getMetrics();
for (int i = 1; i < fontCount; ++i) { for (int i = 1; i < fontCount; ++i) {
@ -163,7 +165,7 @@ bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType
fputs("};\n", file); fputs("};\n", file);
fprintf(file, "const vec2 textSize = vec2(%.9g, %.9g);\n\n", textWidth, -y); 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); fputs("export png(Preview, \"preview.png\");\n", file);
fclose(file); fclose(file);
return anyGlyphs; return anyGlyphs;

View File

@ -9,6 +9,6 @@
namespace msdf_atlas { namespace msdf_atlas {
/// Generates a Shadron script that displays a string using the generated 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);
} }

@ -1 +1 @@
Subproject commit c7a724c17366db009a43514b90329519d792b51b Subproject commit 5dc5f6260b85064c7972808daa8f544d76a73c17