Added #1799 Text tags editor has new parameter [space xxx] specifying exact letter advance value (add to the font advance + calculated letterspacing)

Changed #802 - FLA export - calculating letterspacing on text with fonts without layout is now optional 
 and turned off by default

XFL converter - do not convert shapes twice
Shape fixer - intersections - ignore problems
This commit is contained in:
Jindra Petřík
2023-11-05 21:11:10 +01:00
parent fd7b9b14f8
commit 9d021819e8
8 changed files with 111 additions and 25 deletions
@@ -966,7 +966,11 @@ public final class Configuration {
@ConfigurationDefaultBoolean(true)
@ConfigurationCategory("ui")
public static ConfigurationItem<Boolean> displayAs3PCodePanel = null;
public static ConfigurationItem<Boolean> displayAs3PCodePanel = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("export")
public static ConfigurationItem<Boolean> flaExportUseMappedFontLayout = null;
private enum OSId {
WINDOWS, OSX, UNIX
@@ -207,7 +207,13 @@ public class Intersections {
)
);
List<Double> roots = poly.getRoots();
List<Double> roots;
try {
roots = poly.getRoots();
} catch (RuntimeException rex) {
//Y values of bounds must be of opposite sign. ??fixme??
return result;
}
for (double s : roots) {
if (0 <= s && s <= 1) {
Polynomial xp = new Polynomial(Arrays.asList(
@@ -252,6 +252,7 @@ public abstract class StaticTextTag extends TextTag {
writer.append("]");
int textHeight = 12;
for (TEXTRECORD rec : textRecords) {
int letterSpacing = 0;
if (rec.styleFlagsHasFont || rec.styleFlagsHasColor || rec.styleFlagsHasXOffset || rec.styleFlagsHasYOffset) {
writer.append("[").newLine();
if (rec.styleFlagsHasFont) {
@@ -264,7 +265,7 @@ public abstract class StaticTextTag extends TextTag {
textHeight = rec.textHeight;
}
if (fnt != null && !ignoreLetterSpacing) {
int letterSpacing = detectLetterSpacing(rec, fnt, textHeight);
letterSpacing = detectLetterSpacing(rec, fnt, textHeight);
if (letterSpacing != 0) {
writer.append("letterspacing ").append(letterSpacing).newLine();
}
@@ -288,7 +289,24 @@ public abstract class StaticTextTag extends TextTag {
if (fnt == null) {
writer.append(AppResources.translate("fontNotFound").replace("%fontId%", Integer.toString(rec.fontId)));
} else {
writer.hilightSpecial(Helper.escapeActionScriptString(rec.getText(fnt)).replace("[", "\\[").replace("]", "\\]"), HighlightSpecialType.TEXT);
for (int i = 0; i < rec.glyphEntries.size(); i++) {
GLYPHENTRY ge = rec.glyphEntries.get(i);
char c = fnt.glyphToChar(ge.glyphIndex);
String sc = ("" + c).replace("[", "\\[").replace("]", "\\]");
writer.hilightSpecial(sc, HighlightSpecialType.TEXT);
if (!ignoreLetterSpacing) {
Character nextChar = null;
if (i + 1 < rec.glyphEntries.size()) {
nextChar = fnt.glyphToChar(ge.glyphIndex);
}
int advance = getAdvance(fnt, ge.glyphIndex, textHeight, c, nextChar);
int delta = ge.glyphAdvance - advance;
if (delta != letterSpacing) {
writer.append("[space " + (delta - letterSpacing) + "]");
}
}
}
}
}
return new HighlightedText(writer);
@@ -458,6 +476,20 @@ public abstract class StaticTextTag extends TextTag {
throw new TextParseException("Invalid translatey value - number expected. Found: " + paramValue, lexer.yyline());
}
break;
case "space":
try {
int space = Integer.parseInt(paramValue);
if (textRecords.isEmpty()) {
throw new TextParseException("space parameter must be placed after some text", lexer.yyline());
}
TEXTRECORD lastRecord = textRecords.get(textRecords.size() - 1);
if (!lastRecord.glyphEntries.isEmpty()) {
lastRecord.glyphEntries.get(lastRecord.glyphEntries.size() - 1).glyphAdvance += space;
}
} catch (NumberFormatException nfe) {
throw new TextParseException("Invalid space value - number expected. Found: " + paramValue, lexer.yyline());
}
break;
default:
throw new TextParseException("Unrecognized parameter name: " + paramName, lexer.yyline());
}
@@ -580,35 +612,54 @@ public abstract class StaticTextTag extends TextTag {
advance = (int) Math.round(((double) textHeight * (font.getGlyphAdvance(glyphIndex) + kerningAdjustment)) / (font.getDivider() * 1024.0));
} else {
String fontName = font.getSystemFontName();
advance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(fontName, font.getFontStyle(), (int) (textHeight / SWF.unitDivisor), c, nextChar));
advance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(fontName, font.getFontStyle(), (int) (textHeight / SWF.unitDivisor), c, nextChar));
}
return advance;
}
public static int detectLetterSpacing(TEXTRECORD textRecord, FontTag font, int textHeight) {
int totalLetterSpacing = 0;
public static int detectLetterSpacing(TEXTRECORD textRecord, FontTag font, int textHeight) {
int minLetterSpacing = Integer.MAX_VALUE;
int numNegatives = 0;
List<GLYPHENTRY> glyphEntries = textRecord.glyphEntries;
if (glyphEntries.isEmpty()) {
if (glyphEntries.size() < 2) {
return 0;
}
for (int i = 0; i < glyphEntries.size(); i++) {
int numMin = 0;
for (int i = 0; i < glyphEntries.size() - 1; i++) {
GLYPHENTRY glyph = glyphEntries.get(i);
GLYPHENTRY nextGlyph = null;
/*GLYPHENTRY nextGlyph = null;
if (i + 1 < glyphEntries.size()) {
nextGlyph = glyphEntries.get(i + 1);
}
}*/
GLYPHENTRY nextGlyph = glyphEntries.get(i + 1);
char c = font.glyphToChar(glyph.glyphIndex);
Character nextChar = nextGlyph == null ? null : font.glyphToChar(nextGlyph.glyphIndex);
//Character nextChar = nextGlyph == null ? null : font.glyphToChar(nextGlyph.glyphIndex);
Character nextChar = font.glyphToChar(nextGlyph.glyphIndex);
int advance = getAdvance(font, glyph.glyphIndex, textHeight, c, nextChar);
int letterSpacing = glyph.glyphAdvance - advance;
totalLetterSpacing += letterSpacing;
//System.err.println("advance between char "+c+" and "+nextChar+": advance = " + advance + " glyphAdvance="+glyph.glyphAdvance+" delta:"+letterSpacing);
if (letterSpacing < 0) {
numNegatives++;
}
if (letterSpacing == minLetterSpacing) {
numMin++;
}
if (letterSpacing < minLetterSpacing) {
minLetterSpacing = letterSpacing;
numMin = 1;
}
}
if (minLetterSpacing < 0 && numNegatives < glyphEntries.size() / 2) { //a hack, use negative letterspacing only when 50% letters use it
minLetterSpacing = 0;
}
if (numMin == 1) {
return 0;
}
return (int) Math.round(totalLetterSpacing / glyphEntries.size());
return minLetterSpacing;
}
@Override
@@ -346,17 +346,22 @@ public abstract class TextTag extends DrawableTag {
if (nextEntry != null) {
kerningAdjustment = font.getGlyphKerningAdjustment(entry.glyphIndex, nextEntry.glyphIndex);
}
defaultAdvance = (int) (Math.round(textHeight * (font.getGlyphAdvance(entry.glyphIndex) + kerningAdjustment) / (1024.0 * font.getDivider())));
defaultAdvance = (int) (Math.round(textHeight * (font.getGlyphAdvance(entry.glyphIndex) + kerningAdjustment) / (1024.0 * font.getDivider())));
} else {
defaultAdvance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(aFont, font.glyphToChar(entry.glyphIndex), nextEntry == null ? null : font.glyphToChar(nextEntry.glyphIndex)));
}
int newLetterSpacing = adv - defaultAdvance;
if (e == 0 || e == rec.glyphEntries.size() - 1) {
if (rec.glyphEntries.size() == 1) {
letterSpacing = 0;
if (!font.hasLayout() && !Configuration.flaExportUseMappedFontLayout.get()) {
letterSpacing = 0;
} else {
int newLetterSpacing = adv - defaultAdvance;
if (e == 0 || e == rec.glyphEntries.size() - 1) {
if (rec.glyphEntries.size() == 1) {
letterSpacing = 0;
}
} else if (newLetterSpacing < letterSpacing) {
letterSpacing = newLetterSpacing;
}
} else if (newLetterSpacing < letterSpacing) {
letterSpacing = newLetterSpacing;
}
x += adv;
}
@@ -570,8 +570,15 @@ public class XFLConverter {
}
private static boolean shapeHasMultiLayers(SWF swf, HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles) throws XMLStreamException {
List<String> layers = getShapeLayers(swf, characters, mat, shapeNum, shapeRecords, fillStyles, lineStyles, false);
return layers.size() > 1;
for (SHAPERECORD rec: shapeRecords) {
if (rec instanceof StyleChangeRecord) {
StyleChangeRecord scr = (StyleChangeRecord) rec;
if (scr.stateNewStyles) {
return true;
}
}
}
return false;
}
private static void convertShape(SWF swf, HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape, boolean useLayers, XFLXmlWriter writer) throws XMLStreamException {
@@ -1636,6 +1643,7 @@ public class XFLConverter {
Reference<Integer> nextClipId = new Reference<>(-1);
writer.writeStartElement("symbols");
for (int ch : characters.keySet()) {
//System.err.println("converting " + ch);
CharacterTag symbol = characters.get(ch);
if ((symbol instanceof ShapeTag) && nonLibraryShapes.contains(symbol.getCharacterId())) {
continue; //shapes with 1 ocurrence and single layer are not added to library