diff --git a/.classpath b/.classpath index cd780e3..27ec47e 100644 --- a/.classpath +++ b/.classpath @@ -17,17 +17,12 @@ - - - - - - + @@ -37,12 +32,17 @@ - + - + + + + + + diff --git a/TestExternworddown.svg b/TestExternworddown.svg deleted file mode 100644 index 765cd7e..0000000 --- a/TestExternworddown.svg +++ /dev/null @@ -1,66 +0,0 @@ - - | | + \--> \-> | + bearing.y | + |>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> X + <------------------------> : advance.x + <------------> : sizeTexture.x + <---> : bearing.x + + + + + + _ + *----------------------* ^ ==> calculateFontRealHeight(fontSize); + | | | ^ ==> getAscent(fontSize); + | | | | _ + | /\ | | | ^ ==> Font Height (height of a capital letter) = fontSize + | / \ | | | | + | / \ | | | | + | /------\ | | | | + | / \ | | | | + | / \ | |___|____|________________________==> render line + | | | ^ + | | | | + | | | |==> getDescent(fontSize); + | | | | + *----------------------* | | + + +*/ + public class EsvgFont { /** @@ -57,20 +109,12 @@ public class EsvgFont { Log.error("can not load Node in svg document"); return null; } + + font.horizAdvX = Integer.parseInt(fontElement.getAttribute("horiz-adv-x", "100")); + int nbGlyph = 0; for (XmlNode values : fontElement.getNodes()) { - if (values.getValue().equals("glyph")) { - nbGlyph++; - Log.info("find flyph: " + nbGlyph); - Glyph tmp = Glyph.valueOf(values.toElement()); - if (tmp != null) { - font.glyphs.put(tmp.getUnicodeValue(), tmp); - } - } else if (values.getValue().equals("hkern")) { - // check later ... - } else if (values.getValue().equals("missing-glyph")) { - font.missingGlyph = Glyph.valueOf(values.toElement()); - } else if (values.getValue().equals("font-face")) { + if (values.getValue().equals("font-face")) { if (values instanceof XmlElement fontFace) { font.fontFamily = fontFace.getAttribute("font-family", "unknown"); font.fontStretch = fontFace.getAttribute("font-stretch", "normal"); @@ -103,6 +147,22 @@ public class EsvgFont { int stop = Integer.parseInt(tmpSplit[1], 16); font.unicodeRange = new Pair<>(start, stop); } + } + } + for (XmlNode values : fontElement.getNodes()) { + if (values.getValue().equals("glyph")) { + nbGlyph++; + //Log.info("find flyph: " + nbGlyph); + Glyph tmp = Glyph.valueOf(values.toElement(), font); + if (tmp != null) { + font.glyphs.put(tmp.getUnicodeValue(), tmp); + } + } else if (values.getValue().equals("hkern")) { + // check later ... + } else if (values.getValue().equals("missing-glyph")) { + font.missingGlyph = Glyph.valueOf(values.toElement(), font); + } else if (values.getValue().equals("font-face")) { + // already done ... } else { Log.warning("unsupported node name :" + values.getValue()); } @@ -136,6 +196,7 @@ public class EsvgFont { for (Map.Entry entry : font.glyphs.entrySet()) { if (entry.getValue().getName().equals(g1Splited[iii])) { entry.getValue().addKerning(elementsKerning); + font.hasKerning = true; break; } } @@ -157,9 +218,10 @@ public class EsvgFont { private String fontStretch = "normal"; private int fontWeight = 400; private final Map glyphs = new HashMap<>(); + private boolean hasKerning = false; // The horizontal advance after rendering the glyph in horizontal orientation. If the attribute is not specified, the effect is as if the attribute were set to the value of the font's 'horiz-adv-x' attribute. // Glyph widths are required to be non-negative, even if the glyph is typically rendered right-to-left, as in Hebrew and Arabic scripts. - private final int horizAdvX = 0; + private int horizAdvX = 100; private Glyph missingGlyph = null; private int[] panose1 = { 2, 2, 6, 3, 5, 4, 5, 2, 3, 4 }; private int underlinePosition = -150; @@ -179,6 +241,31 @@ public class EsvgFont { } + public float calculateFontSizeWithHeight(final float fontHeight) { + return fontHeight * this.capHeight / this.unitsPerEm; + } + + public Vector2f calculateRenderOffset(final int fontSize) { + int realSize = calculateFontRealHeight(fontSize); + float deltaY = realSize * this.ascent / this.unitsPerEm; + return new Vector2f(0, deltaY); + } + + public float calculateSclaleFactor(final int fontSize) { + int realSize = calculateFontRealHeight(fontSize); + return (float) realSize / (float) this.unitsPerEm; + } + + public int calculateWidth(final int uVal, final int fontSize) { + Glyph glyph = getGlyph(uVal); + if (glyph == null) { + return 0; + } + int realSize = calculateFontRealHeight(fontSize); + float scale = (float) realSize / (float) this.unitsPerEm; + return (int) (glyph.getHorizAdvX() * scale); + } + public int calculateWidth(final String uVal, final int fontSize) { return calculateWidth(uVal, fontSize, true); } @@ -203,6 +290,20 @@ public class EsvgFont { return out; } + /** + * Get the rendering size of the specific glyph (size rendered in the Weight class). + * @param unicodeValue Unicode value to render + * @param fontSize Size of the font + * @return the size in pixel of the rendering elements + */ + public Vector2i calculateWidthRendering(final Integer unicodeValue, final int fontSize) { + return new Vector2i(calculateWidth(unicodeValue, fontSize), calculateFontRealHeight(fontSize)); + } + + public int getDescent() { + return this.descent; + } + public Glyph getGlyph(final int glyphIndex) { Glyph out = this.glyphs.get(glyphIndex); if (out == null) { @@ -211,13 +312,36 @@ public class EsvgFont { return out; } + public Glyph getGlyphNullIfMissing(final int glyphIndex) { + Glyph out = this.glyphs.get(glyphIndex); + if (out == null) { + return null; + } + return out; + } + + public int getHorizAdvX() { + return this.horizAdvX; + } + + /** + * Get the number of available glyph in the Font + * @return the glyph count. + */ public int getNumGlyphs() { return this.glyphs.size(); } + public float getUnitsPerEm() { + return this.unitsPerEm; + } + + /** + * Check if the font have some kerning data + * @return true if kerning is availlable. + */ public boolean hasKerning() { - // TODO Auto-generated method stub - return false; + return this.hasKerning; } public Weight render(final int uVal, final int fontSize) { @@ -233,7 +357,7 @@ public class EsvgFont { if (model == null) { return null; } - Weight data = glyph.getModel().drawFill(new Vector2i((int) (glyph.getHorizAdvX() * scale), realSize), transform, 8, config); + Weight data = glyph.getModel().drawFill(calculateWidthRendering(uVal, fontSize), transform, 8, config); return data; } @@ -269,7 +393,7 @@ public class EsvgFont { Matrix2x3f transform = Matrix2x3f.createTranslate(new Vector2f(0, -this.descent)).multiply(Matrix2x3f.createScale(scale)); PathModel model = glyph.getModel(); if (model != null) { - Weight redered = model.drawFill(new Vector2i((int) (glyph.getHorizAdvX() * scale), realSize), transform, 8, config); + Weight redered = model.drawFill(calculateWidthRendering((int) uVal, fontSize), transform, 8, config); weight.fusion(redered, offsetWriting, 0); } offsetWriting += advenceXLocal; diff --git a/src/org/atriasoft/esvg/FontCache.java b/src/org/atriasoft/esvg/FontCache.java new file mode 100644 index 0000000..d8b3f60 --- /dev/null +++ b/src/org/atriasoft/esvg/FontCache.java @@ -0,0 +1,86 @@ +package org.atriasoft.esvg; + +import java.util.HashMap; +import java.util.Map; + +import org.atriasoft.etk.ConfigFont; +import org.atriasoft.etk.Configs; +import org.atriasoft.etk.Uri; + +public class FontCache { + private static final Map CACHE_FONTS = new HashMap<>(); + + public static boolean existFont(final String fontName, final boolean bold, final boolean italic) { + ConfigFont fontsConfigs = Configs.getConfigFonts(); + Uri baseUri = fontsConfigs.getFontUri(fontName); + if (baseUri == null) { + return false; + } + if (bold && italic) { + Uri theoricUri = new Uri(baseUri.getGroup(), baseUri.getPath().replace(".svg", "BoldOblique.svg"), baseUri.getproperties()); + return theoricUri.exist(); + } + if (bold && !italic) { + Uri theoricUri = new Uri(baseUri.getGroup(), baseUri.getPath().replace(".svg", "Bold.svg"), baseUri.getproperties()); + return theoricUri.exist(); + } + if (!bold && italic) { + Uri theoricUri = new Uri(baseUri.getGroup(), baseUri.getPath().replace(".svg", "Oblique.svg"), baseUri.getproperties()); + return theoricUri.exist(); + } + if (!bold && !italic) { + Uri theoricUri = baseUri; + return theoricUri.exist(); + } + return false; + } + + public static EsvgFont getFont(final String fontName, final boolean bold, final boolean italic) { + String finalName = fontName + "__" + bold + "__" + italic; + EsvgFont font = FontCache.CACHE_FONTS.get(finalName); + if (font != null) { + return font; + } + // try to find it: + ConfigFont fontsConfigs = Configs.getConfigFonts(); + Uri baseUri = fontsConfigs.getFontUri(fontName); + if (baseUri != null) { + Uri theoricUri = null; + if (bold && italic) { + theoricUri = new Uri(baseUri.getGroup(), baseUri.getPath().replace(".svg", "BoldOblique.svg"), baseUri.getproperties()); + if (!theoricUri.exist()) { + theoricUri = null; + } + } + if (theoricUri == null || (bold && !italic)) { + theoricUri = new Uri(baseUri.getGroup(), baseUri.getPath().replace(".svg", "Bold.svg"), baseUri.getproperties()); + if (!theoricUri.exist()) { + theoricUri = null; + } + } + if (theoricUri == null || (!bold && italic)) { + theoricUri = new Uri(baseUri.getGroup(), baseUri.getPath().replace(".svg", "Oblique.svg"), baseUri.getproperties()); + if (!theoricUri.exist()) { + theoricUri = null; + } + } + if (theoricUri == null || (!bold && !italic)) { + theoricUri = baseUri; + if (!theoricUri.exist()) { + theoricUri = null; + } + } + if (theoricUri != null) { + FontCache.CACHE_FONTS.put(finalName, EsvgFont.load(theoricUri)); + return FontCache.CACHE_FONTS.get(finalName); + } + } + String defaultFontName = Configs.getConfigFonts().getName(); + if (defaultFontName.equals(fontName)) { + return null; + } + return FontCache.getFont(defaultFontName, bold, italic); + } + + private FontCache() {} +} diff --git a/src/org/atriasoft/esvg/GradientUnits.java b/src/org/atriasoft/esvg/GradientUnits.java index 23e4821..d60e384 100644 --- a/src/org/atriasoft/esvg/GradientUnits.java +++ b/src/org/atriasoft/esvg/GradientUnits.java @@ -6,5 +6,5 @@ package org.atriasoft.esvg; * @license MPL v2.0 (see license file) */ public enum GradientUnits { - gradientUnitsobjectBoundingBox, gradientUnitsuserSpaceOnUse + GRADIENT_UNITS_OBJECT_BOUNDING_BOX, GRADIENT_UNITS_USER_SPACE_ON_USE } diff --git a/src/org/atriasoft/esvg/GraphicContext.java b/src/org/atriasoft/esvg/GraphicContext.java new file mode 100644 index 0000000..8f01c01 --- /dev/null +++ b/src/org/atriasoft/esvg/GraphicContext.java @@ -0,0 +1,255 @@ +package org.atriasoft.esvg; + +import org.atriasoft.egami.ImageByteRGBA; +import org.atriasoft.esvg.internal.Log; +import org.atriasoft.esvg.render.PathModel; +import org.atriasoft.etk.Color; +import org.atriasoft.etk.math.Vector2f; +import org.atriasoft.etk.math.Vector2i; + +/** + * Graphic context is used to manage dynamic + * @author heero + * + */ +public class GraphicContext { + private EsvgDocument document; + PaintState paintState; + private PathModel path = null; + private Vector2i size = Vector2i.VALUE_32; + + public GraphicContext() { + clear(); + } + + public void circle(final Vector2f position, final float radius) { + this.document.addElement(new Circle(position, radius, this.paintState.clone())); + } + + public void clear() { + this.document = new EsvgDocument(this.size); + this.paintState = new PaintState(); + } + + /** + * Clear the fill color (disable fill ==> better that set it transparent) + */ + public void clearColorFill() { + this.paintState.fill = null; + } + + /** + * Clear the Stroke color (disable stroke) + */ + public void clearColorStroke() { + this.paintState.clearStroke(); + } + + public void ellipse(final Vector2f center, final Vector2f radius) { + this.document.addElement(new Ellipse(center, radius, this.paintState.clone())); + } + + /** + * Get the fill color. + * @return fill color. + */ + public Color getColorFill() { + return this.paintState.getFill(); + } + + /** + * Get the stroke color. + * @return Stroke color. + */ + public Color getColorStroke() { + return this.paintState.getStroke(); + } + + public CapMode getLineCap() { + return this.paintState.getLineCap(); + } + + public JoinMode getLineJoin() { + return this.paintState.getLineJoin(); + } + + public float getMiterLimit() { + return this.paintState.getMiterLimit(); + } + + public float getOpacity() { + return this.paintState.getOpacity(); + } + + public float getStrokeWidth() { + return this.paintState.getStrokeWidth(); + } + + public void line(final Vector2f origin, final Vector2f destination) { + this.document.addElement(new Line(origin, destination, this.paintState.clone())); + } + + public void lineRel(final Vector2f origin, final Vector2f relativeDestination) { + this.document.addElement(new Line(origin, origin.add(relativeDestination), this.paintState.clone())); + } + + public void pathLine(final Vector2f pos) { + if (this.path == null) { + Log.error("Empty path... Need call pathStart() before"); + return; + } + this.path.lineTo(false, pos); + } + + public void pathLineTo(final Vector2f pos) { + if (this.path == null) { + Log.error("Empty path... Need call pathStart() before"); + return; + } + this.path.lineTo(true, pos); + + } + + public void pathMove(final Vector2f pos) { + if (this.path == null) { + Log.error("Empty path... Need call pathStart() before"); + return; + } + this.path.moveTo(false, pos); + } + + public void pathMoveTo(final Vector2f pos) { + if (this.path == null) { + Log.error("Empty path... Need call pathStart() before"); + return; + } + this.path.moveTo(true, pos); + } + + public void pathStart() { + pathStop(); + this.path = new PathModel(); + } + + public void pathStop() { + if (this.path == null) { + return; + } + this.path.close(false); + this.document.addElement(new Path(this.path, this.paintState.clone())); + this.path = null; + } + + public void pathStopLinked() { + if (this.path == null) { + return; + } + this.path.close(true); + this.document.addElement(new Path(this.path, this.paintState.clone())); + this.path = null; + } + + public void rectangle(final Vector2f position, final Vector2f destination) { + if (this.path != null) { + Log.error("Path not empty ... Need call pathStart() before"); + pathStop(); + } + this.document.addElement(new Rectangle(position, destination.less(position), this.paintState.clone())); + } + + public void rectangleRounded(final Vector2f position, final Vector2f destination, final Vector2f ruound) { + if (this.path != null) { + Log.error("Path not empty ... Need call pathStart() before"); + pathStop(); + } + this.document.addElement(new Rectangle(position, destination.less(position), ruound, this.paintState.clone())); + } + + public void rectangleRoundedWidth(final Vector2f position, final Vector2f width, final Vector2f ruound) { + if (this.path != null) { + Log.error("Path not empty ... Need call pathStart() before"); + pathStop(); + } + this.document.addElement(new Rectangle(position, width, ruound, this.paintState.clone())); + } + + public void rectangleWidth(final Vector2f position, final Vector2f width) { + if (this.path != null) { + Log.error("Path not empty ... Need call pathStart() before"); + pathStop(); + } + this.document.addElement(new Rectangle(position, width, this.paintState.clone())); + } + + public ImageByteRGBA render() { + return null; + } + + public String renderSvg() { + return null; + } + + /** + * set the fill color + * @param color Color to set on fill + * @apiNote use clearFill() if you want to remove drawing of fill + */ + public void setColorFill(final Color color) { + this.paintState.setFill(color); + } + + /** + * set the stroke color + * @param color Color to set on stroke + * @apiNote use clearStroke() if you want to remove drawing of stroke + */ + public void setColorStroke(final Color color) { + this.paintState.setStroke(color); + } + + public void setLineCap(final CapMode lineCap) { + this.paintState.setLineCap(lineCap); + } + + public void setLineJoin(final JoinMode lineJoin) { + this.paintState.setLineJoin(lineJoin); + } + + public void setMiterLimit(final float miterLimit) { + this.paintState.setMiterLimit(miterLimit); + } + + public void setOpacity(final float opacity) { + this.paintState.setOpacity(opacity); + } + + /** + * Set global size of the Graphic context (output render size) + * @param xxx Width of the image + * @param yyy Height of the image + * @apiNote It will clear the current context. + */ + public void setSize(final int xxx, final int yyy) { + setSize(new Vector2i(xxx, yyy)); + } + + /** + * Set global size of the Graphic contexct (output render size) + * @param vector2i New size of the image + * @apiNote It will clear the current context. + */ + private void setSize(final Vector2i size) { + this.size = size; + clear(); + } + + public void setStrokeWidth(final float strokeWidth) { + this.paintState.setStrokeWidth(strokeWidth); + } + + public void text(final Vector2f position, final float height, final String data) { + this.document.addElement(new Text(position, height, data, this.paintState.clone())); + + } + +} diff --git a/src/org/atriasoft/esvg/Line.java b/src/org/atriasoft/esvg/Line.java index 094603b..025234e 100644 --- a/src/org/atriasoft/esvg/Line.java +++ b/src/org/atriasoft/esvg/Line.java @@ -28,6 +28,12 @@ public class Line extends Base { super(parentPaintState); } + public Line(final Vector2f startPos, final Vector2f stopPos, final PaintState parentPaintState) { + super(parentPaintState); + this.startPos = startPos; + this.stopPos = stopPos; + } + private PathModel createPath() { PathModel out = new PathModel(); out.clear(); @@ -52,8 +58,6 @@ public class Line extends Base { PointList listPoints = new PointList(); listPoints = listElement.generateListPoints(level, myRenderer.getInterpolationRecurtionMax(), myRenderer.getInterpolationThreshold()); - //listPoints.applyMatrix(mtx); - SegmentList listSegmentFill = new SegmentList(); SegmentList listSegmentStroke = new SegmentList(); Weight tmpFill = new Weight(); Weight tmpStroke = new Weight(); @@ -74,9 +78,6 @@ public class Line extends Base { } // add on images: myRenderer.print(tmpFill, colorFill, tmpStroke, colorStroke, this.paint.opacity); - //myRenderer.addDebugSegment(listSegmentFill); - //myRenderer.addDebugSegment(listSegmentStroke); - //myRenderer.addDebugSegment(listElement.debugInformation); } @Override diff --git a/src/org/atriasoft/esvg/LinearGradient.java b/src/org/atriasoft/esvg/LinearGradient.java index 5cda7ed..2d0839c 100644 --- a/src/org/atriasoft/esvg/LinearGradient.java +++ b/src/org/atriasoft/esvg/LinearGradient.java @@ -29,7 +29,7 @@ public class LinearGradient extends Base { public SpreadMethod spread = SpreadMethod.PAD; //!< in case of using a single gradient in multiple gradient, the gradient is store in an other element... - public GradientUnits unit = GradientUnits.gradientUnitsobjectBoundingBox; //!< incompatible with href + public GradientUnits unit = GradientUnits.GRADIENT_UNITS_OBJECT_BOUNDING_BOX; //!< incompatible with href public LinearGradient(final PaintState parentPaintState) { super(parentPaintState); @@ -109,9 +109,9 @@ public class LinearGradient extends Base { } contentX = element.getAttribute("gradientUnits", ""); if (contentX.equals("userSpaceOnUse")) { - this.unit = GradientUnits.gradientUnitsuserSpaceOnUse; + this.unit = GradientUnits.GRADIENT_UNITS_USER_SPACE_ON_USE; } else { - this.unit = GradientUnits.gradientUnitsobjectBoundingBox; + this.unit = GradientUnits.GRADIENT_UNITS_OBJECT_BOUNDING_BOX; if (contentX.length() != 0 && contentX != "objectBoundingBox") { Log.error("Parsing error of 'gradientUnits' ==> not suported value: '" + contentX + "' not in : {userSpaceOnUse/objectBoundingBox} use objectBoundingBox"); } diff --git a/src/org/atriasoft/esvg/PaintState.java b/src/org/atriasoft/esvg/PaintState.java index 3fcd809..b1030ea 100644 --- a/src/org/atriasoft/esvg/PaintState.java +++ b/src/org/atriasoft/esvg/PaintState.java @@ -30,6 +30,14 @@ public class PaintState { this.opacity = 1.0f; } + public void clearFill() { + this.fill = new Pair(Color.NONE, ""); + } + + public void clearStroke() { + this.stroke = new Pair(Color.NONE, ""); + } + @Override protected PaintState clone() { PaintState out = new PaintState(); @@ -45,4 +53,60 @@ public class PaintState { return out; } + public Color getFill() { + return this.fill.first; + } + + public CapMode getLineCap() { + return this.lineCap; + } + + public JoinMode getLineJoin() { + return this.lineJoin; + } + + public float getMiterLimit() { + return this.miterLimit; + } + + public float getOpacity() { + return this.opacity; + } + + public Color getStroke() { + return this.stroke.first; + } + + public float getStrokeWidth() { + return this.strokeWidth; + } + + public void setFill(final Color color) { + this.fill = new Pair(color, ""); + } + + public void setLineCap(final CapMode lineCap) { + this.lineCap = lineCap; + } + + public void setLineJoin(final JoinMode lineJoin) { + this.lineJoin = lineJoin; + } + + public void setMiterLimit(final float miterLimit) { + this.miterLimit = miterLimit; + } + + public void setOpacity(final float opacity) { + this.opacity = opacity; + } + + public void setStroke(final Color color) { + this.stroke = new Pair(color, ""); + } + + public void setStrokeWidth(final float strokeWidth) { + this.strokeWidth = strokeWidth; + } + } diff --git a/src/org/atriasoft/esvg/Path.java b/src/org/atriasoft/esvg/Path.java index f41bafb..7f41e18 100644 --- a/src/org/atriasoft/esvg/Path.java +++ b/src/org/atriasoft/esvg/Path.java @@ -316,6 +316,11 @@ public class Path extends Base { super(parentPaintState); } + public Path(final PathModel elements, final PaintState parentPaintState) { + super(parentPaintState); + this.listElement = elements; + } + @Override void display(final int spacing) { this.listElement.display(spacing); diff --git a/src/org/atriasoft/esvg/RadialGradient.java b/src/org/atriasoft/esvg/RadialGradient.java index 2bf7260..73c0af5 100644 --- a/src/org/atriasoft/esvg/RadialGradient.java +++ b/src/org/atriasoft/esvg/RadialGradient.java @@ -28,7 +28,7 @@ public class RadialGradient extends Base { private String href = ""; //!< in case of using a single gradient in multiple gradient, the gradient is store in an other element... private Dimension1D radius = new Dimension1D(50, Distance.POURCENT); //!< Radius of the gradient public SpreadMethod spread = SpreadMethod.PAD; - public GradientUnits unit = GradientUnits.gradientUnitsobjectBoundingBox; + public GradientUnits unit = GradientUnits.GRADIENT_UNITS_OBJECT_BOUNDING_BOX; public RadialGradient(final PaintState parentPaintState) { super(parentPaintState); @@ -114,9 +114,9 @@ public class RadialGradient extends Base { } contentX = element.getAttribute("gradientUnits", ""); if (contentX.equals("userSpaceOnUse")) { - this.unit = GradientUnits.gradientUnitsuserSpaceOnUse; + this.unit = GradientUnits.GRADIENT_UNITS_USER_SPACE_ON_USE; } else { - this.unit = GradientUnits.gradientUnitsobjectBoundingBox; + this.unit = GradientUnits.GRADIENT_UNITS_OBJECT_BOUNDING_BOX; if (contentX.length() != 0 && contentX != "objectBoundingBox") { Log.error("Parsing error of 'gradientUnits' ==> not suported value: '" + contentX + "' not in : {userSpaceOnUse/objectBoundingBox} use objectBoundingBox"); } diff --git a/src/org/atriasoft/esvg/Rectangle.java b/src/org/atriasoft/esvg/Rectangle.java index d84a4bf..88e0c8f 100644 --- a/src/org/atriasoft/esvg/Rectangle.java +++ b/src/org/atriasoft/esvg/Rectangle.java @@ -30,6 +30,19 @@ public class Rectangle extends Base { super(parentPaintState); } + public Rectangle(final Vector2f position, final Vector2f size, final PaintState parentPaintState) { + super(parentPaintState); + this.position = position; + this.size = size; + } + + public Rectangle(final Vector2f position, final Vector2f size, final Vector2f roundedCorner, final PaintState parentPaintState) { + super(parentPaintState); + this.position = position; + this.size = size; + this.roundedCorner = roundedCorner; + } + private PathModel createPath() { PathModel out = new PathModel(); out.clear(); diff --git a/src/org/atriasoft/esvg/Renderer.java b/src/org/atriasoft/esvg/Renderer.java index 7ecbe72..ee459de 100644 --- a/src/org/atriasoft/esvg/Renderer.java +++ b/src/org/atriasoft/esvg/Renderer.java @@ -3,6 +3,7 @@ package org.atriasoft.esvg; import java.util.ArrayList; import java.util.List; +import org.atriasoft.egami.ImageFloatRGBA; import org.atriasoft.esvg.render.DynamicColor; import org.atriasoft.esvg.render.DynamicColorSpecial; import org.atriasoft.esvg.render.Point; @@ -13,7 +14,6 @@ import org.atriasoft.etk.Color; import org.atriasoft.etk.math.FMath; import org.atriasoft.etk.math.Vector2f; import org.atriasoft.etk.math.Vector2i; -import org.atriasoft.etk.util.ArraysTools; /** @file * @author Edouard DUPIN @@ -22,7 +22,7 @@ import org.atriasoft.etk.util.ArraysTools; */ public class Renderer { private static final boolean DEBUG_MODE = false; - protected Color[][] buffer; // for debug + protected ImageFloatRGBA buffer; // for debug protected EsvgDocument document; // for debug private int factor = 1; @@ -81,9 +81,9 @@ public class Renderer { float xpos = coefficient * subSamplingCenterPos + bbb; if (xpos >= 0 && xpos < dynamicSize.x() && yyy >= 0 && yyy < dynamicSize.y()) { if (it.direction == 1.0f) { - this.buffer[yyy][(int) (xpos)] = Color.BLUE; + this.buffer.setColor((int) xpos, yyy, Color.BLUE); } else { - this.buffer[yyy][(int) (xpos)] = Color.DARK_RED; + this.buffer.setColor((int) xpos, yyy, Color.DARK_RED); } } } @@ -119,16 +119,16 @@ public class Renderer { float ypos = coefficient * subSamplingCenterPos + bbb; if (ypos >= 0 && ypos < dynamicSize.y() && xxx >= 0 && xxx < dynamicSize.y()) { if (it.direction == 1.0f) { - this.buffer[(int) (ypos)][xxx] = Color.BLUE; + this.buffer.setColor(xxx, (int) ypos, Color.BLUE); } else { - this.buffer[(int) (ypos)][xxx] = Color.DARK_RED; + this.buffer.setColor(xxx, (int) ypos, Color.DARK_RED); } } } } } - Color[][] getData() { + ImageFloatRGBA getData() { return this.buffer; } @@ -208,11 +208,11 @@ public class Renderer { for (int deltaX = 0; deltaX < this.factor; deltaX++) { int idx = xxx * this.factor + deltaX; int idy = yyy * this.factor + deltaY; - this.buffer[idy][idx] = mergeColor(this.buffer[idy][idx], intermediateColor); + this.buffer.mergeColor(idx, idy, intermediateColor); } } } else { - this.buffer[yyy][xxx] = mergeColor(this.buffer[yyy][xxx], intermediateColor); + this.buffer.mergeColor(xxx, yyy, intermediateColor); } } } @@ -256,11 +256,10 @@ public class Renderer { public void setSize(final Vector2i size) { this.size = size; if (Renderer.DEBUG_MODE) { - this.buffer = new Color[this.size.y()][this.size.x()]; + this.buffer = new ImageFloatRGBA(this.size); } else { - this.buffer = new Color[this.size.y() * this.factor][this.size.x() * this.factor]; + this.buffer = new ImageFloatRGBA(this.size.multiply(this.factor)); } - ArraysTools.fill2(this.buffer, Color.NONE); } } diff --git a/src/org/atriasoft/esvg/RendererFont.java b/src/org/atriasoft/esvg/RendererFont.java deleted file mode 100644 index 1fe45ee..0000000 --- a/src/org/atriasoft/esvg/RendererFont.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.atriasoft.esvg; - -import org.atriasoft.esvg.render.Weight; -import org.atriasoft.etk.Color; -import org.atriasoft.etk.math.FMath; -import org.atriasoft.etk.math.Vector2i; -import org.atriasoft.etk.util.ArraysTools; - -/** @file - * @author Edouard DUPIN - * @copyright 2011, Edouard DUPIN, all right reserved - * @license MPL v2.0 (see license file) - */ -public class RendererFont { - protected float[][] buffer; // for debug - protected EsvgFont document; // for debug - - protected int interpolationRecurtionMax = 10; - - protected float interpolationThreshold = 0.25f; - protected int nbSubScanLine = 8; - protected Vector2i size; - - public RendererFont(final Vector2i size, final EsvgFont document) { - this(size, document, false); - } - - public RendererFont(final Vector2i size, final EsvgFont document, final boolean visualDebug) { - this.size = size; - this.document = document; - setSize(size); - } - - float[][] getData() { - return this.buffer; - } - - int getInterpolationRecurtionMax() { - return this.interpolationRecurtionMax; - } - - float getInterpolationThreshold() { - return this.interpolationThreshold; - } - - int getNumberSubScanLine() { - return this.nbSubScanLine; - } - - Vector2i getSize() { - return this.size; - } - - protected float mergeColor(final float base, final float integration) { - return FMath.avg(0.0f, integration + base, 1.0f); - } - - public void print(final Weight weightFill, final Weight weightStroke, final float opacity) { - // all together - for (int yyy = 0; yyy < this.size.y(); ++yyy) { - for (int xxx = 0; xxx < this.size.x(); ++xxx) { - - Vector2i pos = new Vector2i(xxx, yyy); - float valueFill = weightFill.get(pos); - float valueStroke = weightStroke.get(pos); - // calculate merge of stroke and fill value: - Color intermediateColorFill = Color.NONE; - /* - Color intermediateColorStroke = Color.NONE; - if (colorFill != null && valueFill != 0.0f) { - intermediateColorFill = colorFill.getColor(pos); - intermediateColorFill = intermediateColorFill.withA(intermediateColorFill.a() * valueFill); - } - if (colorStroke != null && valueStroke != 0.0f) { - intermediateColorStroke = colorStroke.getColor(pos); - intermediateColorStroke = intermediateColorStroke.withA(intermediateColorStroke.a() * valueStroke); - } - Color intermediateColor = mergeColor(intermediateColorFill, intermediateColorStroke); - intermediateColor = intermediateColor.withA(intermediateColor.a() * opacity); - this.buffer[yyy][xxx] = mergeColor(this.buffer[yyy][xxx], intermediateColor);*/ - } - } - } - - public void setInterpolationRecurtionMax(final int value) { - this.interpolationRecurtionMax = FMath.avg(1, value, 200); - } - - void setInterpolationThreshold(final float value) { - this.interpolationThreshold = FMath.avg(0.0f, value, 20000.0f); - } - - void setNumberSubScanLine(final int value) { - this.nbSubScanLine = FMath.avg(1, value, 200); - } - - public void setSize(final Vector2i size) { - this.size = size; - this.buffer = new float[this.size.y()][this.size.x()]; - ArraysTools.fill2(this.buffer, 0.0f); - } - -} diff --git a/src/org/atriasoft/esvg/Text.java b/src/org/atriasoft/esvg/Text.java index f3cafd5..cce8ac9 100644 --- a/src/org/atriasoft/esvg/Text.java +++ b/src/org/atriasoft/esvg/Text.java @@ -1,31 +1,271 @@ package org.atriasoft.esvg; +import java.util.ArrayList; +import java.util.List; + +import org.atriasoft.esvg.font.Glyph; import org.atriasoft.esvg.internal.Log; +import org.atriasoft.esvg.render.DynamicColor; +import org.atriasoft.esvg.render.PathModel; +import org.atriasoft.esvg.render.PointList; +import org.atriasoft.esvg.render.SegmentList; +import org.atriasoft.esvg.render.Weight; import org.atriasoft.etk.math.Matrix2x3f; import org.atriasoft.etk.math.Vector2f; import org.atriasoft.etk.util.Dynamic; import org.atriasoft.exml.model.XmlElement; +import org.atriasoft.exml.model.XmlNode; +import org.atriasoft.exml.model.XmlText; /** @file * @author Edouard DUPIN * @copyright 2011, Edouard DUPIN, all right reserved * @license MPL v2.0 (see license file) */ - public class Text extends Base { + private float fontSize = 42; + private Vector2f position = Vector2f.ZERO; + private final List texts = new ArrayList<>(); + public Text(final PaintState parentPaintState) { super(parentPaintState); } + public Text(final Vector2f position, final float fontSize, final String decoratedText, final PaintState parentPaintState) { + super(parentPaintState); + this.position = position; + this.fontSize = fontSize; + this.texts.add(new TextSpan(position, decoratedText, FontProperty.DEFAULT_FONT, parentPaintState.clone())); + } + @Override public void display(final int spacing) { - Log.debug(spacingDist(spacing) + "Text"); + Log.debug(spacingDist(spacing) + "Text : "); + for (TextSpan elem : this.texts) { + Log.debug(spacingDist(spacing + 1) + elem.toString()); + } + } + + @Override + public void draw(final Renderer myRenderer, final Matrix2x3f basicTrans, final int level) { + Log.warning(spacingDist(level) + "DRAW esvg::Text ==> position = " + this.position); + if (this.texts.size() == 0) { + Log.verbose(spacingDist(level + 1) + "No text ..."); + return; + } + boolean withKerning = true; + + for (TextSpan elem : this.texts) { + // get the font or a generic font of the program. + EsvgFont font = FontCache.getFont(elem.fontState().fontName(), elem.fontState().bold(), elem.fontState().italic()); + if (font == null) { + Log.error("Can not get the font :" + elem.fontState()); + return; + } + + int realSize = font.calculateFontRealHeight((int) elem.fontState().fontSize()); + float scale = realSize / font.getUnitsPerEm(); + + float offsetWriting = 0; + int lastValue = 0; + for (char uVal : elem.text().toCharArray()) { + Log.warning(spacingDist(level) + " elem.position = " + elem.position()); + Glyph glyph = font.getGlyph(uVal); + if (glyph == null) { + lastValue = uVal; + continue; + } + if (withKerning) { + //offsetWriting -= glyph.getKerning(lastValue) * scale; + Log.info(" ==> kerning offset = " + (glyph.getKerning(lastValue) * scale)); + lastValue = uVal; + } + + float advenceXLocal = glyph.getHorizAdvX() * scale; + + //Matrix2x3f mtx = this.transformMatrix; + Vector2f tranlate = new Vector2f(elem.position().x() + offsetWriting, elem.position().y() - font.getDescent() * scale); + Log.warning("translate : " + tranlate); + Matrix2x3f translateGlyph = Matrix2x3f.createTranslate(tranlate); + Matrix2x3f scaleGlyph = Matrix2x3f.createScale(new Vector2f(scale, -scale)); + + //Matrix2x3f translateGlyph = Matrix2x3f.createTranslate(tranlate).multiply(Matrix2x3f.createScale(scale)); + //mtx = translateGlyph.multiply(this.transformMatrix); + //mtx = mtx.multiply(translateGlyph); + //mtx = mtx.multiply(basicTrans); + + //Matrix2x3f mtx = this.transformMatrix; + //mtx = mtx.multiply(basicTrans); + + Matrix2x3f mtx = scaleGlyph; + mtx = mtx.multiply(translateGlyph); + mtx = mtx.multiply(this.transformMatrix); + mtx = mtx.multiply(basicTrans); + //Matrix2x3f mtx = this.transformMatrix; + //mtx = mtx.multiply(basicTrans); + + PathModel listElement = glyph.getModel(); + if (listElement != null) { + //-------------------------------------------------- + // -- Generate Fill weight + //-------------------------------------------------- + PointList listPoints = listElement.generateListPoints(level, myRenderer.getInterpolationRecurtionMax(), myRenderer.getInterpolationThreshold()); + DynamicColor colorFill = DynamicColor.createColor(this.paint.fill, mtx); + Weight tmpFill = new Weight(); + // Check if we need to display background + if (colorFill != null) { + SegmentList listSegmentFill = new SegmentList(); + listSegmentFill.createSegmentList(listPoints); + colorFill.setViewPort(listSegmentFill.getViewPort()); + listSegmentFill.applyMatrix(mtx); + // TODO but need check ... listSegmentFill.clearHorizontals(); + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + tmpFill.generate(myRenderer.getSize(), myRenderer.getNumberSubScanLine(), listSegmentFill); + } + + //-------------------------------------------------- + // -- Generate Stroke weight + //-------------------------------------------------- + + Weight tmpStroke = new Weight(); + DynamicColor colorStroke = null; + if (this.paint.strokeWidth > 0.0f) { + colorStroke = DynamicColor.createColor(this.paint.stroke, mtx); + // check if we need to display stroke: + SegmentList listSegmentStroke = new SegmentList(); + listSegmentStroke.createSegmentListStroke(listPoints, this.paint.strokeWidth, this.paint.lineCap, this.paint.lineJoin, this.paint.miterLimit); + colorStroke.setViewPort(listSegmentStroke.getViewPort()); + listSegmentStroke.applyMatrix(mtx); + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + tmpStroke.generate(myRenderer.getSize(), myRenderer.getNumberSubScanLine(), listSegmentStroke); + } + // add on images: + myRenderer.print(tmpFill, colorFill, tmpStroke, colorStroke, this.paint.opacity); + } + offsetWriting += advenceXLocal; + Log.error("offset X =" + offsetWriting + " + " + advenceXLocal + " " + uVal); + } + } } @Override public boolean parseXML(final XmlElement element, final Matrix2x3f parentTrans, final Dynamic sizeMax) { - sizeMax.value = Vector2f.ZERO; - Log.error("NOT IMPLEMENTED"); - return false; + // line must have a minimum size... + this.paint.strokeWidth = 0; + if (element == null) { + return false; + } + parseTransform(element); + parsePaintAttr(element); + + // add the property of the parrent modifications ... + this.transformMatrix = this.transformMatrix.multiply(parentTrans); + + boolean italic = false; + String fontStyle = element.getAttribute("font-style", "normal"); + if ("italic".equals(fontStyle)) { + italic = true; + } else if ("normal".equals(fontStyle)) { + italic = false; + } else { + Log.error("can not parse font-style='" + fontStyle + "' support ['normal', 'italic']"); + } + boolean bold = false; + String fontWeight = element.getAttribute("font-weight", "normal"); + if ("bold".equals(fontWeight)) { + bold = true; + } else if ("normal".equals(fontWeight)) { + bold = false; + } else { + Log.error("can not parse font-weight='" + fontWeight + "' support ['normal', 'bold']"); + } + String fontFamily = element.getAttribute("font-family", "FreeSans"); + if (fontStyle.contains(";")) { + fontFamily = fontFamily.split(";")[0]; + } + Log.info("Get font family: '" + fontFamily + "'"); + + float fontSize = parseLength(element.getAttribute("font-size", "50")); + this.position = Vector2f.ZERO; + + String content = element.getAttribute("x", ""); + if (content.length() != 0) { + this.position = this.position.withX(parseLength(content)); + } + content = element.getAttribute("y", ""); + if (content.length() != 0) { + this.position = this.position.withY(parseLength(content)); + } + + // parse all subElement in the Text + for (XmlNode elem : element.getNodes()) { + if (elem instanceof XmlElement elementSpan && "tspan".equals(elementSpan.getValue())) { + + } else if (elem instanceof XmlText elementText) { + this.texts.add(new TextSpan(this.position, elementText.getValue(), new FontProperty(fontFamily, fontSize, bold, italic), this.paint.clone())); + } else { + Log.warning("not managed element : " + elem); + } + } + + //sizeMax.value = Vector2f.max(this.startPos, this.stopPos); + return true; + } + /* + public Weight render(final String data, final int fontSize, final boolean withKerning) { + int widthOut = calculateWidth(data, fontSize, withKerning); + + int realSize = calculateFontRealHeight(fontSize); + float scale = realSize / (float) this.unitsPerEm; + + Weight weight = new Weight(new Vector2i(widthOut, realSize)); + + int offsetWriting = 0; + int lastValue = 0; + for (char uVal : data.toCharArray()) { + Glyph glyph = getGlyph(uVal); + if (glyph == null) { + lastValue = uVal; + continue; + } + if (withKerning) { + offsetWriting -= glyph.getKerning(lastValue) * scale; + Log.info(" ==> kerning offset = " + (glyph.getKerning(lastValue) * scale)); + lastValue = uVal; + } + + float advenceXLocal = glyph.getHorizAdvX() * scale; + + RenderingConfig config = new RenderingConfig(10, 0.25f, 8); + Matrix2x3f transform = Matrix2x3f.createTranslate(new Vector2f(0, -this.descent)).multiply(Matrix2x3f.createScale(scale)); + PathModel model = glyph.getModel(); + if (model != null) { + Weight redered = model.drawFill(calculateWidthRendering((int) uVal, fontSize), transform, 8, config); + weight.fusion(redered, offsetWriting, 0); + } + offsetWriting += advenceXLocal; + + } + return weight; + } + */ } + +record FontProperty( + String fontName, + float fontSize, + boolean bold, + boolean italic) { + public static final FontProperty DEFAULT_FONT = new FontProperty("FreeSans", 15, false, false); +} + +record TextSpan( + Vector2f position, + String text, + FontProperty fontState, + PaintState paintState) {} + +/** +sample: +*/ diff --git a/src/org/atriasoft/esvg/font/Glyph.java b/src/org/atriasoft/esvg/font/Glyph.java index 14503b0..1fde067 100644 --- a/src/org/atriasoft/esvg/font/Glyph.java +++ b/src/org/atriasoft/esvg/font/Glyph.java @@ -3,6 +3,7 @@ package org.atriasoft.esvg.font; import java.util.ArrayList; import java.util.List; +import org.atriasoft.esvg.EsvgFont; import org.atriasoft.esvg.Path; import org.atriasoft.esvg.internal.Log; import org.atriasoft.esvg.render.PathModel; @@ -12,12 +13,20 @@ public class Glyph { private static final boolean LAZY_MODE = true; public static Glyph valueOf(final XmlElement element) { + return Glyph.valueOf(element, null); + } + + public static Glyph valueOf(final XmlElement element, final EsvgFont font) { if (element == null) { return null; } String name = element.getAttribute("glyph-name", null); Log.verbose("get glyph name = '" + name + "'"); - int horizAdvX = Integer.parseInt(element.getAttribute("horiz-adv-x", "0")); + String tmpValue = element.getAttribute("horiz-adv-x", null); + int horizAdvX = font == null ? 100 : font.getHorizAdvX(); + if (tmpValue != null && tmpValue.length() != 0) { + horizAdvX = Integer.parseInt(tmpValue); + } Log.verbose(" horizAdvX= '" + horizAdvX + "'"); String unicode = element.getAttribute("unicode", null); Log.verbose(" unicode= '" + unicode + "'"); @@ -63,6 +72,7 @@ public class Glyph { private String name; private final String path; private String unicode; + private int unicodeValue; public Glyph(final int horizAdvX, final PathModel model, final String name, final String unicode, final int unicodeValue) { @@ -97,7 +107,7 @@ public class Glyph { } for (Kerning elem : this.kernings) { if (elem.unicode() == unicodeValue) { - Log.info("Get kerning between : '" + (char) this.unicodeValue + "' and '" + (char) unicodeValue + "' => " + elem.offset()); + Log.verbose("Get kerning between : '" + (char) this.unicodeValue + "' and '" + (char) unicodeValue + "' => " + elem.offset()); return elem.offset(); } } diff --git a/src/org/atriasoft/esvg/font/Kerning.java b/src/org/atriasoft/esvg/font/Kerning.java index 5e45969..51f43a1 100644 --- a/src/org/atriasoft/esvg/font/Kerning.java +++ b/src/org/atriasoft/esvg/font/Kerning.java @@ -1,5 +1,36 @@ package org.atriasoft.esvg.font; +/** + * @notindoc + * Kerning properties of one specific Glyph with an other + * + * Without Kerning : + * [pre] + * + * \ / /\ + * \ / / \ + * \ / / \ + * \ / /------\ + * \ / / \ + * \/ / \ + * v v a a + * [/pre] + * + * With Kerning : + * [pre] + * + * \ / /\ + * \ / / \ + * \ / / \ + * \ / /\ + * \ / / \ + * \/ / \ + * v a v a + * [/pre] + * + * @note The "Kerning" is the methode to provide a better display for some string like + * the "VA" has 2 letter that overlap themself. This name Kerning + */ public record Kerning( float offset, int unicode) {} diff --git a/src/org/atriasoft/esvg/render/DynamicColorSpecial.java b/src/org/atriasoft/esvg/render/DynamicColorSpecial.java index 8d0feb6..0005ae2 100644 --- a/src/org/atriasoft/esvg/render/DynamicColorSpecial.java +++ b/src/org/atriasoft/esvg/render/DynamicColorSpecial.java @@ -241,7 +241,7 @@ public class DynamicColorSpecial implements DynamicColor { private Color getColorLinear(final Vector2i pos) { float ratio = 0.0f; - if (this.unit == GradientUnits.gradientUnitsuserSpaceOnUse) { + if (this.unit == GradientUnits.GRADIENT_UNITS_USER_SPACE_ON_USE) { Vector2f vectorBase = this.pos2.less(this.pos1); Vector2f vectorOrtho = new Vector2f(vectorBase.y(), -vectorBase.x()); Vector2f intersec = DynamicColorSpecial.getIntersect(this.pos1, vectorBase, new Vector2f(pos.x(), pos.y()), vectorOrtho); diff --git a/src/org/atriasoft/esvg/render/ElementBezierCurveTo.java b/src/org/atriasoft/esvg/render/ElementBezierCurveTo.java index 3631cef..c9acfc3 100644 --- a/src/org/atriasoft/esvg/render/ElementBezierCurveTo.java +++ b/src/org/atriasoft/esvg/render/ElementBezierCurveTo.java @@ -10,7 +10,7 @@ import org.atriasoft.etk.math.Vector2f; public class ElementBezierCurveTo extends Element { public ElementBezierCurveTo(final boolean relative, final Vector2f pos1, final Vector2f pos) { - super(PathType.bezierCurveTo, relative); + super(PathType.BEZIER_CURVE_TO, relative); this.pos = pos; this.pos1 = pos1; } diff --git a/src/org/atriasoft/esvg/render/ElementBezierSmoothCurveTo.java b/src/org/atriasoft/esvg/render/ElementBezierSmoothCurveTo.java index 4a58711..54cc02b 100644 --- a/src/org/atriasoft/esvg/render/ElementBezierSmoothCurveTo.java +++ b/src/org/atriasoft/esvg/render/ElementBezierSmoothCurveTo.java @@ -10,7 +10,7 @@ import org.atriasoft.etk.math.Vector2f; public class ElementBezierSmoothCurveTo extends Element { ElementBezierSmoothCurveTo(final boolean relative, final Vector2f pos) { - super(PathType.bezierSmoothCurveTo, relative); + super(PathType.BEZIER_SMOOTH_CURVE_TO, relative); this.pos = pos; } diff --git a/src/org/atriasoft/esvg/render/ElementClose.java b/src/org/atriasoft/esvg/render/ElementClose.java index 9a580b5..e80c83f 100644 --- a/src/org/atriasoft/esvg/render/ElementClose.java +++ b/src/org/atriasoft/esvg/render/ElementClose.java @@ -8,11 +8,11 @@ package org.atriasoft.esvg.render; public class ElementClose extends Element { ElementClose() { - super(PathType.close, false); + super(PathType.CLOSE, false); } ElementClose(final boolean relative) { - super(PathType.close, relative); + super(PathType.CLOSE, relative); } @Override diff --git a/src/org/atriasoft/esvg/render/ElementCurveTo.java b/src/org/atriasoft/esvg/render/ElementCurveTo.java index b222017..7bbca2f 100644 --- a/src/org/atriasoft/esvg/render/ElementCurveTo.java +++ b/src/org/atriasoft/esvg/render/ElementCurveTo.java @@ -10,7 +10,7 @@ import org.atriasoft.etk.math.Vector2f; public class ElementCurveTo extends Element { public ElementCurveTo(final boolean relative, final Vector2f pos1, final Vector2f pos2, final Vector2f pos) { - super(PathType.curveTo, relative); + super(PathType.CURVE_TO, relative); this.pos = pos; this.pos1 = pos1; this.pos2 = pos2; diff --git a/src/org/atriasoft/esvg/render/ElementElliptic.java b/src/org/atriasoft/esvg/render/ElementElliptic.java index d0c5512..ed01218 100644 --- a/src/org/atriasoft/esvg/render/ElementElliptic.java +++ b/src/org/atriasoft/esvg/render/ElementElliptic.java @@ -14,7 +14,7 @@ public class ElementElliptic extends Element { public ElementElliptic(final boolean relative, final Vector2f radius, // in this.pos1 final float angle, final boolean largeArcFlag, final boolean sweepFlag, final Vector2f pos) { - super(PathType.elliptic, relative); + super(PathType.ELLIPTIC, relative); this.pos1 = radius; this.pos = pos; this.angle = angle; diff --git a/src/org/atriasoft/esvg/render/ElementLineTo.java b/src/org/atriasoft/esvg/render/ElementLineTo.java index 4b497e0..2d805df 100644 --- a/src/org/atriasoft/esvg/render/ElementLineTo.java +++ b/src/org/atriasoft/esvg/render/ElementLineTo.java @@ -9,7 +9,7 @@ import org.atriasoft.etk.math.Vector2f; public class ElementLineTo extends Element { public ElementLineTo(final boolean relative, final Vector2f pos) { - super(PathType.lineTo, relative); + super(PathType.LINE_TO, relative); this.pos = pos; } diff --git a/src/org/atriasoft/esvg/render/ElementLineToH.java b/src/org/atriasoft/esvg/render/ElementLineToH.java index 99251ed..3df4190 100644 --- a/src/org/atriasoft/esvg/render/ElementLineToH.java +++ b/src/org/atriasoft/esvg/render/ElementLineToH.java @@ -2,7 +2,7 @@ package org.atriasoft.esvg.render; public class ElementLineToH extends Element { public ElementLineToH(final boolean relative, final float poX) { - super(PathType.lineToH, relative); + super(PathType.LINE_TO_H, relative); this.pos = this.pos.withX(poX); } diff --git a/src/org/atriasoft/esvg/render/ElementLineToV.java b/src/org/atriasoft/esvg/render/ElementLineToV.java index fc7bbd0..c146fb5 100644 --- a/src/org/atriasoft/esvg/render/ElementLineToV.java +++ b/src/org/atriasoft/esvg/render/ElementLineToV.java @@ -2,7 +2,7 @@ package org.atriasoft.esvg.render; public class ElementLineToV extends Element { public ElementLineToV(final boolean relative, final float posY) { - super(PathType.lineToV, relative); + super(PathType.LINE_TO_V, relative); this.pos = this.pos.withY(posY); } diff --git a/src/org/atriasoft/esvg/render/ElementMoveTo.java b/src/org/atriasoft/esvg/render/ElementMoveTo.java index ecd3904..d9bbfea 100644 --- a/src/org/atriasoft/esvg/render/ElementMoveTo.java +++ b/src/org/atriasoft/esvg/render/ElementMoveTo.java @@ -9,7 +9,7 @@ import org.atriasoft.etk.math.Vector2f; public class ElementMoveTo extends Element { public ElementMoveTo(final boolean relative, final Vector2f pos) { - super(PathType.moveTo, relative); + super(PathType.MOVE_TO, relative); this.pos = pos; } diff --git a/src/org/atriasoft/esvg/render/ElementSmoothCurveTo.java b/src/org/atriasoft/esvg/render/ElementSmoothCurveTo.java index b536911..da7a4ab 100644 --- a/src/org/atriasoft/esvg/render/ElementSmoothCurveTo.java +++ b/src/org/atriasoft/esvg/render/ElementSmoothCurveTo.java @@ -9,7 +9,7 @@ import org.atriasoft.etk.math.Vector2f; public class ElementSmoothCurveTo extends Element { public ElementSmoothCurveTo(final boolean relative, final Vector2f pos2, final Vector2f pos) { - super(PathType.moveTo, relative); + super(PathType.MOVE_TO, relative); this.pos = pos; this.pos2 = pos2; diff --git a/src/org/atriasoft/esvg/render/ElementStop.java b/src/org/atriasoft/esvg/render/ElementStop.java index d679939..a7c724c 100644 --- a/src/org/atriasoft/esvg/render/ElementStop.java +++ b/src/org/atriasoft/esvg/render/ElementStop.java @@ -2,7 +2,7 @@ package org.atriasoft.esvg.render; public class ElementStop extends Element { ElementStop() { - super(PathType.stop, false); + super(PathType.STOP, false); } @Override diff --git a/src/org/atriasoft/esvg/render/PathModel.java b/src/org/atriasoft/esvg/render/PathModel.java index e0038d8..ee46557 100644 --- a/src/org/atriasoft/esvg/render/PathModel.java +++ b/src/org/atriasoft/esvg/render/PathModel.java @@ -155,7 +155,7 @@ public class PathModel { } Log.verbose(PathModel.spacingDist(level + 1) + " Draw : " + it.toString()); switch (it.getType()) { - case stop: + case STOP: if (tmpListPoint.size() != 0) { if (tmpListPoint.size() == 0) { Log.warning(PathModel.spacingDist(level + 1) + " Request path stop of not starting path ..."); @@ -168,7 +168,7 @@ public class PathModel { lastAngle = Vector2f.ZERO; // nothing alse to do ... break; - case close: + case CLOSE: if (tmpListPoint.size() != 0) { if (tmpListPoint.size() == 0) { Log.warning(PathModel.spacingDist(level + 1) + " Request path close of not starting path ..."); @@ -188,7 +188,7 @@ public class PathModel { lastAngle = Vector2f.ZERO; // nothing alse to do ... break; - case moveTo: + case MOVE_TO: // stop last path if (tmpListPoint.size() != 0) { tmpListPoint.get(tmpListPoint.size() - 1).setEndPath(); @@ -203,7 +203,7 @@ public class PathModel { tmpListPoint.add(new Point(lastPosition, PointType.start)); lastAngle = lastPosition; break; - case lineTo: + case LINE_TO: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.start)); @@ -215,7 +215,7 @@ public class PathModel { tmpListPoint.add(new Point(lastPosition, PointType.join)); lastAngle = lastPosition; break; - case lineToH: + case LINE_TO_H: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.start)); @@ -227,7 +227,7 @@ public class PathModel { tmpListPoint.add(new Point(lastPosition, PointType.join)); lastAngle = lastPosition; break; - case lineToV: + case LINE_TO_V: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.start)); @@ -239,7 +239,7 @@ public class PathModel { tmpListPoint.add(new Point(lastPosition, PointType.join)); lastAngle = lastPosition; break; - case curveTo: + case CURVE_TO: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.join)); @@ -256,7 +256,7 @@ public class PathModel { lastAngle = pos2; } break; - case smoothCurveTo: + case SMOOTH_CURVE_TO: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.join)); @@ -274,7 +274,7 @@ public class PathModel { lastAngle = pos2; } break; - case bezierCurveTo: + case BEZIER_CURVE_TO: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.join)); @@ -293,7 +293,7 @@ public class PathModel { lastAngle = tmp1; } break; - case bezierSmoothCurveTo: + case BEZIER_SMOOTH_CURVE_TO: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.join)); @@ -312,7 +312,7 @@ public class PathModel { lastAngle = tmp1; } break; - case elliptic: + case ELLIPTIC: // If no previous point, we need to create the last point has start ... if (tmpListPoint.size() == 0) { tmpListPoint.add(new Point(lastPosition, PointType.join)); diff --git a/src/org/atriasoft/esvg/render/PathType.java b/src/org/atriasoft/esvg/render/PathType.java index d50165e..8532f4a 100644 --- a/src/org/atriasoft/esvg/render/PathType.java +++ b/src/org/atriasoft/esvg/render/PathType.java @@ -1,5 +1,5 @@ package org.atriasoft.esvg.render; public enum PathType { - bezierCurveTo, bezierSmoothCurveTo, close, curveTo, elliptic, lineTo, lineToH, lineToV, moveTo, smoothCurveTo, stop, + BEZIER_CURVE_TO, BEZIER_SMOOTH_CURVE_TO, CLOSE, CURVE_TO, ELLIPTIC, LINE_TO, LINE_TO_H, LINE_TO_V, MOVE_TO, SMOOTH_CURVE_TO, STOP, } diff --git a/src/org/atriasoft/esvg/render/SegmentList.java b/src/org/atriasoft/esvg/render/SegmentList.java index 431df19..680e5b7 100644 --- a/src/org/atriasoft/esvg/render/SegmentList.java +++ b/src/org/atriasoft/esvg/render/SegmentList.java @@ -1,6 +1,7 @@ package org.atriasoft.esvg.render; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.atriasoft.etk.math.Vector2f; @@ -34,11 +35,6 @@ public class SegmentList { public SegmentList() {} public void addSegment(final Point pos0, final Point pos1) { - // Skip horizontal Segments - if (pos0.pos.y() == pos1.pos.y()) { - // remove /0 operation - return; - } this.data.add(new Segment(pos0.pos, pos1.pos)); } @@ -64,6 +60,17 @@ public class SegmentList { } } + public void clearHorizontals() { + // TODO Auto-generated method stub + Iterator itr = this.data.iterator(); + while (itr.hasNext()) { + Segment seg = itr.next(); + if (seg.p0.y() == seg.p1.y()) { + itr.remove(); + } + } + } + public void createSegmentList(final PointList listPoint) { for (List it : listPoint.data) { // Build Segments diff --git a/test/src/test/atriasoft/esvg/ConfigTest.java b/test/src/test/atriasoft/esvg/ConfigTest.java index e0f65cf..b0733bf 100644 --- a/test/src/test/atriasoft/esvg/ConfigTest.java +++ b/test/src/test/atriasoft/esvg/ConfigTest.java @@ -1,7 +1,7 @@ package test.atriasoft.esvg; -import java.awt.image.BufferedImage; - +import org.atriasoft.egami.ImageFloatRGBA; +import org.atriasoft.egami.Image; import org.atriasoft.esvg.EsvgDocument; import org.atriasoft.esvg.internal.Log; import org.atriasoft.esvg.render.Weight; @@ -9,52 +9,43 @@ import org.atriasoft.etk.Color; import org.atriasoft.etk.Uri; import org.atriasoft.etk.math.Vector2i; -import com.pngencoder.PngEncoder; +import org.atriasoft.pngencoder.PngEncoder; public class ConfigTest { public static final String BASE_PATH = "./testResult/";//"~/dev/workspace-game/atriasoft/esvg/"; public static final boolean VISUAL_DEBUG = true; public static void generateAnImage(final EsvgDocument doc, final Uri uri) { - Color[][] data = doc.renderImageFloatRGBA(null, ConfigTest.VISUAL_DEBUG); - if (data.length == 0) { + Image data = doc.renderImageFloatRGBA(null, ConfigTest.VISUAL_DEBUG); + if (data == null) { Log.critical("No data generated ..."); } - BufferedImage bufferedImage = new BufferedImage(data[0].length, data.length, BufferedImage.TYPE_INT_ARGB); - for (int yyy = 0; yyy < data.length; yyy++) { - for (int xxx = 0; xxx < data[yyy].length; xxx++) { - Color elem = data[yyy][xxx]; - int tmpColor = ((int) (elem.a() * 255.0f) << 24) + ((int) (elem.r() * 255.0f) << 16) + ((int) (elem.g() * 255.0f) << 8) + ((int) (elem.b() * 255.0f)); - bufferedImage.setRGB(xxx, yyy, tmpColor); - } - } Log.warning("Save file in " + uri.getPath()); - byte[] outElem = new PngEncoder().withBufferedImage(bufferedImage).withCompressionLevel(9).toBytes(); + byte[] outElem = new PngEncoder().withBufferedImage(data).withCompressionLevel(9).toBytes(); Log.warning("outsize = " + outElem.length); - new PngEncoder().withBufferedImage(bufferedImage).withCompressionLevel(9).toFile(uri.getPath()); + new PngEncoder().withBufferedImage(data).withCompressionLevel(9).toFile(uri.getPath()); } public static void generateAnImage(final Weight weight, final Uri uri) { - BufferedImage bufferedImage = new BufferedImage(weight.getWidth() + 2, weight.getHeight() + 2, BufferedImage.TYPE_INT_ARGB); + ImageFloatRGBA image = new ImageFloatRGBA(weight.getWidth() + 2, weight.getHeight() + 2); for (int yyy = 0; yyy < weight.getHeight(); yyy++) { for (int xxx = 0; xxx < weight.getWidth(); xxx++) { float elem = weight.get(new Vector2i(xxx, yyy)); - int tmpColor = (0xFF << 24) + ((int) (elem * 255.0f) << 16) + ((int) (elem * 255.0f) << 8) + ((int) (elem * 255.0f)); - bufferedImage.setRGB(xxx + 1, 1 + weight.getHeight() - 1 - yyy, tmpColor); + image.setColorFloat(xxx, yyy, 1.0f, 1.0f, 1.0f, elem); } } for (int yyy = 0; yyy < weight.getHeight() + 2; yyy++) { - bufferedImage.setRGB(0, yyy, 0xFFFF0000); - bufferedImage.setRGB(weight.getWidth() + 1, yyy, 0xFFFF0000); + image.setColor(0, yyy, Color.ORANGE); + image.setColor(weight.getWidth() + 1, yyy, Color.ORANGE); } for (int xxx = 0; xxx < weight.getWidth() + 2; xxx++) { - bufferedImage.setRGB(xxx, 0, 0xFFFF0000); - bufferedImage.setRGB(xxx, weight.getHeight() + 1, 0xFFFF0000); + image.setColor(xxx, 0, Color.ORANGE); + image.setColor(xxx, weight.getHeight() + 1, Color.ORANGE); } Log.warning("Save file in " + uri.getPath()); - byte[] outElem = new PngEncoder().withBufferedImage(bufferedImage).withCompressionLevel(9).toBytes(); + byte[] outElem = new PngEncoder().withBufferedImage(image).withCompressionLevel(9).toBytes(); Log.warning("outsize = " + outElem.length); - new PngEncoder().withBufferedImage(bufferedImage).withCompressionLevel(9).toFile(uri.getPath()); + new PngEncoder().withBufferedImage(image).withCompressionLevel(9).toFile(uri.getPath()); } private ConfigTest() {} diff --git a/test/src/test/atriasoft/esvg/TestText.java b/test/src/test/atriasoft/esvg/TestText.java new file mode 100644 index 0000000..6c6695a --- /dev/null +++ b/test/src/test/atriasoft/esvg/TestText.java @@ -0,0 +1,117 @@ +package test.atriasoft.esvg; + +import org.atriasoft.esvg.Esvg; +import org.atriasoft.esvg.EsvgDocument; +import org.junit.jupiter.api.Test; +import org.atriasoft.etk.Uri; + +class TestText { + @Test + public void testTextBase() { + Esvg.init(); + //@formatter:off + String data = "\n" + + "\n" + + "\n" + + " \n" + + " Hello\n" + + " Hello Bold\n" + + " Hello Italic\n" + + " Hello Bold Italic\n" + + " Hello Rotated\n" + + " \n" + + "\n"; + //@formatter:on + EsvgDocument doc = new EsvgDocument(); + doc.parse(data); + Uri.writeAll(new Uri(ConfigTest.BASE_PATH + "TestTextFull.svg"), data.replace("'", "\"")); + ConfigTest.generateAnImage(doc, new Uri(ConfigTest.BASE_PATH + "TestTextFull.png")); + } + + public void testTextFull() { + //@formatter:off + String data = "\n" + + "\n" + + "\n" + + " \n" + + " Hellosqmlksd\n" + + " Hello\n" + + " Hello Bold\n" + + " Hello Italic\n" + + " Hello Bold Italic\n" + + " \n" + + "\n"; + //@formatter:on + EsvgDocument doc = new EsvgDocument(); + doc.parse(data); + Uri.writeAll(new Uri(ConfigTest.BASE_PATH + "TestTextFull.svg"), data.replace("'", "\"")); + ConfigTest.generateAnImage(doc, new Uri(ConfigTest.BASE_PATH + "TestTextFull.png")); + } +}