diff options
author | Sven Gothel <[email protected]> | 2023-02-15 04:17:41 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-02-15 04:17:41 +0100 |
commit | 91f5d1019c59f56654f40ce18d70d15117b7f302 (patch) | |
tree | ffb2716314cd24e546912a79e1ebd3dde8a77a1e /src/jogl/classes | |
parent | 250832f581d8acccf0b62259e2ec08adb259be0e (diff) |
Graph TextRegionUtil: Move ShapeVisitor to OutlineShape.Visitor and processString(..) to Font, cleaning up ..
Further having Font.processString() return the AABBox of the whole string's 'laidout' OutlineShapes,
which is used for (debug) Font.getPointsBounds2(..) just to validate the coordinated with
the Glyph based AABBox of Font.getPointsBounds(..).
Static TextRegionUtil.drawString(..) no more require to pass the temp AffineTransform instances (ugly).
Diffstat (limited to 'src/jogl/classes')
9 files changed, 198 insertions, 128 deletions
diff --git a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java index e14266c05..1369061d3 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java +++ b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java @@ -1,5 +1,5 @@ /** - * Copyright 2010 JogAmp Community. All rights reserved. + * Copyright 2010-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: @@ -113,6 +113,18 @@ public final class OutlineShape implements Comparable<OutlineShape> { } } + /** + * General purpose {@link OutlineShape} visitor. + */ + public static interface Visitor { + /** + * Visiting the given {@link OutlineShape} with it's corresponding {@link AffineTransform}. + * @param shape may be used as is, otherwise a copy shall be made if intended to be modified. + * @param t may be used immediately as is, otherwise a copy shall be made if stored. + */ + public void visit(final OutlineShape shape, final AffineTransform t); + } + /** Initial {@link #getSharpness()} value, which can be modified via {@link #setSharpness(float)}. */ public static final float DEFAULT_SHARPNESS = 0.5f; diff --git a/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java b/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java index f9db8c655..714be1998 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java +++ b/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java @@ -1,5 +1,5 @@ /** - * Copyright 2014 JogAmp Community. All rights reserved. + * Copyright 2014-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: @@ -33,10 +33,10 @@ import java.util.Iterator; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLException; +import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.Region; import com.jogamp.graph.font.Font; -import com.jogamp.graph.font.Font.Glyph; import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.Vertex.Factory; import com.jogamp.graph.geom.plane.AffineTransform; @@ -55,18 +55,6 @@ public class TextRegionUtil { this.renderModes = renderModes; } - public static interface ShapeVisitor { - /** - * Visiting the given {@link OutlineShape} with it's corresponding {@link AffineTransform}. - * <p> - * The shape is in font em-size [0..1]. - * </p> - * @param shape may be used as is, otherwise a copy shall be made if intended to be modified. - * @param t may be used immediately as is, otherwise a copy shall be made if stored. - */ - public void visit(final OutlineShape shape, final AffineTransform t); - } - public static int getCharCount(final String s, final char c) { final int sz = s.length(); int count = 0; @@ -79,65 +67,6 @@ public class TextRegionUtil { } /** - * Visit each {@link Font.Glyph}'s {@link OutlineShape} with the given {@link ShapeVisitor} - * additionally passing the progressed {@link AffineTransform}. - * <p> - * The produced shapes are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. - * </p> - * @param visitor - * @param transform optional given transform - * @param font the target {@link Font} - * @param str string text - * @param temp1 temporary AffineTransform storage, mandatory - * @param temp2 temporary AffineTransform storage, mandatory - */ - public static void processString(final ShapeVisitor visitor, final AffineTransform transform, - final Font font, final CharSequence str, - final AffineTransform temp1, final AffineTransform temp2) { - final int charCount = str.length(); - - // region.setFlipped(true); - final float lineHeight = font.getLineHeight(); - - float y = 0; - float advanceTotal = 0; - Font.Glyph left_glyph = null; - - for(int i=0; i< charCount; i++) { - final char character = str.charAt(i); - if( '\n' == character ) { - y -= lineHeight; - advanceTotal = 0; - left_glyph = null; - } else if (character == ' ') { - advanceTotal += font.getAdvanceWidth(Glyph.ID_SPACE); - left_glyph = null; - } else { - // reset transform - if( null != transform ) { - temp1.setTransform(transform); - } else { - temp1.setToIdentity(); - } - final int glyph_id = font.getGlyphID(character); - final Font.Glyph glyph = font.getGlyph(glyph_id); - final OutlineShape glyphShape = glyph.getShape(); - if( null == glyphShape ) { - left_glyph = null; - continue; - } - if( null != left_glyph ) { - advanceTotal += left_glyph.getKerning(glyph_id); - } - temp1.translate(advanceTotal, y, temp2); - visitor.visit(glyphShape, temp1); - advanceTotal += glyph.getAdvance(); - left_glyph = glyph; - } - } - } - - /** * Add the string in 3D space w.r.t. the font in font em-size [0..1] at the end of the {@link GLRegion}. * <p> * The shapes added to the GLRegion are in font em-size [0..1]. @@ -149,16 +78,17 @@ public class TextRegionUtil { * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param temp1 temporary AffineTransform storage, mandatory * @param temp2 temporary AffineTransform storage, mandatory + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] OutlineShape into account. */ - public static void addStringToRegion(final GLRegion region, final Factory<? extends Vertex> vertexFactory, - final Font font, final CharSequence str, final float[] rgbaColor, - final AffineTransform temp1, final AffineTransform temp2) { - final ShapeVisitor visitor = new ShapeVisitor() { + public static AABBox addStringToRegion(final GLRegion region, final Factory<? extends Vertex> vertexFactory, + final Font font, final CharSequence str, final float[] rgbaColor, + final AffineTransform temp1, final AffineTransform temp2) { + final OutlineShape.Visitor visitor = new OutlineShape.Visitor() { @Override public final void visit(final OutlineShape shape, final AffineTransform t) { region.addOutlineShape(shape, t, region.hasColorChannel() ? rgbaColor : null); } }; - processString(visitor, null, font, str, temp1, temp2); + return font.processString(visitor, null, str, temp1, temp2); } /** @@ -176,11 +106,12 @@ public class TextRegionUtil { * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. * The actual used scample-count is written back when msaa-rendering is enabled, otherwise the store is untouched. + * @return the bounding box of the given string from the produced and rendered GLRegion * @throws Exception if TextRenderer not initialized */ - public void drawString3D(final GL2ES2 gl, - final RegionRenderer renderer, final Font font, final CharSequence str, - final float[] rgbaColor, final int[/*1*/] sampleCount) { + public AABBox drawString3D(final GL2ES2 gl, + final RegionRenderer renderer, final Font font, final CharSequence str, + final float[] rgbaColor, final int[/*1*/] sampleCount) { if( !renderer.isInitialized() ) { throw new GLException("TextRendererImpl01: not initialized!"); } @@ -191,7 +122,10 @@ public class TextRegionUtil { addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, tempT1, tempT2); addCachedRegion(gl, font, str, special, region); } + final AABBox res = new AABBox(); + res.copy(region.getBounds()); region.draw(gl, renderer, sampleCount); + return res; } /** @@ -202,7 +136,7 @@ public class TextRegionUtil { * <p> * In case of a multisampling region renderer, i.e. {@link Region#VBAA_RENDERING_BIT}, recreating the {@link GLRegion} * is a huge performance impact. - * In such case better use {@link #drawString3D(GL2ES2, GLRegion, RegionRenderer, Font, CharSequence, float[], int[], AffineTransform, AffineTransform)} + * In such case better use {@link #drawString3D(GL2ES2, GLRegion, RegionRenderer, Font, CharSequence, float[], int[])} * instead. * </p> * @param gl the current GL state @@ -212,21 +146,24 @@ public class TextRegionUtil { * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. * The actual used scample-count is written back when msaa-rendering is enabled, otherwise the store is untouched. - * @param temp1 temporary AffineTransform storage, mandatory - * @param temp2 temporary AffineTransform storage, mandatory * @throws Exception if TextRenderer not initialized + * @return the bounding box of the given string from the produced and rendered GLRegion */ - public static void drawString3D(final GL2ES2 gl, final int renderModes, - final RegionRenderer renderer, final Font font, final CharSequence str, - final float[] rgbaColor, final int[/*1*/] sampleCount, final AffineTransform temp1, - final AffineTransform temp2) { + public static AABBox drawString3D(final GL2ES2 gl, final int renderModes, + final RegionRenderer renderer, final Font font, final CharSequence str, + final float[] rgbaColor, final int[/*1*/] sampleCount) { if(!renderer.isInitialized()){ throw new GLException("TextRendererImpl01: not initialized!"); } + final AffineTransform temp1 = new AffineTransform(); + final AffineTransform temp2 = new AffineTransform(); final GLRegion region = GLRegion.create(renderModes, null); addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, temp1, temp2); + final AABBox res = new AABBox(); + res.copy(region.getBounds()); region.draw(gl, renderer, sampleCount); region.destroy(gl); + return res; } /** @@ -241,20 +178,23 @@ public class TextRegionUtil { * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. * The actual used scample-count is written back when msaa-rendering is enabled, otherwise the store is untouched. - * @param temp1 temporary AffineTransform storage, mandatory - * @param temp2 temporary AffineTransform storage, mandatory + * @return the bounding box of the given string from the produced and rendered GLRegion * @throws Exception if TextRenderer not initialized */ - public static void drawString3D(final GL2ES2 gl, final GLRegion region, final RegionRenderer renderer, - final Font font, final CharSequence str, final float[] rgbaColor, - final int[/*1*/] sampleCount, final AffineTransform temp1, - final AffineTransform temp2) { + public static AABBox drawString3D(final GL2ES2 gl, final GLRegion region, final RegionRenderer renderer, + final Font font, final CharSequence str, final float[] rgbaColor, + final int[/*1*/] sampleCount) { if(!renderer.isInitialized()){ throw new GLException("TextRendererImpl01: not initialized!"); } + final AffineTransform temp1 = new AffineTransform(); + final AffineTransform temp2 = new AffineTransform(); region.clear(gl); addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, temp1, temp2); + final AABBox res = new AABBox(); + res.copy(region.getBounds()); region.draw(gl, renderer, sampleCount); + return res; } /** diff --git a/src/jogl/classes/com/jogamp/graph/font/Font.java b/src/jogl/classes/com/jogamp/graph/font/Font.java index 1b852436f..36f00f628 100644 --- a/src/jogl/classes/com/jogamp/graph/font/Font.java +++ b/src/jogl/classes/com/jogamp/graph/font/Font.java @@ -1,5 +1,5 @@ /** -// * Copyright 2010 JogAmp Community. All rights reserved. + * Copyright 2010-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: @@ -294,7 +294,15 @@ public interface Font { AABBox getMetricBounds(final CharSequence string); /** - * Return the bounding box by taking each glyph's font-unit sized bounding box into account. + * Return the bounding box by taking each glyph's font em-sized bounding box into account. + * @param transform optional given transform + * @param string string text + * @return the bounding box of the given string in font em-size [0..1] + */ + AABBox getPointsBounds(final AffineTransform transform, final CharSequence string); + + /** + * Return the bounding box by taking each glyph's font-units sized bounding box into account. * @param transform optional given transform * @param string string text * @return the bounding box of the given string in font-units [0..1] @@ -302,16 +310,49 @@ public interface Font { AABBox getPointsBoundsFU(final AffineTransform transform, final CharSequence string); /** - * Return the bounding box by taking each glyph's font em-sized bounding box into account. + * Return the bounding box of the given string by taking each glyph's font em-sized OutlineShape into account. * @param transform optional given transform * @param string string text - * @return the bounding box of the given string in font em-size [0..1] + * @return the bounding box of the given string in font-units [0..1] */ - AABBox getPointsBounds(final AffineTransform transform, final CharSequence string); + AABBox getPointsBounds2(final AffineTransform transform, final CharSequence string); boolean isPrintableChar(final char c); - /** Shall return {@link #getFullFamilyName()} */ + /** + * Visit each {@link Glyph}'s {@link OutlineShape} of the string with the {@link OutlineShape.Visitor} + * while passing the progressed {@link AffineTransform}. + * <p> + * The produced shapes are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. + * </p> + * @param visitor handling each glyph's outline shape in font em-size [0..1] and the given {@link AffineTransform} + * @param transform optional given transform + * @param font the target {@link Font} + * @param string string text + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] OutlineShape into account. + */ + AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string); + + /** + * Visit each {@link Glyph}'s {@link OutlineShape} of the string with the {@link OutlineShape.Visitor} + * while passing the progressed {@link AffineTransform}. + * <p> + * The produced shapes are in font em-size [0..1], but can be adjusted with the given transform, progressed and passed to the visitor. + * </p> + * @param visitor handling each glyph's outline shape in font em-size [0..1] and the given {@link AffineTransform} + * @param transform optional given transform + * @param font the target {@link Font} + * @param string string text + * @param temp1 temporary AffineTransform storage, mandatory + * @param temp2 temporary AffineTransform storage, mandatory + * @return the bounding box of the given string by taking each glyph's font em-sized [0..1] OutlineShape into account. + */ + AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string, + final AffineTransform temp1, final AffineTransform temp2); + + /** Returns {@link #getFullFamilyName()} */ @Override public String toString(); diff --git a/src/jogl/classes/com/jogamp/graph/font/FontFactory.java b/src/jogl/classes/com/jogamp/graph/font/FontFactory.java index 5c317bd2b..ac42274f6 100644 --- a/src/jogl/classes/com/jogamp/graph/font/FontFactory.java +++ b/src/jogl/classes/com/jogamp/graph/font/FontFactory.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2010-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: diff --git a/src/jogl/classes/com/jogamp/graph/font/FontSet.java b/src/jogl/classes/com/jogamp/graph/font/FontSet.java index 60a16b241..607aab433 100644 --- a/src/jogl/classes/com/jogamp/graph/font/FontSet.java +++ b/src/jogl/classes/com/jogamp/graph/font/FontSet.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2010-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java index b45e2c215..eeee14365 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: @@ -307,46 +307,53 @@ class TypecastFont implements Font { @Override public AABBox getMetricBoundsFU(final CharSequence string) { - if (string == null) { + if (null == string || 0 == string.length() ) { return new AABBox(); } final int charCount = string.length(); + final int lineHeight = getLineHeightFU(); - int totalHeight = 0; - int totalWidth = 0; - int curLineWidth = 0; + + int y = 0; + int advanceTotal = 0; + for (int i=0; i<charCount; i++) { final char character = string.charAt(i); if (character == '\n') { - totalWidth = Math.max(curLineWidth, totalWidth); - curLineWidth = 0; - totalHeight += lineHeight; + advanceTotal = 0; + y -= lineHeight; continue; } - curLineWidth += getAdvanceWidthFU( getGlyphID( character ) ); + advanceTotal += getAdvanceWidthFU( getGlyphID( character ) ); } - if (curLineWidth > 0) { - totalHeight += lineHeight; - totalWidth = Math.max(curLineWidth, totalWidth); + if (advanceTotal > 0) { + y -= lineHeight; } - return new AABBox(0, 0, 0, totalWidth, totalHeight,0); + return new AABBox(0,y,0, advanceTotal,0,0); + } + + @Override + public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string) { + return getPointsBoundsFU(transform, string).scale2(1.0f/metrics.getUnitsPerEM(), new float[3]); } @Override public AABBox getPointsBoundsFU(final AffineTransform transform, final CharSequence string) { - if (string == null) { + if (null == string || 0 == string.length() ) { return new AABBox(); } final AffineTransform temp1 = new AffineTransform(); final AffineTransform temp2 = new AffineTransform(); + + final AABBox res = new AABBox(); final int charCount = string.length(); + final int lineHeight = getLineHeightFU(); - final AABBox tbox = new AABBox(); - final AABBox res = new AABBox(); - float y = 0; - float advanceTotal = 0; + int y = 0; + int advanceTotal = 0; Font.Glyph left_glyph = null; + final AABBox temp_box = new AABBox(); for(int i=0; i< charCount; i++) { final char character = string.charAt(i); @@ -366,7 +373,8 @@ class TypecastFont implements Font { } final int glyph_id = getGlyphID(character); final Font.Glyph glyph = getGlyph(glyph_id); - if( null == glyph.getShape() ) { + final OutlineShape glyphShape = glyph.getShape(); + if( null == glyphShape ) { left_glyph = null; continue; } @@ -374,7 +382,7 @@ class TypecastFont implements Font { advanceTotal += left_glyph.getKerningFU(glyph_id); } temp1.translate(advanceTotal, y, temp2); - res.resize(temp1.transform(glyph.getBBoxFU(), tbox)); + res.resize(temp1.transform(glyph.getBBoxFU(), temp_box)); advanceTotal += glyph.getAdvanceFU(); left_glyph = glyph; } @@ -383,8 +391,77 @@ class TypecastFont implements Font { } @Override - public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string) { - return getPointsBoundsFU(transform, string).scale2(1.0f/metrics.getUnitsPerEM(), new float[3]); + public AABBox getPointsBounds2(final AffineTransform transform, final CharSequence string) { + if (null == string || 0 == string.length() ) { + return new AABBox(); + } + final OutlineShape.Visitor visitor = new OutlineShape.Visitor() { + @Override + public final void visit(final OutlineShape shape, final AffineTransform t) { + // nop + } }; + return processString(visitor, transform, string); + } + + @Override + public AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string) { + return processString(visitor, transform, string, new AffineTransform(), new AffineTransform()); + } + + @Override + public AABBox processString(final OutlineShape.Visitor visitor, final AffineTransform transform, + final CharSequence string, + final AffineTransform temp1, final AffineTransform temp2) { + if (null == string || 0 == string.length() ) { + return new AABBox(); + } + final AABBox res = new AABBox(); + final int charCount = string.length(); + + // region.setFlipped(true); + final float lineHeight = getLineHeight(); + + float y = 0; + float advanceTotal = 0; + Font.Glyph left_glyph = null; + final AABBox temp_box = new AABBox(); + + for(int i=0; i< charCount; i++) { + final char character = string.charAt(i); + if( '\n' == character ) { + y -= lineHeight; + advanceTotal = 0; + left_glyph = null; + } else if (character == ' ') { + advanceTotal += getAdvanceWidth(Glyph.ID_SPACE); + left_glyph = null; + } else { + // reset transform + if( null != transform ) { + temp1.setTransform(transform); + } else { + temp1.setToIdentity(); + } + final int glyph_id = getGlyphID(character); + final Font.Glyph glyph = getGlyph(glyph_id); + final OutlineShape glyphShape = glyph.getShape(); + if( null == glyphShape ) { + left_glyph = null; + continue; + } + if( null != left_glyph ) { + advanceTotal += left_glyph.getKerning(glyph_id); + } + temp1.translate(advanceTotal, y, temp2); + res.resize(temp1.transform(glyphShape.getBounds(), temp_box)); + + visitor.visit(glyphShape, temp1); + advanceTotal += glyph.getAdvance(); + left_glyph = glyph; + } + } + return res; } @Override diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java index d8f46090b..9db8fc6d5 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java index 274114e4a..cae8b2755 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java index 8099a2ff7..d6fade9f3 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-2023 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: |