[DEV] management of the font (external mode)

This commit is contained in:
Edouard DUPIN 2021-04-09 01:15:07 +02:00
parent fb91ff5cbf
commit add77c6128
28 changed files with 102873 additions and 262 deletions

View File

@ -5,6 +5,7 @@
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="resources"/>
<classpathentry including="**/*.java" kind="src" output="out/eclipse/classes-test" path="test/src">
<attributes>
<attribute name="test" value="true"/>

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.3 MiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 697 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 737 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 897 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.8 MiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 795 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 791 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 4.9 MiB

View File

@ -4,6 +4,7 @@
open module org.atriasoft.esvg {
exports org.atriasoft.esvg;
exports org.atriasoft.esvg.font;
exports org.atriasoft.esvg.render;
requires transitive io.scenarium.logger;

View File

@ -0,0 +1,11 @@
package org.atriasoft.esvg;
import org.atriasoft.etk.Uri;
public class Esvg {
public static void init() {
Uri.addLibrary("esvg", Esvg.class, "/resources/esvg/");
}
private Esvg() {}
}

View File

@ -368,6 +368,32 @@ public class EsvgDocument extends Base {
return true;
}
/*
public float[][] renderImageFloat(final Vector2i size) {
return renderImageFloat(size, false);
}
public float[][] renderImageFloat(Vector2i size, final boolean visualDebug) {
if (size == null) {
size = new Vector2i((int) this.size.x(), (int) this.size.y());
} else {
if (size.x() <= 0) {
size = size.withX((int) this.size.x());
}
if (size.y() <= 0) {
size = size.withY((int) this.size.y());
}
}
Log.debug("Generate size " + size);
Renderer renderedElement = new Renderer(size, this, visualDebug);
// create the first element matrix modification ...
Matrix2x3f basicTrans = Matrix2x3f.IDENTITY.multiply(Matrix2x3f.createScale(new Vector2f(size.x() / this.size.x(), size.y() / this.size.y())));
draw(renderedElement, basicTrans);
// direct return the generated data ...
return renderedElement.getData();
}
*/
/**
* Generate Image in a specific format.
* @param size Size expected of the rendered image (value <=0 if it need to be automatic.) return the size generate

View File

@ -0,0 +1,280 @@
package org.atriasoft.esvg;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import org.atriasoft.esvg.font.Glyph;
import org.atriasoft.esvg.font.Kerning;
import org.atriasoft.esvg.internal.Log;
import org.atriasoft.esvg.render.PathModel;
import org.atriasoft.esvg.render.RenderingConfig;
import org.atriasoft.esvg.render.Weight;
import org.atriasoft.etk.Uri;
import org.atriasoft.etk.math.Matrix2x3f;
import org.atriasoft.etk.math.Vector2f;
import org.atriasoft.etk.math.Vector2i;
import org.atriasoft.etk.util.Pair;
import org.atriasoft.exml.Exml;
import org.atriasoft.exml.exception.ExmlBuilderException;
import org.atriasoft.exml.model.XmlElement;
import org.atriasoft.exml.model.XmlNode;
// https://www.w3.org/TR/SVGTiny12/fonts.html
public class EsvgFont {
/**
* Load the file that might contain the svg
* @param uri File of the svg
* @return false : An error occured
* @return true : Parsing is OK
*/
public static EsvgFont load(final Uri uri) {
EsvgFont font = new EsvgFont();
XmlNode doc = null;
try {
doc = Exml.parse(uri);
} catch (ExmlBuilderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
if (!(doc instanceof XmlElement root)) {
Log.error("can not load the SVG font ==> wrong root node");
return null;
}
if (!root.existNode("svg") || !(root.getNodeNoExcept("svg") instanceof XmlElement svgNode)) {
Log.error("can not load Node <svg> in svg document");
return null;
}
if (!svgNode.existNode("defs") || !(svgNode.getNodeNoExcept("defs") instanceof XmlElement defsNode)) {
Log.error("can not load Node <defs> in svg document");
return null;
}
if (!defsNode.existNode("font") || !(defsNode.getNodeNoExcept("font") instanceof XmlElement fontElement)) {
Log.error("can not load Node <font> in svg document");
return null;
}
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 instanceof XmlElement fontFace) {
font.fontFamily = fontFace.getAttribute("font-family", "unknown");
font.fontStretch = fontFace.getAttribute("font-stretch", "normal");
font.fontWeight = Integer.parseInt(fontFace.getAttribute("font-weight", "400"));
font.unitsPerEm = Integer.parseInt(fontFace.getAttribute("units-per-em", "1000"));
font.ascent = Integer.parseInt(fontFace.getAttribute("ascent", "800"));
font.descent = Integer.parseInt(fontFace.getAttribute("descent", "-200"));
font.xHeight = Integer.parseInt(fontFace.getAttribute("x-height", "450"));
font.capHeight = Integer.parseInt(fontFace.getAttribute("cap-height", "662"));
font.underlineThickness = Integer.parseInt(fontFace.getAttribute("underline-thickness", "50"));
font.underlinePosition = Integer.parseInt(fontFace.getAttribute("underline-position", "-150"));
//panose-1="2 2 6 3 5 4 5 2 3 4"
String tmp = fontFace.getAttribute("panose-1", null);
String[] tmpSplit = tmp.split(" ");
font.panose1 = new int[tmpSplit.length];
for (int iii = 0; iii < tmpSplit.length; iii++) {
font.panose1[iii] = Integer.parseInt(tmpSplit[iii]);
}
//bbox="-879 -545 1767 934"
tmp = fontFace.getAttribute("bbox", null);
tmpSplit = tmp.split(" ");
font.bbox = new int[tmpSplit.length];
for (int iii = 0; iii < tmpSplit.length; iii++) {
font.bbox[iii] = Integer.parseInt(tmpSplit[iii]);
}
//unicode-range="U+0020-1F093"
tmp = fontFace.getAttribute("unicode-range", null);
tmpSplit = tmp.split("-");
int start = Integer.parseInt(tmpSplit[0].substring(2), 16);
int stop = Integer.parseInt(tmpSplit[1], 16);
font.unicodeRange = new Pair<>(start, stop);
}
} else {
Log.warning("unsupported node name :" + values.getValue());
}
}
for (XmlNode values : fontElement.getNodes()) {
if (values.getValue().equals("hkern")) {
if (values instanceof XmlElement kernElem) {
String g1 = kernElem.getAttribute("g1", null);
String g2 = kernElem.getAttribute("g2", null);
if (g1 == null || g2 == null) {
continue;
}
float offset = Float.parseFloat(kernElem.getAttribute("k", "0"));
if (offset == 0.0f) {
continue;
}
String[] g1Splited = g1.split(",");
String[] g2Splited = g2.split(",");
// create the list of kerning of the next elements
List<Kerning> elementsKerning = new ArrayList<>();
for (int iii = 0; iii < g2Splited.length; iii++) {
for (Map.Entry<Integer, Glyph> entry : font.glyphs.entrySet()) {
if (entry.getValue().getName().equals(g2Splited[iii])) {
elementsKerning.add(new Kerning(offset, entry.getKey()));
break;
}
}
}
// add it on the
for (int iii = 0; iii < g1Splited.length; iii++) {
for (Map.Entry<Integer, Glyph> entry : font.glyphs.entrySet()) {
if (entry.getValue().getName().equals(g1Splited[iii])) {
entry.getValue().addKerning(elementsKerning);
break;
}
}
}
}
}
}
return font;
}
// The maximum accented height of the font within the font coordinate system.
private int ascent = 800; // this is the height of the font (on top...)
private int[] bbox = { -879, -545, 1767, 934 };
// The height of uppercase glyphs in the font within the font coordinate system.
private int capHeight = 662;
// The maximum unaccented depth of the font within the font coordinate system.
private int descent = -200; // lower size of the font
private String fontFamily = "unknown";
private String fontStretch = "normal";
private int fontWeight = 400;
private final Map<Integer, Glyph> glyphs = new HashMap<>();
// 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 Glyph missingGlyph = null;
private int[] panose1 = { 2, 2, 6, 3, 5, 4, 5, 2, 3, 4 };
private int underlinePosition = -150;
private int underlineThickness = 50;
private Pair<Integer, Integer> unicodeRange = new Pair<>(0x0020, 0x1F093);
private int unitsPerEm = 1000; // full size of the font
// The height of lowercase glyphs in the font within the font coordinate system.
private int xHeight = 450;
/**
* Get the font real size use (height) for all the characters.
* @param fontSize size of the font the user require
* @return Real size in pixel of element can impact the output
*/
public int calculateFontRealHeight(final int fontSize) {
return fontSize * this.unitsPerEm / this.capHeight;
}
public int calculateWidth(final String uVal, final int fontSize) {
return calculateWidth(uVal, fontSize, true);
}
public int calculateWidth(final String data, final int fontSize, final boolean withKerning) {
int realSize = calculateFontRealHeight(fontSize);
float scale = (float) realSize / (float) this.unitsPerEm;
int out = 0;
int lastValue = 0;
for (char uVal : data.toCharArray()) {
Glyph glyph = getGlyph(uVal);
if (glyph == null) {
lastValue = uVal;
continue;
}
if (withKerning) {
out -= glyph.getKerning(lastValue) * scale;
lastValue = uVal;
}
out += glyph.getHorizAdvX() * scale;
}
return out;
}
public Glyph getGlyph(final int glyphIndex) {
Glyph out = this.glyphs.get(glyphIndex);
if (out == null) {
return this.missingGlyph;
}
return out;
}
public int getNumGlyphs() {
return this.glyphs.size();
}
public boolean hasKerning() {
// TODO Auto-generated method stub
return false;
}
public Weight render(final int uVal, final int fontSize) {
int realSize = calculateFontRealHeight(fontSize);
Glyph glyph = getGlyph(uVal);
if (glyph == null) {
return null;
}
float scale = (float) realSize / (float) this.unitsPerEm;
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) {
return null;
}
Weight data = glyph.getModel().drawFill(new Vector2i((int) (glyph.getHorizAdvX() * scale), realSize), transform, 8, config);
return data;
}
public Weight render(final String uVal, final int fontSize) {
return render(uVal, fontSize, 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 = (float) 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(new Vector2i((int) (glyph.getHorizAdvX() * scale), realSize), transform, 8, config);
weight.fusion(redered, offsetWriting, 0);
}
offsetWriting += advenceXLocal;
}
return weight;
}
}

View File

@ -12,7 +12,6 @@ import org.atriasoft.esvg.render.DynamicColor;
import org.atriasoft.esvg.render.SegmentList;
import org.atriasoft.etk.math.Matrix2x3f;
import org.atriasoft.etk.math.Vector2f;
import org.atriasoft.etk.math.Vector2i;
import org.atriasoft.etk.util.Dynamic;
import org.atriasoft.exml.model.XmlElement;
import org.atriasoft.exml.parser.Tools;
@ -39,21 +38,200 @@ public class Path extends Base {
}
private static String cleanBadSpaces(final String input) {
StringBuilder out = new StringBuilder(input.length());
boolean haveSpace = false;
for (char it : input.toCharArray()) {
if (it == ' ' || it == '\t' || it == '\r') {
haveSpace = true;
} else {
if (haveSpace) {
haveSpace = false;
out.append(' ');
}
out.append(it);
public static PathModel createPathModel(final String d) {
PathModel out = new PathModel();
Log.verbose("Parse Path : \"" + d + "\"");
List<String> commandsSplited = Path.splitCommand(d);
String[] listDot = null;
// TODO REWORK this, can be done with a simple split and search in a list...
for (Command sss = Path.extractCmd(commandsSplited, 0); sss != null; sss = Path.extractCmd(commandsSplited, sss.offset())) {
boolean relative = false;
listDot = sss.listElem();
// Log.verbose("Find new command : '" + sss.cmd + "'");
// if (listDot != null) {
// for (int jjj = 0; jjj < listDot.length; jjj++) {
// Log.verbose(" -> '" + listDot[jjj] + "'");
// }
// } else {
// Log.verbose(" -> no elements");
// }
switch (sss.cmd) {
case 'm': // Move to (relative)
relative = true;
case 'M': // Move to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
if (listDot.length % 2 != 0) {
Log.warning("the PATH command " + sss.cmd + " must be a multiple of 2");
break;
}
// 2 Elements ...
if (listDot.length >= 2) {
out.moveTo(relative, new Vector2f(Float.parseFloat(listDot[0]), Float.parseFloat(listDot[1])));
}
for (int iii = 2; iii < listDot.length; iii += 2) {
out.lineTo(relative, new Vector2f(Float.parseFloat(listDot[iii]), Float.parseFloat(listDot[iii + 1])));
}
break;
case 'l': // Line to (relative)
relative = true;
case 'L': // Line to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
if (listDot.length % 2 != 0) {
Log.warning("the PATH command " + sss.cmd + " must be a multiple of 2");
break;
}
for (int iii = 0; iii < listDot.length; iii += 2) {
out.lineTo(relative, new Vector2f(Float.parseFloat(listDot[iii]), Float.parseFloat(listDot[iii + 1])));
}
break;
case 'v': // Vertical Line to (relative)
relative = true;
case 'V': // Vertical Line to (absolute)
// 1 Element ...
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
for (int iii = 0; iii < listDot.length; iii++) {
out.lineToV(relative, Float.parseFloat(listDot[iii]));
}
break;
case 'h': // Horizantal Line to (relative)
relative = true;
case 'H': // Horizantal Line to (absolute)
// 1 Element ...
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
for (int iii = 0; iii < listDot.length; iii++) {
out.lineToH(relative, Float.parseFloat(listDot[iii]));
}
break;
case 'q': // Quadratic Bezier curve (relative)
relative = true;
case 'Q': // Quadratic Bezier curve (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 4 Elements ...
if (listDot.length % 4 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length + " (must have 4 numbers)");
break;
}
for (int iii = 0; iii < listDot.length; iii += 4) {
out.bezierCurveTo(relative, new Vector2f(Float.parseFloat(listDot[iii]), Float.parseFloat(listDot[iii + 1])),
new Vector2f(Float.parseFloat(listDot[iii + 2]), Float.parseFloat(listDot[iii + 3])));
}
break;
case 't': // smooth quadratic Bezier curve to (relative)
relative = true;
case 'T': // smooth quadratic Bezier curve to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 4 Elements ...
if (listDot.length % 2 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length + " (must have 2 numbers)");
break;
}
// 2 Elements ...
for (int iii = 0; iii < listDot.length; iii += 2) {
out.bezierSmoothCurveTo(relative, new Vector2f(Float.parseFloat(listDot[iii]), Float.parseFloat(listDot[iii + 1])));
}
break;
case 'c': // curve to (relative)
relative = true;
case 'C': // curve to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 6 Elements ...
if (listDot.length % 6 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length + "(Must be a multiple of 6)");
break;
}
for (int iii = 0; iii < listDot.length; iii += 6) {
out.curveTo(relative, new Vector2f(Float.parseFloat(listDot[iii]), Float.parseFloat(listDot[iii + 1])),
new Vector2f(Float.parseFloat(listDot[iii + 2]), Float.parseFloat(listDot[iii + 3])),
new Vector2f(Float.parseFloat(listDot[iii + 4]), Float.parseFloat(listDot[iii + 5])));
}
break;
case 's': // smooth curve to (relative)
relative = true;
case 'S': // smooth curve to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 4 Elements ...
if (listDot.length % 4 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length + "(Must be a multiple of 4)");
break;
}
for (int iii = 0; iii < listDot.length; iii += 4) {
out.smoothCurveTo(relative, new Vector2f(Float.parseFloat(listDot[iii]), Float.parseFloat(listDot[iii + 1])),
new Vector2f(Float.parseFloat(listDot[iii + 2]), Float.parseFloat(listDot[iii + 3])));
}
break;
case 'a': // elliptical Arc (relative)
relative = true;
case 'A': // elliptical Arc (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 4 element ff,ff f i,i ff,ff Elements ...
if (listDot.length % 7 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length);
break;
}
for (int iii = 0; iii < listDot.length; iii += 7) {
boolean largeArcFlag = true;
boolean sweepFlag = true;
if (Integer.parseInt(listDot[iii + 3]) == 0) {
largeArcFlag = false;
}
if (Integer.parseInt(listDot[iii + 4]) == 0) {
sweepFlag = false;
}
out.ellipticTo(relative, new Vector2f(Float.parseFloat(listDot[iii]), Float.parseFloat(listDot[iii + 1])), Float.parseFloat(listDot[iii + 2]), largeArcFlag, sweepFlag,
new Vector2f(Float.parseFloat(listDot[iii + 5]), Float.parseFloat(listDot[iii + 6])));
}
break;
case 'z': // closepath (relative)
relative = true;
case 'Z': // closepath (absolute)
// 0 Element ...
if (listDot != null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length);
break;
}
out.close(relative);
break;
default:
Log.error("Unknow error : '" + sss.cmd + "'");
}
}
return out.toString();
return out;
}
//return the next char position ... (after 'X' or NULL)
@ -67,12 +245,12 @@ public class Path extends Base {
// Log.warning(" -[" + iii + "] '" + input.get(iii) + "'");
// }
if (input.get(offset).length() != 1) {
Log.error("Error in the SVG Path : '" + input.get(offset) + "' [" + offset);
Log.error("Error in the SVG Path : '" + input.get(offset) + "' [" + Integer.toString(offset));
return null;
}
char cmd = input.get(offset).charAt(0);
if (!((cmd <= 'Z' && cmd >= 'A') || (cmd <= 'z' && cmd >= 'a'))) {
Log.error("Error in the SVG Path : '" + cmd + "' [" + offset);
Log.error("Error in the SVG Path : '" + cmd + "' [" + Integer.toString(offset));
return null;
}
//Log.verbose("Find command : " + cmd);
@ -89,7 +267,7 @@ public class Path extends Base {
}
int length = iii - (offset + 1);
if (length == 0) {
return new Command(cmd, null, iii + 1);
return new Command(cmd, null, iii);
}
String[] outputList = new String[length];
for (int jjj = 0; jjj < length; jjj++) {
@ -98,6 +276,40 @@ public class Path extends Base {
return new Command(cmd, outputList, iii);
}
static List<String> splitCommand(final String data) {
List<String> out = new ArrayList<>();
StringBuilder tmpString = new StringBuilder(20);
boolean isNumber = false;
for (char it : data.toCharArray()) {
// ',' is here beause some people oprefer the ' ' instead of ','
if (it == ' ' || it == '\t' || it == '\r' || it == '\n' || it == ',') {
String elements = tmpString.toString();
if (!elements.isEmpty()) {
out.add(elements);
}
tmpString.setLength(0);
isNumber = false;
} else if (Tools.checkNumber(it, true) || it == '.') {
isNumber = true;
tmpString.append(it);
} else if ((it <= 'Z' && it >= 'A') || (it <= 'z' && it >= 'a')) {
if (isNumber) {
out.add(tmpString.toString());
tmpString.setLength(0);
}
isNumber = false;
out.add(Character.toString(it));
} else {
Log.error("Can not parse path : '" + it + "'");
}
}
String elements = tmpString.toString();
if (!elements.isEmpty()) {
out.add(elements);
}
return out;
}
public PathModel listElement = new PathModel();
public Path(final PaintState parentPaintState) {
@ -186,219 +398,8 @@ public class Path extends Base {
Log.warning("path: missing 'd' attribute or empty");
return false;
}
Log.verbose("Parse Path : \"" + elementXML1 + "\"");
List<String> commandsSplited = splitCommand(elementXML1);
String[] listDot = null;
// TODO REWORK this, can be done with a simple split and search in a list...
for (Command sss = Path.extractCmd(commandsSplited, 0); sss != null; sss = Path.extractCmd(commandsSplited, sss.offset())) {
boolean relative = false;
listDot = sss.listElem();
Log.error("Find new command : '" + sss.cmd + "'");
if (listDot != null) {
for (int jjj = 0; jjj < listDot.length; jjj++) {
Log.error(" -> '" + listDot[jjj] + "'");
}
} else {
Log.error(" -> no elements");
}
switch (sss.cmd) {
case 'm': // Move to (relative)
relative = true;
case 'M': // Move to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 2 Elements ...
if (listDot.length >= 1) {
this.listElement.moveTo(relative, Vector2f.valueOf(listDot[0]));
}
for (int iii = 1; iii < listDot.length; iii++) {
this.listElement.lineTo(relative, Vector2f.valueOf(listDot[iii]));
}
break;
case 'l': // Line to (relative)
relative = true;
case 'L': // Line to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
for (int iii = 0; iii < listDot.length; iii++) {
this.listElement.lineTo(relative, Vector2f.valueOf(listDot[iii]));
}
break;
case 'v': // Vertical Line to (relative)
relative = true;
case 'V': // Vertical Line to (absolute)
// 1 Element ...
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
for (int iii = 0; iii < listDot.length; iii++) {
this.listElement.lineToV(relative, Float.parseFloat(listDot[iii]));
}
break;
case 'h': // Horizantal Line to (relative)
relative = true;
case 'H': // Horizantal Line to (absolute)
// 1 Element ...
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
for (int iii = 0; iii < listDot.length; iii++) {
this.listElement.lineToH(relative, Float.parseFloat(listDot[iii]));
}
break;
case 'q': // Quadratic Bezier curve (relative)
relative = true;
case 'Q': // Quadratic Bezier curve (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 4 Elements ...
if (listDot.length % 2 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length);
break;
}
for (int iii = 0; iii < listDot.length; iii += 2) {
this.listElement.bezierCurveTo(relative, Vector2f.valueOf(listDot[iii]), Vector2f.valueOf(listDot[iii + 1]));
}
break;
case 't': // smooth quadratic Bezier curve to (relative)
relative = true;
case 'T': // smooth quadratic Bezier curve to (absolute)
// 2 Elements ...
for (int iii = 0; iii < listDot.length; iii++) {
this.listElement.bezierSmoothCurveTo(relative, Vector2f.valueOf(listDot[iii]));
}
break;
case 'c': // curve to (relative)
relative = true;
case 'C': // curve to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 6 Elements ...
if (listDot.length % 3 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length);
break;
}
for (int iii = 0; iii < listDot.length; iii += 3) {
this.listElement.curveTo(relative, Vector2f.valueOf(listDot[iii]), Vector2f.valueOf(listDot[iii + 1]), Vector2f.valueOf(listDot[iii + 2]));
}
break;
case 's': // smooth curve to (relative)
relative = true;
case 'S': // smooth curve to (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 4 Elements ...
if (listDot.length % 2 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length);
break;
}
for (int iii = 0; iii < listDot.length; iii += 2) {
this.listElement.smoothCurveTo(relative, Vector2f.valueOf(listDot[iii]), Vector2f.valueOf(listDot[iii + 1]));
}
break;
case 'a': // elliptical Arc (relative)
relative = true;
case 'A': // elliptical Arc (absolute)
if (listDot == null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot);
break;
}
// 4 element ff,ff f i,i ff,ff Elements ...
if (listDot.length % 4 != 0) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length);
break;
}
for (int iii = 0; iii < listDot.length; iii += 7) {
boolean largeArcFlag = true;
boolean sweepFlag = true;
Vector2i tmp = Vector2i.valueOf(listDot[iii + 2]);
if (tmp.x() == 0) {
largeArcFlag = false;
}
if (tmp.y() == 0) {
sweepFlag = false;
}
this.listElement.ellipticTo(relative, Vector2f.valueOf(listDot[iii]), Float.parseFloat(listDot[iii + 1]), largeArcFlag, sweepFlag, Vector2f.valueOf(listDot[iii + 3]));
}
break;
case 'z': // closepath (relative)
relative = true;
case 'Z': // closepath (absolute)
// 0 Element ...
if (listDot != null) {
Log.warning("the PATH command " + sss.cmd + " has not the good number of element = " + listDot.length);
break;
}
this.listElement.close(relative);
break;
default:
Log.error("Unknow error : '" + sss.cmd + "'");
}
}
return true;
}
List<String> splitCommand(final String data) {
List<String> out = new ArrayList<>();
StringBuilder tmpString = new StringBuilder(20);
boolean isText = false;
boolean isNumber = false;
for (char it : data.toCharArray()) {
if (it == ' ' || it == '\t' || it == '\r') {
String elements = tmpString.toString();
if (!elements.isEmpty()) {
out.add(elements);
}
tmpString.setLength(0);
isText = false;
isNumber = false;
} else if (Tools.checkNumber(it, true) || it == ',' || it == '.') {
if (isText) {
out.add(tmpString.toString());
tmpString.setLength(0);
}
isText = false;
isNumber = true;
tmpString.append(it);
} else if ((it <= 'Z' && it >= 'A') || (it <= 'z' && it >= 'a')) {
if (isNumber) {
out.add(tmpString.toString());
tmpString.setLength(0);
}
isText = true;
isNumber = false;
tmpString.append(it);
} else {
Log.error("Can not parse path : '" + it + "'");
}
}
String elements = tmpString.toString();
if (!elements.isEmpty()) {
out.add(elements);
}
return out;
this.listElement = Path.createPathModel(elementXML1);
return this.listElement != null;
}
}

View File

@ -20,7 +20,6 @@ import org.atriasoft.etk.util.ArraysTools;
* @copyright 2011, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
public class Renderer {
private static final boolean DEBUG_MODE = false;
protected Color[][] buffer; // for debug

View File

@ -0,0 +1,103 @@
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);
}
}

View File

@ -0,0 +1,154 @@
package org.atriasoft.esvg.font;
import java.util.ArrayList;
import java.util.List;
import org.atriasoft.esvg.Path;
import org.atriasoft.esvg.internal.Log;
import org.atriasoft.esvg.render.PathModel;
import org.atriasoft.exml.model.XmlElement;
public class Glyph {
private static final boolean LAZY_MODE = true;
public static Glyph valueOf(final XmlElement element) {
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"));
Log.verbose(" horizAdvX= '" + horizAdvX + "'");
String unicode = element.getAttribute("unicode", null);
Log.verbose(" unicode= '" + unicode + "'");
if (unicode == null) {
Log.debug("Not manage glyph : '" + name + "' (missing unicode value)");
return null;
}
String d = element.getAttribute("d", null);
Log.verbose(" d= '" + d + "'");
int unicodeValue = 0;
if (unicode.startsWith("&#x") && unicode.endsWith(";")) {
String subElement = unicode.substring(3, unicode.length() - 1);
if (subElement.indexOf("&") != -1) {
Log.debug("not supported glyph concatenarion" + name + " value='" + unicode + "'");
return null;
}
unicodeValue = Integer.parseInt(subElement, 16);
} else if (unicode.startsWith("&#") && unicode.endsWith(";")) {
String subElement = unicode.substring(2, unicode.length() - 1);
if (subElement.indexOf("&") != -1) {
Log.debug("not supported glyph concatenarion" + name + " value='" + unicode + "'");
return null;
}
unicodeValue = Integer.parseInt(subElement, 16);
} else if (unicode.length() != 1) {
Log.debug("not supported glyph concatenarion" + name + " value='" + unicode + "'");
return null;
} else {
unicodeValue = unicode.charAt(0);
}
Log.verbose(" unicodeValue= '" + unicodeValue + "'");
Glyph out = new Glyph(horizAdvX, d, name, unicode, unicodeValue);
if (!Glyph.LAZY_MODE) {
// when not in lazy mode we force the parsing of the model, this permit to check the whole font... otherwise many font is really big > 8000 glyph, then it is a waste of time...
out.getModel();
}
return out;
}
private int horizAdvX;
private List<Kerning> kernings = new ArrayList<>();
private PathModel model;
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) {
this.horizAdvX = horizAdvX;
this.model = model;
this.path = null;
this.name = name;
this.unicode = unicode;
this.unicodeValue = unicodeValue;
}
public Glyph(final int horizAdvX, final String path, final String name, final String unicode, final int unicodeValue) {
this.horizAdvX = horizAdvX;
this.model = null;
this.path = path;
this.name = name;
this.unicode = unicode;
this.unicodeValue = unicodeValue;
}
public void addKerning(final List<Kerning> elementsKerning) {
this.kernings.addAll(elementsKerning);
}
public int getHorizAdvX() {
return this.horizAdvX;
}
public float getKerning(final int unicodeValue) {
if (unicodeValue == 0) {
return 0.0f;
}
for (Kerning elem : this.kernings) {
if (elem.unicode() == unicodeValue) {
Log.info("Get kerning between : '" + (char) this.unicodeValue + "' and '" + (char) unicodeValue + "' => " + elem.offset());
return elem.offset();
}
}
return 0;
}
public List<Kerning> getKernings() {
return this.kernings;
}
public PathModel getModel() {
if (this.model == null && this.path != null) {
this.model = Path.createPathModel(this.path);
}
return this.model;
}
public String getName() {
return this.name;
}
public String getUnicode() {
return this.unicode;
}
public Integer getUnicodeValue() {
return this.unicodeValue;
}
public void setHorizAdvX(final int horizAdvX) {
this.horizAdvX = horizAdvX;
}
public void setKernings(final List<Kerning> kernings) {
this.kernings = kernings;
}
public void setModel(final PathModel model) {
this.model = model;
}
public void setName(final String name) {
this.name = name;
}
public void setUnicode(final String unicode) {
this.unicode = unicode;
}
public void setUnicodeValue(final int unicodeValue) {
this.unicodeValue = unicodeValue;
}
}

View File

@ -0,0 +1,5 @@
package org.atriasoft.esvg.font;
public record Kerning(
float offset,
int unicode) {}

View File

@ -4,62 +4,63 @@ import io.scenarium.logger.LogLevel;
import io.scenarium.logger.Logger;
public class Log {
private static final String LIBNAME = "esvg";
private static final String LIBNAMEDRAW = Logger.getDrawableName(Log.LIBNAME);
private static final boolean PRINTCRITICAL = Logger.getNeedPrint(Log.LIBNAME, LogLevel.CRITICAL);
private static final boolean PRINTDEBUG = Logger.getNeedPrint(Log.LIBNAME, LogLevel.DEBUG);
private static final boolean PRINTERROR = Logger.getNeedPrint(Log.LIBNAME, LogLevel.ERROR);
private static final boolean PRINTINFO = Logger.getNeedPrint(Log.LIBNAME, LogLevel.INFO);
private static final boolean PRINTPRINT = Logger.getNeedPrint(Log.LIBNAME, LogLevel.PRINT);
private static final boolean PRINTTODO = Logger.getNeedPrint(Log.LIBNAME, LogLevel.TODO);
private static final boolean PRINTVERBOSE = Logger.getNeedPrint(Log.LIBNAME, LogLevel.VERBOSE);
private static final boolean PRINTWARNING = Logger.getNeedPrint(Log.LIBNAME, LogLevel.WARNING);
private static final boolean FORCE_ALL = false;
private static final String LIB_NAME = "esvg";
private static final String LIB_NAME_DRAW = Logger.getDrawableName(Log.LIB_NAME);
private static final boolean PRINT_CRITICAL = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.CRITICAL);
private static final boolean PRINT_DEBUG = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.DEBUG);
private static final boolean PRINT_ERROR = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.ERROR);
private static final boolean PRINT_INFO = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.INFO);
private static final boolean PRINT_PRINT = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.PRINT);
private static final boolean PRINT_TODO = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.TODO);
private static final boolean PRINT_VERBOSE = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.VERBOSE);
private static final boolean PRINT_WARNING = Logger.getNeedPrint(Log.LIB_NAME, LogLevel.WARNING);
public static void critical(final String data) {
if (Log.PRINTCRITICAL) {
Logger.critical(Log.LIBNAMEDRAW, data);
if (Log.PRINT_CRITICAL || Log.FORCE_ALL) {
Logger.critical(Log.LIB_NAME_DRAW, data);
}
}
public static void debug(final String data) {
if (Log.PRINTDEBUG) {
Logger.debug(Log.LIBNAMEDRAW, data);
if (Log.PRINT_DEBUG || Log.FORCE_ALL) {
Logger.debug(Log.LIB_NAME_DRAW, data);
}
}
public static void error(final String data) {
if (Log.PRINTERROR) {
Logger.error(Log.LIBNAMEDRAW, data);
if (Log.PRINT_ERROR || Log.FORCE_ALL) {
Logger.error(Log.LIB_NAME_DRAW, data);
}
}
public static void info(final String data) {
if (Log.PRINTINFO) {
Logger.info(Log.LIBNAMEDRAW, data);
if (Log.PRINT_INFO || Log.FORCE_ALL) {
Logger.info(Log.LIB_NAME_DRAW, data);
}
}
public static void print(final String data) {
if (Log.PRINTPRINT) {
Logger.print(Log.LIBNAMEDRAW, data);
if (Log.PRINT_PRINT || Log.FORCE_ALL) {
Logger.print(Log.LIB_NAME_DRAW, data);
}
}
public static void todo(final String data) {
if (Log.PRINTTODO) {
Logger.todo(Log.LIBNAMEDRAW, data);
if (Log.PRINT_TODO || Log.FORCE_ALL) {
Logger.todo(Log.LIB_NAME_DRAW, data);
}
}
public static void verbose(final String data) {
if (Log.PRINTVERBOSE) {
Logger.verbose(Log.LIBNAMEDRAW, data);
if (Log.PRINT_VERBOSE || Log.FORCE_ALL) {
Logger.verbose(Log.LIB_NAME_DRAW, data);
}
}
public static void warning(final String data) {
if (Log.PRINTWARNING) {
Logger.warning(Log.LIBNAMEDRAW, data);
if (Log.PRINT_WARNING || Log.FORCE_ALL) {
Logger.warning(Log.LIB_NAME_DRAW, data);
}
}

View File

@ -257,7 +257,7 @@ public class DynamicColorSpecial implements DynamicColor {
}
break;
case REFLECT:
ratio -= ((int) (ratio) >> 1) + 1;
ratio -= ((int) (ratio)) / 2 * 2.0f;
if (ratio > 1.0f) {
ratio = 2.0f - ratio;
}
@ -305,7 +305,7 @@ public class DynamicColorSpecial implements DynamicColor {
break;
case REFLECT:
ratio = FMath.abs(ratio);
ratio -= ((int) (ratio) >> 1) + 1;
ratio -= ((int) (ratio)) / 2 * 2.0f;
if (ratio > 1.0f) {
ratio = 2.0f - ratio;
}
@ -396,8 +396,8 @@ public class DynamicColorSpecial implements DynamicColor {
// nothing to do ...
break;
case REFLECT:
ratio -= (((int) (ratio)) >> 1) + 1;
if (ratio > 1.0f) {
ratio -= ((int) (ratio)) / 2 * 2.0f;
while (ratio > 1.0f) {
ratio = 2.0f - ratio;
}
break;

View File

@ -3,10 +3,13 @@ package org.atriasoft.esvg.render;
import java.util.ArrayList;
import java.util.List;
import org.atriasoft.esvg.CapMode;
import org.atriasoft.esvg.JoinMode;
import org.atriasoft.esvg.internal.Log;
import org.atriasoft.etk.math.FMath;
import org.atriasoft.etk.math.Matrix2x3f;
import org.atriasoft.etk.math.Vector2f;
import org.atriasoft.etk.math.Vector2i;
/** @file
* @author Edouard DUPIN
@ -101,6 +104,32 @@ public class PathModel {
}
}
public Weight drawFill(final Vector2i size, final Matrix2x3f basicTrans, final int level, final RenderingConfig config) {
PointList listPoints = new PointList();
listPoints = generateListPoints(level, config.recurtionMax(), config.interpolationThreshold());
SegmentList listSegment = new SegmentList();
Weight weight = new Weight();
// Check if we need to display background
listSegment.createSegmentList(listPoints);
listSegment.applyMatrix(basicTrans);
// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
weight.generate(size, config.numberOfScanline(), listSegment);
return weight;
}
public Weight drawStroke(final Vector2i size, final Matrix2x3f basicTrans, final int level, final float strokeWidth, final RenderingConfig config) {
PointList listPoints = new PointList();
listPoints = generateListPoints(level, config.recurtionMax(), config.interpolationThreshold());
SegmentList listSegment = new SegmentList();
Weight weight = new Weight();
// Check if we need to display background
listSegment.createSegmentListStroke(listPoints, strokeWidth, CapMode.BUTT, JoinMode.MITER, 4.0f);
listSegment.applyMatrix(basicTrans);
// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
weight.generate(size, config.numberOfScanline(), listSegment);
return weight;
}
public void ellipticTo(final boolean relative, final Vector2f radius, final float angle, final boolean largeArcFlag, final boolean sweepFlag, final Vector2f pos) {
this.listElement.add(new ElementElliptic(relative, radius, angle, largeArcFlag, sweepFlag, pos));
}

View File

@ -34,7 +34,7 @@ public class PointList {
Log.warning(" Find List " + it.size() + " members");
for (int iii = 0; iii < it.size(); ++iii) {
Point elem = it.get(iii);
Log.warning(" [" + iii + "] Find " + elem.type + " " + elem.pos);
Log.verbose(" [" + iii + "] Find " + elem.type + " " + elem.pos);
}
}
}

View File

@ -0,0 +1,6 @@
package org.atriasoft.esvg.render;
public record RenderingConfig(
int recurtionMax,
float interpolationThreshold,
int numberOfScanline) {}

View File

@ -37,6 +37,15 @@ public class Weight {
ArraysTools.fill2(this.data, fill);
}
public void fusion(final Weight redered, final int offsetXXX, final int offsetYYY) {
for (int yyy = 0; yyy < redered.getHeight(); yyy++) {
for (int xxx = 0; xxx < redered.getWidth(); xxx++) {
this.data[offsetYYY + yyy][offsetXXX + xxx] = FMath.avg(0.0f, this.data[offsetYYY + yyy][offsetXXX + xxx] + redered.get(xxx, yyy), 1.0f);
}
}
}
public void generate(final Vector2i size, final int subSamplingCount, final SegmentList listSegment) {
resize(size);
// for each lines:
@ -143,6 +152,13 @@ public class Weight {
}
}
public float get(final int xxx, final int yyy) {
if (this.data == null) {
return 0;
}
return this.data[yyy][xxx];
}
public float get(final Vector2i pos) {
if (this.data == null) {
return 0;

View File

@ -4,8 +4,10 @@ import java.awt.image.BufferedImage;
import org.atriasoft.esvg.EsvgDocument;
import org.atriasoft.esvg.internal.Log;
import org.atriasoft.esvg.render.Weight;
import org.atriasoft.etk.Color;
import org.atriasoft.etk.Uri;
import org.atriasoft.etk.math.Vector2i;
import com.pngencoder.PngEncoder;
@ -32,5 +34,28 @@ public class ConfigTest {
new PngEncoder().withBufferedImage(bufferedImage).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);
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);
}
}
for (int yyy = 0; yyy < weight.getHeight() + 2; yyy++) {
bufferedImage.setRGB(0, yyy, 0xFFFF0000);
bufferedImage.setRGB(weight.getWidth() + 1, yyy, 0xFFFF0000);
}
for (int xxx = 0; xxx < weight.getWidth() + 2; xxx++) {
bufferedImage.setRGB(xxx, 0, 0xFFFF0000);
bufferedImage.setRGB(xxx, weight.getHeight() + 1, 0xFFFF0000);
}
Log.warning("Save file in " + uri.getPath());
byte[] outElem = new PngEncoder().withBufferedImage(bufferedImage).withCompressionLevel(9).toBytes();
Log.warning("outsize = " + outElem.length);
new PngEncoder().withBufferedImage(bufferedImage).withCompressionLevel(9).toFile(uri.getPath());
}
private ConfigTest() {}
}

View File

@ -0,0 +1,93 @@
package test.atriasoft.esvg;
import org.atriasoft.esvg.Esvg;
import org.atriasoft.esvg.EsvgDocument;
import org.atriasoft.esvg.EsvgFont;
import org.atriasoft.esvg.render.Weight;
import org.atriasoft.etk.Uri;
import org.junit.jupiter.api.Test;
class TestFont {
@Test
public void testFontError1() {
String data = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg height='900' width='700'>"
+ " <path d='M296 463q80 0 125 -45t45 -126q0 -130 -73 -217.5t-187 -87.5q-80 0 -125 45.5t-45 127.5q0 126 73 214.5t187 88.5zM218 51q78 0 123.5 68t45.5 166q0 115 -103 115q-75 0 -122 -66.5t-47 -167.5q0 -115 103 -115z'"
+ " stroke='green' stroke-width='3' />" + "</svg>";
EsvgDocument doc = new EsvgDocument();
doc.parse(data);
Uri.writeAll(new Uri(ConfigTest.BASE_PATH + "TestFontError1.svg"), data.replace("'", "\""));
ConfigTest.generateAnImage(doc, new Uri(ConfigTest.BASE_PATH + "TestFontError1.png"));
}
@Test
public void testFontError2() {
String data = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg height='900' width='700'>"
+ " <path d='M296 463q80 0 125 -45t45 -126q0 -130 -73 -217.5t-187 -87.5q-80 0 -125 45.5t-45 127.5q0 126 73 214.5t187 88.5z'" + " stroke='green' stroke-width='3' />"
+ "</svg>";
EsvgDocument doc = new EsvgDocument();
doc.parse(data);
Uri.writeAll(new Uri(ConfigTest.BASE_PATH + "TestFontError2.svg"), data.replace("'", "\""));
ConfigTest.generateAnImage(doc, new Uri(ConfigTest.BASE_PATH + "TestFontError2.png"));
}
@Test
public void testFontError3() {
String data = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg height='900' width='700'>"
+ " <path d='M218 51q78 0 123.5 68t45.5 166q0 115 -103 115q-75 0 -122 -66.5t-47 -167.5q0 -115 103 -115z'" + " stroke='green' stroke-width='3' />" + "</svg>";
EsvgDocument doc = new EsvgDocument();
doc.parse(data);
Uri.writeAll(new Uri(ConfigTest.BASE_PATH + "TestFontError3.svg"), data.replace("'", "\""));
ConfigTest.generateAnImage(doc, new Uri(ConfigTest.BASE_PATH + "TestFontError3.png"));
}
@Test
public void testFontError4() {
String data = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<svg height='900' width='700'>"
+ " <path d='M1011 -78q51 -11 84.5 -50.5t33.5 -93.5q0 -61 -43 -103t-104 -42q-60 0 -102 43q-43 42 -43 102q0 34 16 67q-2 0 -20 -4.5t-36.5 -8t-47 -7.5t-66.5 -6.5t-80 -2.5q-144 0 -248 27.5t-164.5 80.5t-88.5 123t-28 161q0 124 106 240l33 -30q-101 -101 -101 -206\n"
+ "q0 -335 487 -335q304 0 401 123q-39 -10 -68 -10q-95 3 -142 74q-50 -74 -136 -74q-68 0 -120 47t-52 125v92q0 75 -25.5 106t-58.5 31q-49 0 -92 -49t-43 -124q0 -19 3.5 -40.5t13.5 -53t34 -61.5t60 -48l-22 -25q-43 23 -72 58t-41 72.5t-16 63t-4 46.5q0 103 58.5 161.5\n"
+ "t130.5 58.5q24 0 47.5 -8t46.5 -26.5t37 -56t14 -89.5v-87q0 -79 37 -112t81 -33q40 0 69.5 22.5t29.5 57.5v322h83v-275q0 -55 27.5 -84.5t67.5 -29.5q60 0 95.5 52.5t35.5 125.5q0 20 -3 40t-12.5 50t-33 57.5t-57.5 46.5l29 25q57 -44 90.5 -105t33.5 -130\n"
+ "q0 -195 -115 -291zM972 -218q23 0 37.5 15t14.5 37q0 17 -16 32.5t-35 15.5q-30 0 -41.5 -12t-11.5 -36q0 -23 15 -37.5t37 -14.5zM970 -316q41 0 69.5 29t28.5 69q0 38 -19 60q1 -3 1 -13q0 -38 -25 -62.5t-60 -24.5q-33 0 -57.5 22.5t-28.5 55.5q-7 -20 -7 -38\n"
+ "q0 -41 29 -69.5t69 -28.5z'" + " stroke='green' stroke-width='3' />" + "</svg>";
EsvgDocument doc = new EsvgDocument();
doc.parse(data);
Uri.writeAll(new Uri(ConfigTest.BASE_PATH + "TestFontError4.svg"), data.replace("'", "\""));
ConfigTest.generateAnImage(doc, new Uri(ConfigTest.BASE_PATH + "TestFontError4.png"));
}
@Test
public void testFontprintE25() {
Esvg.init();
EsvgFont font = EsvgFont.load(new Uri("FONTS", "FreeSherif.svg", "esvg"));
Weight out = font.render('E', 25);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_E25.png"));
out = font.render('e', 25);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_e25.png"));
out = font.render('É', 25);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_Ecute25.png"));
out = font.render('é', 25);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_ecute25.png"));
out = font.render('p', 25);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_p25.png"));
out = font.render('f', 25);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_f25.png"));
}
@Test
public void testFontprintSimpleText() {
Esvg.init();
EsvgFont font = EsvgFont.load(new Uri("FONTS", "FreeSherif.svg", "esvg"));
Weight out = font.render("Hello, How are you? VA // @ ê É", 100, false);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_Hello.png"));
out = font.render("Hello, How are you? VA // @ ê É", 100, true);
ConfigTest.generateAnImage(out, new Uri(ConfigTest.BASE_PATH + "testFontprint_Hello_withKerning.png"));
}
@Test
public void testFontRead() {
Esvg.init();
EsvgFont font = EsvgFont.load(new Uri("FONTS", "FreeSherif.svg", "esvg"));
}
}

View File

@ -97,4 +97,5 @@ class TestPath {
Uri.writeAll(new Uri(ConfigTest.BASE_PATH + "TestPathstroke.svg"), data.replace("'", "\""));
ConfigTest.generateAnImage(doc, new Uri(ConfigTest.BASE_PATH + "TestPathstroke.png"));
}
}