From 2588a2aae310bd9834ab8aa53636f99cdd626156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Chlumsk=C3=BD?= Date: Wed, 1 May 2024 23:25:00 +0200 Subject: [PATCH] Padding --- README.md | 4 + artery-font-format | 2 +- msdf-atlas-gen/GlyphGeometry.cpp | 114 ++++++++---- msdf-atlas-gen/GlyphGeometry.h | 18 +- msdf-atlas-gen/GridAtlasPacker.cpp | 91 ++++++--- msdf-atlas-gen/GridAtlasPacker.h | 23 ++- msdf-atlas-gen/Padding.cpp | 37 ++++ msdf-atlas-gen/Padding.h | 24 +++ msdf-atlas-gen/TightAtlasPacker.cpp | 33 +++- msdf-atlas-gen/TightAtlasPacker.h | 21 ++- msdf-atlas-gen/artery-font-export.cpp | 6 +- msdf-atlas-gen/artery-font-export.h | 2 +- msdf-atlas-gen/json-export.cpp | 6 +- msdf-atlas-gen/json-export.h | 2 +- msdf-atlas-gen/main.cpp | 186 ++++++++++++++++--- msdf-atlas-gen/msdf-atlas-gen.h | 1 + msdf-atlas-gen/shadron-preview-generator.cpp | 18 +- msdf-atlas-gen/shadron-preview-generator.h | 2 +- msdfgen | 2 +- 19 files changed, 468 insertions(+), 124 deletions(-) create mode 100644 msdf-atlas-gen/Padding.cpp create mode 100644 msdf-atlas-gen/Padding.h diff --git a/README.md b/README.md index c666243..d3b9391 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,11 @@ Any non-empty subset of the following may be specified: - `-minsize ` – sets the minimum size. The largest possible size that fits the same atlas dimensions will be used - `-emrange ` – sets the distance field range in em's - `-pxrange ` (default = 2) – sets the distance field range in output pixels +- `-aemrange` / `-apxrange ` – sets the distance field range asymmetrically by specifying the minimum and maximum representable signed distances (outside distances are negative!) - `-pxalign ` (default = vertical) – enables or disables alignment of glyph's origin point with the pixel grid +- `-empadding` / `-pxpadding ` – sets additional padding within each glyph's box (in em's / pixels) +- `-outerempadding` / `-outerpxpadding ` – sets additional padding around each glyph's box +- `-aempadding` / `-apxpadding` / `-aouterempadding` / `-aouterpxpadding ` – sets additional padding (see above) asymmetrically with a separate width value for each side ### Distance field generator settings diff --git a/artery-font-format b/artery-font-format index 34134bd..8886742 160000 --- a/artery-font-format +++ b/artery-font-format @@ -1 +1 @@ -Subproject commit 34134bde3cea35a93c2ae5703fa8d3d463793400 +Subproject commit 888674220216d1d326c6f29cf89165b545279c1f diff --git a/msdf-atlas-gen/GlyphGeometry.cpp b/msdf-atlas-gen/GlyphGeometry.cpp index 049ff72..eb9fa14 100644 --- a/msdf-atlas-gen/GlyphGeometry.cpp +++ b/msdf-atlas-gen/GlyphGeometry.cpp @@ -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 = (glyphAttributes.innerPadding+glyphAttributes.outerPadding)/geometryScale; 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 = (glyphAttributes.innerPadding+glyphAttributes.outerPadding)/geometryScale; 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,8 @@ 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); +} + } diff --git a/msdf-atlas-gen/GlyphGeometry.h b/msdf-atlas-gen/GlyphGeometry.h index 594020b..6e0c13a 100644 --- a/msdf-atlas-gen/GlyphGeometry.h +++ b/msdf-atlas-gen/GlyphGeometry.h @@ -5,6 +5,7 @@ #include #include "types.h" #include "Rectangle.h" +#include "Padding.h" #include "GlyphBox.h" namespace msdf_atlas { @@ -13,6 +14,14 @@ namespace msdf_atlas { 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 +29,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 +63,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 +88,14 @@ 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); + } diff --git a/msdf-atlas-gen/GridAtlasPacker.cpp b/msdf-atlas-gen/GridAtlasPacker.cpp index c8af8b8..35d279e 100644 --- a/msdf-atlas-gen/GridAtlasPacker.cpp +++ b/msdf-atlas-gen/GridAtlasPacker.cpp @@ -107,20 +107,20 @@ GridAtlasPacker::GridAtlasPacker() : 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 +133,10 @@ 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; + 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 (hFixed) 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 -= 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 +224,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 +233,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 +300,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 +320,12 @@ 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 + pad(maxBounds, -pxPadding); + 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 +336,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 +364,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 +395,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 +414,15 @@ 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 + pad(maxBounds, pxPadding); + 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 +496,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 +577,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 +597,22 @@ void GridAtlasPacker::setOriginPixelAlignment(bool alignX, bool 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 { width = this->width, height = this->height; } @@ -594,7 +633,7 @@ double GridAtlasPacker::getScale() const { return scale; } -double GridAtlasPacker::getPixelRange() const { +msdfgen::Range GridAtlasPacker::getPixelRange() const { return pxRange+scale*unitRange; } diff --git a/msdf-atlas-gen/GridAtlasPacker.h b/msdf-atlas-gen/GridAtlasPacker.h index 98de280..b7debd2 100644 --- a/msdf-atlas-gen/GridAtlasPacker.h +++ b/msdf-atlas-gen/GridAtlasPacker.h @@ -1,6 +1,7 @@ #pragma once +#include "Padding.h" #include "GlyphGeometry.h" namespace msdf_atlas { @@ -40,14 +41,22 @@ 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); + /// 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 void getDimensions(int &width, int &height) const; @@ -60,7 +69,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 +86,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 +100,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; }; diff --git a/msdf-atlas-gen/Padding.cpp b/msdf-atlas-gen/Padding.cpp new file mode 100644 index 0000000..998cdfb --- /dev/null +++ b/msdf-atlas-gen/Padding.cpp @@ -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); +} + +} diff --git a/msdf-atlas-gen/Padding.h b/msdf-atlas-gen/Padding.h new file mode 100644 index 0000000..f014d7e --- /dev/null +++ b/msdf-atlas-gen/Padding.h @@ -0,0 +1,24 @@ + +#pragma once + +#include + +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); + +} diff --git a/msdf-atlas-gen/TightAtlasPacker.cpp b/msdf-atlas-gen/TightAtlasPacker.cpp index f18add2..77f921c 100644 --- a/msdf-atlas-gen/TightAtlasPacker.cpp +++ b/msdf-atlas-gen/TightAtlasPacker.cpp @@ -22,16 +22,23 @@ TightAtlasPacker::TightAtlasPacker() : { } 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 rectangles; std::vector rectangleGlyphs; rectangles.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) { 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 +150,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 +170,22 @@ void TightAtlasPacker::setOriginPixelAlignment(bool alignX, bool 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 { width = this->width, height = this->height; } @@ -171,7 +194,7 @@ double TightAtlasPacker::getScale() const { return scale; } -double TightAtlasPacker::getPixelRange() const { +msdfgen::Range TightAtlasPacker::getPixelRange() const { return pxRange+scale*unitRange; } diff --git a/msdf-atlas-gen/TightAtlasPacker.h b/msdf-atlas-gen/TightAtlasPacker.h index a385262..0f2aa5f 100644 --- a/msdf-atlas-gen/TightAtlasPacker.h +++ b/msdf-atlas-gen/TightAtlasPacker.h @@ -2,6 +2,7 @@ #pragma once #include "types.h" +#include "Padding.h" #include "GlyphGeometry.h" namespace msdf_atlas { @@ -31,21 +32,29 @@ 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); + /// 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 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 +62,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; diff --git a/msdf-atlas-gen/artery-font-export.cpp b/msdf-atlas-gen/artery-font-export.cpp index 2c15ac6..6c90759 100644 --- a/msdf-atlas-gen/artery-font-export.cpp +++ b/msdf-atlas-gen/artery-font-export.cpp @@ -69,8 +69,10 @@ bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::B fontVariant.codepointType = convertCodepointType(identifierType); 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); + if (properties.imageType != ImageType::HARD_MASK) { + 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.ascender = REAL(fontMetrics.ascenderY); fontVariant.metrics.descender = REAL(fontMetrics.descenderY); diff --git a/msdf-atlas-gen/artery-font-export.h b/msdf-atlas-gen/artery-font-export.h index f61d417..7aa00e9 100644 --- a/msdf-atlas-gen/artery-font-export.h +++ b/msdf-atlas-gen/artery-font-export.h @@ -12,7 +12,7 @@ namespace msdf_atlas { struct ArteryFontExportProperties { double fontSize; - double pxRange; + msdfgen::Range pxRange; ImageType imageType; ImageFormat imageFormat; YDirection yDirection; diff --git a/msdf-atlas-gen/json-export.cpp b/msdf-atlas-gen/json-export.cpp index f86bd06..44117ee 100644 --- a/msdf-atlas-gen/json-export.cpp +++ b/msdf-atlas-gen/json-export.cpp @@ -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, "\"distanceRangeMiddle\":%.17g,", .5*(metrics.distanceRange.lower+metrics.distanceRange.upper)); + } fprintf(f, "\"size\":%.17g,", metrics.size); fprintf(f, "\"width\":%d,", metrics.width); fprintf(f, "\"height\":%d,", metrics.height); diff --git a/msdf-atlas-gen/json-export.h b/msdf-atlas-gen/json-export.h index aaa5e0b..58c763f 100644 --- a/msdf-atlas-gen/json-export.h +++ b/msdf-atlas-gen/json-export.h @@ -15,7 +15,7 @@ struct JsonAtlasMetrics { const double *originX, *originY; int spacing; }; - double distanceRange; + msdfgen::Range distanceRange; double size; int width, height; YDirection yDirection; diff --git a/msdf-atlas-gen/main.cpp b/msdf-atlas-gen/main.cpp index 385bd86..caee8cf 100644 --- a/msdf-atlas-gen/main.cpp +++ b/msdf-atlas-gen/main.cpp @@ -128,14 +128,28 @@ GLYPH CONFIGURATION Specifies the size of the glyphs in the atlas bitmap in pixels per em. -minsize Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used. - -emrange - Specifies the SDF distance range in em's. - -pxrange - Specifies the SDF distance range in output pixels. The default value is 2. + -emrange + Specifies the width of the representable SDF distance range in ems. + -pxrange + Specifies the width of the SDF distance range in output pixels. The default value is 2. + -aemrange + Specifies the outermost (negative) and innermost representable distance in ems. + -apxrange + Specifies the outermost (negative) and innermost representable distance in pixels. -pxalign 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 + -pxpadding + -outerempadding + -outerpxpadding +Or asymmetrical padding with a separate value for each side: + -aempadding + -apxpadding + -aouterempadding + -aouterpxpadding DISTANCE FIELD GENERATOR SETTINGS -angle @@ -264,6 +278,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 +300,7 @@ struct Configuration { YDirection yDirection; int width, height; double emSize; - double pxRange; + msdfgen::Range pxRange; double angleThreshold; double miterLimit; bool pxAlignOriginX, pxAlignOriginY; @@ -370,13 +391,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 +597,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 with a positive real number."); - rangeMode = RANGE_EM; + if (!(parseDouble(r, argv[argPos++]) && r != 0)) + ABORT("Invalid range argument. Use -emrange 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 with a positive real number."); - rangeMode = RANGE_PIXEL; + if (!(parseDouble(r, argv[argPos++]) && r != 0)) + ABORT("Invalid range argument. Use -pxrange 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 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 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 +645,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 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 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 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 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 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 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 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 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 +945,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 +1156,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 +1199,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 +1245,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."); diff --git a/msdf-atlas-gen/msdf-atlas-gen.h b/msdf-atlas-gen/msdf-atlas-gen.h index 7f5b060..a7081f4 100644 --- a/msdf-atlas-gen/msdf-atlas-gen.h +++ b/msdf-atlas-gen/msdf-atlas-gen.h @@ -14,6 +14,7 @@ #include "types.h" #include "utf8.h" #include "Rectangle.h" +#include "Padding.h" #include "Charset.h" #include "GlyphBox.h" #include "GlyphGeometry.h" diff --git a/msdf-atlas-gen/shadron-preview-generator.cpp b/msdf-atlas-gen/shadron-preview-generator.cpp index 08d2f06..dde3786 100644 --- a/msdf-atlas-gen/shadron-preview-generator.cpp +++ b/msdf-atlas-gen/shadron-preview-generator.cpp @@ -7,7 +7,7 @@ namespace msdf_atlas { static const char *const shadronFillGlyphMask = R"( -template +template 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 +template 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, triangles, VERTEX_LIST), \ - fragment(fillGlyph), \ + fragment(fillGlyph), \ 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; diff --git a/msdf-atlas-gen/shadron-preview-generator.h b/msdf-atlas-gen/shadron-preview-generator.h index 75da3eb..a0d5727 100644 --- a/msdf-atlas-gen/shadron-preview-generator.h +++ b/msdf-atlas-gen/shadron-preview-generator.h @@ -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); } diff --git a/msdfgen b/msdfgen index c7a724c..5dc5f62 160000 --- a/msdfgen +++ b/msdfgen @@ -1 +1 @@ -Subproject commit c7a724c17366db009a43514b90329519d792b51b +Subproject commit 5dc5f6260b85064c7972808daa8f544d76a73c17