diff --git a/msdf-atlas-gen/GlyphGeometry.cpp b/msdf-atlas-gen/GlyphGeometry.cpp index 049ff72..31f568a 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 = 1/geometryScale*(glyphAttributes.innerPadding+glyphAttributes.outerPadding); box.range = range; box.scale = scale; if (bounds.l < bounds.r && bounds.b < bounds.t) { double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; - l -= .5*range, b -= .5*range; - r += .5*range, t += .5*range; - if (miterLimit > 0) - shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); - if (pxAlignOriginX) { + l += range.lower, b += range.lower; + r -= range.lower, t -= range.lower; + if (glyphAttributes.miterLimit > 0) + shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1); + l -= fullPadding.l, b -= fullPadding.b; + r += fullPadding.r, t += fullPadding.t; + if (glyphAttributes.pxAlignOriginX) { int sl = (int) floor(scale*l-.5); int sr = (int) ceil(scale*r+.5); box.rect.w = sr-sl; @@ -76,7 +75,7 @@ void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool box.rect.w = (int) ceil(w)+1; box.translate.x = -l+.5*(box.rect.w-w)/scale; } - if (pxAlignOriginY) { + if (glyphAttributes.pxAlignOriginY) { int sb = (int) floor(scale*b-.5); int st = (int) ceil(scale*t+.5); box.rect.h = st-sb; @@ -86,19 +85,37 @@ void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool box.rect.h = (int) ceil(h)+1; box.translate.y = -b+.5*(box.rect.h-h)/scale; } + box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding; } else { box.rect.w = 0, box.rect.h = 0; box.translate = msdfgen::Vector2(); } } -void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin) { - frameBox(scale, range, miterLimit, width, height, fixedX, fixedY, pxAlignOrigin, pxAlignOrigin); +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin) { + GlyphAttributes attribs = { }; + attribs.scale = scale; + attribs.range = range; + attribs.miterLimit = miterLimit; + attribs.pxAlignOriginX = pxAlignOrigin; + attribs.pxAlignOriginY = pxAlignOrigin; + wrapBox(attribs); } -void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY) { - scale *= geometryScale; - range /= geometryScale; +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY) { + GlyphAttributes attribs = { }; + attribs.scale = scale; + attribs.range = range; + attribs.miterLimit = miterLimit; + attribs.pxAlignOriginX = pxAlignOriginX; + attribs.pxAlignOriginY = pxAlignOriginY; + wrapBox(attribs); +} + +void GlyphGeometry::frameBox(const GlyphAttributes &glyphAttributes, int width, int height, const double *fixedX, const double *fixedY) { + double scale = glyphAttributes.scale*geometryScale; + msdfgen::Range range = glyphAttributes.range/geometryScale; + Padding fullPadding = 1/geometryScale*(glyphAttributes.innerPadding+glyphAttributes.outerPadding); box.range = range; box.scale = scale; box.rect.w = width; @@ -108,13 +125,15 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int box.translate.y = *fixedY/geometryScale; } else { double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; - l -= .5*range, b -= .5*range; - r += .5*range, t += .5*range; - if (miterLimit > 0) - shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); + l += range.lower, b += range.lower; + r -= range.lower, t -= range.lower; + if (glyphAttributes.miterLimit > 0) + shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1); + l -= fullPadding.l, b -= fullPadding.b; + r += fullPadding.r, t += fullPadding.t; if (fixedX) box.translate.x = *fixedX/geometryScale; - else if (pxAlignOriginX) { + else if (glyphAttributes.pxAlignOriginX) { int sl = (int) floor(scale*l-.5); int sr = (int) ceil(scale*r+.5); box.translate.x = (-sl+(box.rect.w-(sr-sl))/2)/scale; @@ -124,7 +143,7 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int } if (fixedY) box.translate.y = *fixedY/geometryScale; - else if (pxAlignOriginY) { + else if (glyphAttributes.pxAlignOriginY) { int sb = (int) floor(scale*b-.5); int st = (int) ceil(scale*t+.5); box.translate.y = (-sb+(box.rect.h-(st-sb))/2)/scale; @@ -133,6 +152,27 @@ void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int box.translate.y = -b+.5*(box.rect.h-h)/scale; } } + box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding; +} + +void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin) { + GlyphAttributes attribs = { }; + attribs.scale = scale; + attribs.range = range; + attribs.miterLimit = miterLimit; + attribs.pxAlignOriginX = pxAlignOrigin; + attribs.pxAlignOriginY = pxAlignOrigin; + frameBox(attribs, width, height, fixedX, fixedY); +} + +void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY) { + GlyphAttributes attribs = { }; + attribs.scale = scale; + attribs.range = range; + attribs.miterLimit = miterLimit; + attribs.pxAlignOriginX = pxAlignOriginX; + attribs.pxAlignOriginY = pxAlignOriginY; + frameBox(attribs, width, height, fixedX, fixedY); } void GlyphGeometry::placeBox(int x, int y) { @@ -194,7 +234,7 @@ void GlyphGeometry::getBoxSize(int &w, int &h) const { w = box.rect.w, h = box.rect.h; } -double GlyphGeometry::getBoxRange() const { +msdfgen::Range GlyphGeometry::getBoxRange() const { return box.range; } @@ -213,20 +253,20 @@ msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const { void GlyphGeometry::getQuadPlaneBounds(double &l, double &b, double &r, double &t) const { if (box.rect.w > 0 && box.rect.h > 0) { double invBoxScale = 1/box.scale; - l = geometryScale*(-box.translate.x+.5*invBoxScale); - b = geometryScale*(-box.translate.y+.5*invBoxScale); - r = geometryScale*(-box.translate.x+(box.rect.w-.5)*invBoxScale); - t = geometryScale*(-box.translate.y+(box.rect.h-.5)*invBoxScale); + l = geometryScale*(-box.translate.x+(box.outerPadding.l+.5)*invBoxScale); + b = geometryScale*(-box.translate.y+(box.outerPadding.b+.5)*invBoxScale); + r = geometryScale*(-box.translate.x+(-box.outerPadding.r+box.rect.w-.5)*invBoxScale); + t = geometryScale*(-box.translate.y+(-box.outerPadding.t+box.rect.h-.5)*invBoxScale); } else l = 0, b = 0, r = 0, t = 0; } void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const { if (box.rect.w > 0 && box.rect.h > 0) { - l = box.rect.x+.5; - b = box.rect.y+.5; - r = box.rect.x+box.rect.w-.5; - t = box.rect.y+box.rect.h-.5; + l = box.rect.x+box.outerPadding.l+.5; + b = box.rect.y+box.outerPadding.b+.5; + r = box.rect.x-box.outerPadding.r+box.rect.w-.5; + t = box.rect.y-box.outerPadding.t+box.rect.h-.5; } else l = 0, b = 0, r = 0, t = 0; } @@ -244,4 +284,26 @@ GlyphGeometry::operator GlyphBox() const { return box; } +msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b) { + return msdfgen::Range(a.lower+b.lower, a.upper+b.upper); +} + +Padding operator+(const Padding &a, const Padding &b) { + Padding result; + result.l = a.l+b.l; + result.b = a.b+b.b; + result.r = a.r+b.r; + result.t = a.t+b.t; + return result; +} + +Padding operator*(double a, const Padding &b) { + Padding result; + result.l = a*b.l; + result.b = a*b.b; + result.r = a*b.r; + result.t = a*b.t; + return result; +} + } diff --git a/msdf-atlas-gen/GlyphGeometry.h b/msdf-atlas-gen/GlyphGeometry.h index 594020b..c4f881d 100644 --- a/msdf-atlas-gen/GlyphGeometry.h +++ b/msdf-atlas-gen/GlyphGeometry.h @@ -9,10 +9,20 @@ namespace msdf_atlas { +typedef msdfgen::Shape::Bounds Padding; + /// Represents the shape geometry of a single glyph as well as its configuration class GlyphGeometry { public: + struct GlyphAttributes { + double scale; + msdfgen::Range range; + Padding innerPadding, outerPadding; + double miterLimit; + bool pxAlignOriginX, pxAlignOriginY; + }; + GlyphGeometry(); /// Loads glyph geometry from font bool load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true); @@ -20,9 +30,11 @@ public: /// Applies edge coloring to glyph shape void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed); /// Computes the dimensions of the glyph's box as well as the transformation for the generator function + void wrapBox(const GlyphAttributes &glyphAttributes); void wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin = false); void wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY); /// Computes the glyph's transformation and alignment (unless specified) for given dimensions + void frameBox(const GlyphAttributes &glyphAttributes, int width, int height, const double *fixedX, const double *fixedY); void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin = false); void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY); /// Sets the glyph's box's position in the atlas @@ -52,7 +64,7 @@ public: /// Outputs the dimensions of the glyph's box in the atlas void getBoxSize(int &w, int &h) const; /// Returns the range needed to generate the glyph's SDF - double getBoxRange() const; + msdfgen::Range getBoxRange() const; /// Returns the projection needed to generate the glyph's bitmap msdfgen::Projection getBoxProjection() const; /// Returns the scale needed to generate the glyph's bitmap @@ -77,11 +89,17 @@ private: double advance; struct { Rectangle rect; - double range; + msdfgen::Range range; double scale; msdfgen::Vector2 translate; + Padding outerPadding; } box; }; +msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b); + +Padding operator+(const Padding &a, const Padding &b); +Padding operator*(double a, const Padding &b); + } diff --git a/msdf-atlas-gen/GridAtlasPacker.cpp b/msdf-atlas-gen/GridAtlasPacker.cpp index c8af8b8..5dcc701 100644 --- a/msdf-atlas-gen/GridAtlasPacker.cpp +++ b/msdf-atlas-gen/GridAtlasPacker.cpp @@ -102,25 +102,27 @@ GridAtlasPacker::GridAtlasPacker() : pxRange(0), miterLimit(0), pxAlignOriginX(false), pxAlignOriginY(false), + innerUnitPadding(), outerUnitPadding(), + innerPxPadding(), outerPxPadding(), scaleMaximizationTolerance(.001), alignedColumnsBias(.125), cutoff(false) { } -msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double range) const { +msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double outerRange) const { static const double LARGE_VALUE = 1e240; msdfgen::Shape::Bounds maxBounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE }; for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { if (!glyph->isWhitespace()) { double geometryScale = glyph->getGeometryScale(); - double shapeRange = range/geometryScale; + double shapeOuterRange = outerRange/geometryScale; geometryScale *= scale; const msdfgen::Shape::Bounds &shapeBounds = glyph->getShapeBounds(); double l = shapeBounds.l, b = shapeBounds.b, r = shapeBounds.r, t = shapeBounds.t; - l -= .5*shapeRange, b -= .5*shapeRange; - r += .5*shapeRange, t += .5*shapeRange; + l -= shapeOuterRange, b -= shapeOuterRange; + r += shapeOuterRange, t += shapeOuterRange; if (miterLimit > 0) - glyph->getShape().boundMiters(l, b, r, t, .5*shapeRange, miterLimit, 1); + glyph->getShape().boundMiters(l, b, r, t, shapeOuterRange, miterLimit, 1); l *= geometryScale, b *= geometryScale; r *= geometryScale, t *= geometryScale; maxBounds.l = std::min(maxBounds.l, l); @@ -133,6 +135,11 @@ msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &m } if (maxBounds.l >= maxBounds.r || maxBounds.b >= maxBounds.t) maxBounds = msdfgen::Shape::Bounds(); + Padding fullPadding = scale*(innerUnitPadding+outerUnitPadding)+innerPxPadding+outerPxPadding; + maxBounds.l -= fullPadding.l, maxBounds.b -= fullPadding.b; + maxBounds.r += fullPadding.r, maxBounds.t += fullPadding.t; + maxWidth += fullPadding.l+fullPadding.r; + maxHeight += fullPadding.b+fullPadding.t; // If origin is pixel-aligned but not fixed, one pixel has to be added to max dimensions to allow for aligning the origin by shifting by < 1 pixel if (hFixed) maxWidth = maxBounds.r-maxBounds.l; @@ -154,7 +161,7 @@ double GridAtlasPacker::scaleToFit(GlyphGeometry *glyphs, int count, int cellWid --cellWidth, --cellHeight; // Implicit half-pixel padding from each side to make sure that no representable values are beyond outermost pixel centers cellWidth -= spacing, cellHeight -= spacing; bool lastResult = false; - #define TRY_FIT(scale) (maxWidth = 0, maxHeight = 0, maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, (scale), unitRange+pxRange/(scale)), lastResult = maxWidth <= cellWidth && maxHeight <= cellHeight) + #define TRY_FIT(scale) (maxWidth = 0, maxHeight = 0, maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, (scale), -(unitRange.lower+pxRange.lower/(scale))), lastResult = maxWidth <= cellWidth && maxHeight <= cellHeight) double minScale = 1, maxScale = 1; if (TRY_FIT(1)) { while (maxScale < 1e+32 && ((maxScale = 2*minScale), TRY_FIT(maxScale))) @@ -220,7 +227,7 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { return -1; } - if ((cellWidth > 0 && cellWidth-spacing-1 <= pxRange) || (cellHeight > 0 && cellHeight-spacing-1 <= pxRange)) // cells definitely too small + if ((cellWidth > 0 && cellWidth-spacing-1 <= -2*pxRange.lower) || (cellHeight > 0 && cellHeight-spacing-1 <= -2*pxRange.lower)) // cells definitely too small return -1; msdfgen::Shape::Bounds maxBounds = { }; @@ -229,14 +236,14 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { if (scale <= 0) { // If both pxRange and miterLimit is non-zero, miter bounds have to be computed for all potential scales - if (pxRange && miterLimit > 0) { + if (pxRange.lower != pxRange.upper && miterLimit > 0) { if (cellWidth > 0 || cellHeight > 0) { scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight); if (scale < minScale) { scale = minScale; cutoff = true; - maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale); + maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, -(unitRange.lower+pxRange.lower/scale)); } } @@ -296,13 +303,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { } if (scale <= 0) { - maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, minScale, unitRange+pxRange/minScale); + maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, minScale, -(unitRange.lower+pxRange.lower/minScale)); cellWidth = (int) ceil(maxWidth)+spacing+1; cellHeight = (int) ceil(maxHeight)+spacing+1; raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight); if (scale < minScale) - maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale = minScale, unitRange+pxRange/minScale); + maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale = minScale, -(unitRange.lower+pxRange.lower/minScale)); } if (initial.rows < 0 && initial.cellHeight < 0) { @@ -316,7 +323,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { } else { - maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, 1, unitRange); + Padding pxPadding = innerPxPadding+outerPxPadding; + maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, 1, -unitRange.lower); + // Undo pxPadding added by getMaxBounds before pixel scale is known + maxBounds.l += pxPadding.l, maxBounds.b += pxPadding.b; + maxBounds.r -= pxPadding.r, maxBounds.t -= pxPadding.t; + maxWidth -= pxPadding.l+pxPadding.r; + maxHeight -= pxPadding.b+pxPadding.t; int hSlack = 0, vSlack = 0; if (pxAlignOriginX && !hFixed) { maxWidth -= 1; // Added by getMaxBounds @@ -327,11 +340,13 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { vSlack = 1; } + double extraPxWidth = -2*pxRange.lower+pxPadding.l+pxPadding.r; + double extraPxHeight = -2*pxRange.lower+pxPadding.b+pxPadding.t; double hScale = 0, vScale = 0; if (cellWidth > 0) - hScale = (cellWidth-hSlack-spacing-1-pxRange)/maxWidth; + hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth; if (cellHeight > 0) - vScale = (cellHeight-vSlack-spacing-1-pxRange)/maxHeight; + vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight; if (hScale || vScale) { if (hScale && vScale) scale = std::min(hScale, vScale); @@ -353,8 +368,8 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { int tHeight = (height+spacing)/rows; lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint); if (tWidth > 0 && tHeight > 0) { - hScale = (tWidth-hSlack-spacing-1-pxRange)/maxWidth; - vScale = (tHeight-vSlack-spacing-1-pxRange)/maxHeight; + hScale = (tWidth-hSlack-spacing-extraPxWidth-1)/maxWidth; + vScale = (tHeight-vSlack-spacing-extraPxHeight-1)/maxHeight; double curScale = std::min(hScale, vScale); if (curScale > scale) { scale = curScale; @@ -384,16 +399,16 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { } if (scale <= 0) { - cellWidth = (int) ceil(minScale*maxWidth+pxRange)+hSlack+spacing+1; - cellHeight = (int) ceil(minScale*maxHeight+pxRange)+vSlack+spacing+1; + cellWidth = (int) ceil(minScale*maxWidth+extraPxWidth)+hSlack+spacing+1; + cellHeight = (int) ceil(minScale*maxHeight+extraPxHeight)+vSlack+spacing+1; raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint); - hScale = (cellWidth-hSlack-spacing-1-pxRange)/maxWidth; - vScale = (cellHeight-vSlack-spacing-1-pxRange)/maxHeight; + hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth; + vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight; scale = std::min(hScale, vScale); } if (initial.rows < 0 && initial.cellHeight < 0) { - int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(scale*maxHeight+pxRange)+vSlack+spacing+1; + int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(scale*maxHeight+extraPxHeight)+vSlack+spacing+1; raiseToConstraint(optimalCellWidth, optimalCellHeight, cellDimensionsConstraint); if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) { cellWidth = optimalCellWidth; @@ -403,11 +418,16 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { maxBounds.l *= scale, maxBounds.b *= scale; maxBounds.r *= scale, maxBounds.t *= scale; maxWidth *= scale, maxHeight *= scale; + // Redo addition of pxPadding once scale is known + maxBounds.l -= pxPadding.l, maxBounds.b -= pxPadding.b; + maxBounds.r += pxPadding.r, maxBounds.t += pxPadding.t; + maxWidth += pxPadding.l+pxPadding.r; + maxHeight += pxPadding.b+pxPadding.t; } } else { - maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, unitRange+pxRange/scale); + maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, -(unitRange.lower+pxRange.lower/scale)); int optimalCellWidth = (int) ceil(maxWidth)+spacing+1; int optimalCellHeight = (int) ceil(maxHeight)+spacing+1; if (cellWidth < 0 || cellHeight < 0) { @@ -481,10 +501,18 @@ int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) { if (rows*cellHeight > height) rows = height/cellHeight; + GlyphGeometry::GlyphAttributes attribs = { }; + attribs.scale = scale; + attribs.range = unitRange+pxRange/scale; + attribs.innerPadding = innerUnitPadding+1/scale*innerPxPadding; + attribs.outerPadding = outerUnitPadding+1/scale*outerPxPadding; + attribs.miterLimit = miterLimit; + attribs.pxAlignOriginX = pxAlignOriginX; + attribs.pxAlignOriginY = pxAlignOriginY; int col = 0, row = 0; for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { if (!glyph->isWhitespace()) { - glyph->frameBox(scale, unitRange+pxRange/scale, miterLimit, cellWidth-spacing, cellHeight-spacing, hFixed ? &fixedX : nullptr, vFixed ? &fixedY : nullptr, pxAlignOriginX, pxAlignOriginY); + glyph->frameBox(attribs, cellWidth-spacing, cellHeight-spacing, hFixed ? &fixedX : nullptr, vFixed ? &fixedY : nullptr); glyph->placeBox(col*cellWidth, height-(row+1)*cellHeight); if (++col >= columns) { if (++row >= rows) { @@ -554,11 +582,11 @@ void GridAtlasPacker::setMinimumScale(double minScale) { this->minScale = minScale; } -void GridAtlasPacker::setUnitRange(double unitRange) { +void GridAtlasPacker::setUnitRange(msdfgen::Range unitRange) { this->unitRange = unitRange; } -void GridAtlasPacker::setPixelRange(double pxRange) { +void GridAtlasPacker::setPixelRange(msdfgen::Range pxRange) { this->pxRange = pxRange; } @@ -574,6 +602,44 @@ void GridAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) { pxAlignOriginX = alignX, pxAlignOriginY = alignY; } +static Padding makeUniformPadding(double width) { + Padding p; + p.l = width, p.b = width, p.r = width, p.t = width; + return p; +} + +void GridAtlasPacker::setInnerUnitPadding(const Padding &padding) { + innerUnitPadding = padding; +} + +void GridAtlasPacker::setInnerUnitPadding(double uniformPadding) { + innerUnitPadding = makeUniformPadding(uniformPadding); +} + +void GridAtlasPacker::setOuterUnitPadding(const Padding &padding) { + outerUnitPadding = padding; +} + +void GridAtlasPacker::setOuterUnitPadding(double uniformPadding) { + outerUnitPadding = makeUniformPadding(uniformPadding); +} + +void GridAtlasPacker::setInnerPixelPadding(const Padding &padding) { + innerPxPadding = padding; +} + +void GridAtlasPacker::setInnerPixelPadding(double uniformPadding) { + innerPxPadding = makeUniformPadding(uniformPadding); +} + +void GridAtlasPacker::setOuterPixelPadding(const Padding &padding) { + outerPxPadding = padding; +} + +void GridAtlasPacker::setOuterPixelPadding(double uniformPadding) { + outerPxPadding = makeUniformPadding(uniformPadding); +} + void GridAtlasPacker::getDimensions(int &width, int &height) const { width = this->width, height = this->height; } @@ -594,7 +660,7 @@ double GridAtlasPacker::getScale() const { return scale; } -double GridAtlasPacker::getPixelRange() const { +msdfgen::Range GridAtlasPacker::getPixelRange() const { return pxRange+scale*unitRange; } diff --git a/msdf-atlas-gen/GridAtlasPacker.h b/msdf-atlas-gen/GridAtlasPacker.h index 98de280..3d8cb45 100644 --- a/msdf-atlas-gen/GridAtlasPacker.h +++ b/msdf-atlas-gen/GridAtlasPacker.h @@ -40,14 +40,26 @@ public: /// Sets the minimum glyph scale void setMinimumScale(double minScale); /// Sets the unit component of the total distance range - void setUnitRange(double unitRange); + void setUnitRange(msdfgen::Range unitRange); /// Sets the pixel component of the total distance range - void setPixelRange(double pxRange); + void setPixelRange(msdfgen::Range pxRange); /// Sets the miter limit for bounds computation void setMiterLimit(double miterLimit); /// Sets whether each glyph's origin point should stay aligned with the pixel grid void setOriginPixelAlignment(bool align); void setOriginPixelAlignment(bool alignX, bool alignY); + /// Sets the unit component of width of additional padding that is part of each glyph quad + void setInnerUnitPadding(const Padding &padding); + void setInnerUnitPadding(double uniformPadding); + /// Sets the unit component of width of additional padding around each glyph quad + void setOuterUnitPadding(const Padding &padding); + void setOuterUnitPadding(double uniformPadding); + /// Sets the pixel component of width of additional padding that is part of each glyph quad + void setInnerPixelPadding(const Padding &padding); + void setInnerPixelPadding(double uniformPadding); + /// Sets the pixel component of width of additional padding around each glyph quad + void setOuterPixelPadding(const Padding &padding); + void setOuterPixelPadding(double uniformPadding); /// Outputs the atlas's final dimensions void getDimensions(int &width, int &height) const; @@ -60,7 +72,7 @@ public: /// Returns the final glyph scale double getScale() const; /// Returns the final combined pixel range (including converted unit range) - double getPixelRange() const; + msdfgen::Range getPixelRange() const; /// Outputs the position of the origin within each cell, each value is only valid if the origin is fixed in the respective dimension void getFixedOrigin(double &x, double &y); /// Returns true if the explicitly constrained cell dimensions aren't large enough to fit each glyph fully @@ -77,10 +89,12 @@ private: double scale; double minScale; double fixedX, fixedY; - double unitRange; - double pxRange; + msdfgen::Range unitRange; + msdfgen::Range pxRange; double miterLimit; bool pxAlignOriginX, pxAlignOriginY; + Padding innerUnitPadding, outerUnitPadding; + Padding innerPxPadding, outerPxPadding; double scaleMaximizationTolerance; double alignedColumnsBias; bool cutoff; @@ -89,7 +103,7 @@ private: static void raiseToConstraint(int &width, int &height, DimensionsConstraint constraint); double dimensionsRating(int width, int height, bool aligned) const; - msdfgen::Shape::Bounds getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double range) const; + msdfgen::Shape::Bounds getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double outerRange) const; double scaleToFit(GlyphGeometry *glyphs, int count, int cellWidth, int cellHeight, msdfgen::Shape::Bounds &maxBounds, double &maxWidth, double &maxHeight) const; }; diff --git a/msdf-atlas-gen/TightAtlasPacker.cpp b/msdf-atlas-gen/TightAtlasPacker.cpp index f18add2..497a91a 100644 --- a/msdf-atlas-gen/TightAtlasPacker.cpp +++ b/msdf-atlas-gen/TightAtlasPacker.cpp @@ -18,20 +18,29 @@ TightAtlasPacker::TightAtlasPacker() : pxRange(0), miterLimit(0), pxAlignOriginX(false), pxAlignOriginY(false), + innerUnitPadding(), outerUnitPadding(), + innerPxPadding(), outerPxPadding(), scaleMaximizationTolerance(.001) { } int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const { - double range = unitRange+pxRange/scale; // Wrap glyphs into boxes std::vector rectangles; std::vector rectangleGlyphs; rectangles.reserve(count); rectangleGlyphs.reserve(count); + GlyphGeometry::GlyphAttributes attribs = { }; + attribs.scale = scale; + attribs.range = unitRange+pxRange/scale; + attribs.innerPadding = innerUnitPadding+1/scale*innerPxPadding; + attribs.outerPadding = outerUnitPadding+1/scale*outerPxPadding; + attribs.miterLimit = miterLimit; + attribs.pxAlignOriginX = pxAlignOriginX; + attribs.pxAlignOriginY = pxAlignOriginY; for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) { if (!glyph->isWhitespace()) { Rectangle rect = { }; - glyph->wrapBox(scale, range, miterLimit, pxAlignOriginX, pxAlignOriginY); + glyph->wrapBox(attribs); glyph->getBoxSize(rect.w, rect.h); if (rect.w > 0 && rect.h > 0) { rectangles.push_back(rect); @@ -143,11 +152,11 @@ void TightAtlasPacker::setMinimumScale(double minScale) { this->minScale = minScale; } -void TightAtlasPacker::setUnitRange(double unitRange) { +void TightAtlasPacker::setUnitRange(msdfgen::Range unitRange) { this->unitRange = unitRange; } -void TightAtlasPacker::setPixelRange(double pxRange) { +void TightAtlasPacker::setPixelRange(msdfgen::Range pxRange) { this->pxRange = pxRange; } @@ -163,6 +172,44 @@ void TightAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) { pxAlignOriginX = alignX, pxAlignOriginY = alignY; } +static Padding makeUniformPadding(double width) { + Padding p; + p.l = width, p.b = width, p.r = width, p.t = width; + return p; +} + +void TightAtlasPacker::setInnerUnitPadding(const Padding &padding) { + innerUnitPadding = padding; +} + +void TightAtlasPacker::setInnerUnitPadding(double uniformPadding) { + innerUnitPadding = makeUniformPadding(uniformPadding); +} + +void TightAtlasPacker::setOuterUnitPadding(const Padding &padding) { + outerUnitPadding = padding; +} + +void TightAtlasPacker::setOuterUnitPadding(double uniformPadding) { + outerUnitPadding = makeUniformPadding(uniformPadding); +} + +void TightAtlasPacker::setInnerPixelPadding(const Padding &padding) { + innerPxPadding = padding; +} + +void TightAtlasPacker::setInnerPixelPadding(double uniformPadding) { + innerPxPadding = makeUniformPadding(uniformPadding); +} + +void TightAtlasPacker::setOuterPixelPadding(const Padding &padding) { + outerPxPadding = padding; +} + +void TightAtlasPacker::setOuterPixelPadding(double uniformPadding) { + outerPxPadding = makeUniformPadding(uniformPadding); +} + void TightAtlasPacker::getDimensions(int &width, int &height) const { width = this->width, height = this->height; } @@ -171,7 +218,7 @@ double TightAtlasPacker::getScale() const { return scale; } -double TightAtlasPacker::getPixelRange() const { +msdfgen::Range TightAtlasPacker::getPixelRange() const { return pxRange+scale*unitRange; } diff --git a/msdf-atlas-gen/TightAtlasPacker.h b/msdf-atlas-gen/TightAtlasPacker.h index a385262..4c223d0 100644 --- a/msdf-atlas-gen/TightAtlasPacker.h +++ b/msdf-atlas-gen/TightAtlasPacker.h @@ -31,21 +31,33 @@ public: /// Sets the minimum glyph scale void setMinimumScale(double minScale); /// Sets the unit component of the total distance range - void setUnitRange(double unitRange); + void setUnitRange(msdfgen::Range unitRange); /// Sets the pixel component of the total distance range - void setPixelRange(double pxRange); + void setPixelRange(msdfgen::Range pxRange); /// Sets the miter limit for bounds computation void setMiterLimit(double miterLimit); /// Sets whether each glyph's origin point should stay aligned with the pixel grid void setOriginPixelAlignment(bool align); void setOriginPixelAlignment(bool alignX, bool alignY); + /// Sets the unit component of width of additional padding that is part of each glyph quad + void setInnerUnitPadding(const Padding &padding); + void setInnerUnitPadding(double uniformPadding); + /// Sets the unit component of width of additional padding around each glyph quad + void setOuterUnitPadding(const Padding &padding); + void setOuterUnitPadding(double uniformPadding); + /// Sets the pixel component of width of additional padding that is part of each glyph quad + void setInnerPixelPadding(const Padding &padding); + void setInnerPixelPadding(double uniformPadding); + /// Sets the pixel component of width of additional padding around each glyph quad + void setOuterPixelPadding(const Padding &padding); + void setOuterPixelPadding(double uniformPadding); /// Outputs the atlas's final dimensions void getDimensions(int &width, int &height) const; /// Returns the final glyph scale double getScale() const; /// Returns the final combined pixel range (including converted unit range) - double getPixelRange() const; + msdfgen::Range getPixelRange() const; private: int width, height; @@ -53,10 +65,12 @@ private: DimensionsConstraint dimensionsConstraint; double scale; double minScale; - double unitRange; - double pxRange; + msdfgen::Range unitRange; + msdfgen::Range pxRange; double miterLimit; bool pxAlignOriginX, pxAlignOriginY; + Padding innerUnitPadding, outerUnitPadding; + Padding innerPxPadding, outerPxPadding; double scaleMaximizationTolerance; int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const; diff --git a/msdf-atlas-gen/artery-font-export.cpp b/msdf-atlas-gen/artery-font-export.cpp index 2c15ac6..2d23edd 100644 --- a/msdf-atlas-gen/artery-font-export.cpp +++ b/msdf-atlas-gen/artery-font-export.cpp @@ -70,7 +70,7 @@ bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::B fontVariant.imageType = convertImageType(properties.imageType); fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize); if (properties.imageType != ImageType::HARD_MASK) - fontVariant.metrics.distanceRange = REAL(properties.pxRange); + fontVariant.metrics.distanceRange = REAL(properties.pxRange.upper-properties.pxRange.lower); fontVariant.metrics.emSize = REAL(fontMetrics.emSize); fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY); fontVariant.metrics.descender = REAL(fontMetrics.descenderY); 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..bc7a52a 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, "\"zeroDistanceValue\":%.17g,", -metrics.distanceRange.lower/(metrics.distanceRange.upper-metrics.distanceRange.lower)); + } fprintf(f, "\"size\":%.17g,", metrics.size); fprintf(f, "\"width\":%d,", metrics.width); fprintf(f, "\"height\":%d,", metrics.height); 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..c3929bd 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 @@ -239,6 +253,12 @@ static bool strStartsWith(const char *str, const char *prefix) { return true; } +static Padding makeUniformPadding(double width) { + Padding p; + p.l = width, p.b = width, p.r = width, p.t = width; + return p; +} + #ifndef MSDFGEN_DISABLE_VARIABLE_FONTS static msdfgen::FontHandle *loadVarFont(msdfgen::FreetypeHandle *library, const char *filename) { std::string buffer; @@ -264,6 +284,13 @@ static msdfgen::FontHandle *loadVarFont(msdfgen::FreetypeHandle *library, const } #endif +enum class Units { + /// Value is specified in ems + EMS, + /// Value is specified in pixels + PIXELS +}; + struct FontInput { const char *fontFilename; bool variableFont; @@ -279,7 +306,7 @@ struct Configuration { YDirection yDirection; int width, height; double emSize; - double pxRange; + msdfgen::Range pxRange; double angleThreshold; double miterLimit; bool pxAlignOriginX, pxAlignOriginY; @@ -370,13 +397,12 @@ int main(int argc, const char *const *argv) { config.generatorAttributes.config.overlapSupport = !config.preprocessGeometry; config.generatorAttributes.scanlinePass = !config.preprocessGeometry; double minEmSize = 0; - enum { - /// Range specified in ems - RANGE_EM, - /// Range specified in output pixels - RANGE_PIXEL, - } rangeMode = RANGE_PIXEL; - double rangeValue = 0; + Units rangeUnits = Units::PIXELS; + msdfgen::Range rangeValue = 0; + Padding innerPadding = { }; + Padding outerPadding = { }; + Units innerPaddingUnits = Units::EMS; + Units outerPaddingUnits = Units::EMS; PackingStyle packingStyle = PackingStyle::TIGHT; DimensionsConstraint atlasSizeConstraint = DimensionsConstraint::NONE; DimensionsConstraint cellSizeConstraint = DimensionsConstraint::NONE; @@ -577,20 +603,40 @@ int main(int argc, const char *const *argv) { } ARG_CASE("-emrange", 1) { double r; - if (!(parseDouble(r, argv[argPos++]) && r >= 0)) - ABORT("Invalid range argument. Use -emrange 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 +651,70 @@ int main(int argc, const char *const *argv) { ++argPos; continue; } + ARG_CASE("-empadding", 1) { + double p; + if (!parseDouble(p, argv[argPos++])) + ABORT("Invalid padding argument. Use -empadding with a real number."); + innerPaddingUnits = Units::EMS; + innerPadding = makeUniformPadding(p); + continue; + } + ARG_CASE("-pxpadding", 1) { + double p; + if (!parseDouble(p, argv[argPos++])) + ABORT("Invalid padding argument. Use -pxpadding with a real number."); + innerPaddingUnits = Units::PIXELS; + innerPadding = makeUniformPadding(p); + continue; + } + ARG_CASE("-outerempadding", 1) { + double p; + if (!parseDouble(p, argv[argPos++])) + ABORT("Invalid padding argument. Use -outerempadding with a real number."); + outerPaddingUnits = Units::EMS; + outerPadding = makeUniformPadding(p); + continue; + } + ARG_CASE("-outerpxpadding", 1) { + double p; + if (!parseDouble(p, argv[argPos++])) + ABORT("Invalid padding argument. Use -outerpxpadding with a real number."); + outerPaddingUnits = Units::PIXELS; + outerPadding = makeUniformPadding(p); + continue; + } + ARG_CASE("-aempadding", 4) { + double l, b, r, t; + if (!(parseDouble(l, argv[argPos++]) && parseDouble(b, argv[argPos++]) && parseDouble(r, argv[argPos++]) && parseDouble(t, argv[argPos++]))) + ABORT("Invalid padding arguments. Use -aempadding 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 +951,10 @@ int main(int argc, const char *const *argv) { minEmSize = DEFAULT_SIZE; } if (config.imageType == ImageType::HARD_MASK || config.imageType == ImageType::SOFT_MASK) { - rangeMode = RANGE_PIXEL; + rangeUnits = Units::PIXELS; rangeValue = 1; - } else if (rangeValue <= 0) { - rangeMode = RANGE_PIXEL; + } else if (rangeValue.lower == rangeValue.upper) { + rangeUnits = Units::PIXELS; rangeValue = DEFAULT_PIXEL_RANGE; } if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename)) @@ -1052,15 +1162,33 @@ int main(int argc, const char *const *argv) { // Determine final atlas dimensions, scale and range, pack glyphs { - double unitRange = 0, pxRange = 0; - switch (rangeMode) { - case RANGE_EM: - unitRange = rangeValue; + msdfgen::Range emRange = 0, pxRange = 0; + switch (rangeUnits) { + case Units::EMS: + emRange = rangeValue; break; - case RANGE_PIXEL: + case Units::PIXELS: pxRange = rangeValue; break; } + Padding innerEmPadding = { }, outerEmPadding = { }; + Padding innerPxPadding = { }, outerPxPadding = { }; + switch (innerPaddingUnits) { + case Units::EMS: + innerEmPadding = innerPadding; + break; + case Units::PIXELS: + innerPxPadding = innerPadding; + break; + } + switch (outerPaddingUnits) { + case Units::EMS: + outerEmPadding = outerPadding; + break; + case Units::PIXELS: + outerPxPadding = outerPadding; + break; + } bool fixedDimensions = fixedWidth >= 0 && fixedHeight >= 0; bool fixedScale = config.emSize > 0; switch (packingStyle) { @@ -1077,9 +1205,13 @@ int main(int argc, const char *const *argv) { else atlasPacker.setMinimumScale(minEmSize); atlasPacker.setPixelRange(pxRange); - atlasPacker.setUnitRange(unitRange); + atlasPacker.setUnitRange(emRange); atlasPacker.setMiterLimit(config.miterLimit); atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY); + atlasPacker.setInnerUnitPadding(innerEmPadding); + atlasPacker.setOuterUnitPadding(outerEmPadding); + atlasPacker.setInnerPixelPadding(innerPxPadding); + atlasPacker.setOuterPixelPadding(outerPxPadding); if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) { if (remaining < 0) { ABORT("Failed to pack glyphs into atlas."); @@ -1119,9 +1251,13 @@ int main(int argc, const char *const *argv) { else atlasPacker.setMinimumScale(minEmSize); atlasPacker.setPixelRange(pxRange); - atlasPacker.setUnitRange(unitRange); + atlasPacker.setUnitRange(emRange); atlasPacker.setMiterLimit(config.miterLimit); atlasPacker.setOriginPixelAlignment(config.pxAlignOriginX, config.pxAlignOriginY); + atlasPacker.setInnerUnitPadding(innerEmPadding); + atlasPacker.setOuterUnitPadding(outerEmPadding); + atlasPacker.setInnerPixelPadding(innerPxPadding); + atlasPacker.setOuterPixelPadding(outerPxPadding); if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) { if (remaining < 0) { ABORT("Failed to pack glyphs into atlas."); 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); }