From 0757b766dd807c4ca0fa8507d36125dd760a4195 Mon Sep 17 00:00:00 2001 From: Honfika Date: Sun, 2 Mar 2014 14:55:52 +0100 Subject: [PATCH] dynamic text: word wrap --- .../flash/tags/DefineEditTextTag.java | 199 +++++++++++------- .../tags/dynamictext/CharacterWithStyle.java | 29 +++ .../tags/dynamictext/DynamicTextModel.java | 77 +++++++ .../tags/dynamictext/GlyphCharacter.java | 37 ++++ .../flash/tags/dynamictext/Paragraph.java | 71 +++++++ .../tags/dynamictext/SameStyleTextRecord.java | 47 +++++ .../flash/tags/dynamictext/TextStyle.java | 52 +++++ .../flash/tags/dynamictext/Word.java | 64 ++++++ 8 files changed, 497 insertions(+), 79 deletions(-) create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/CharacterWithStyle.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/DynamicTextModel.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/GlyphCharacter.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Paragraph.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/SameStyleTextRecord.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/TextStyle.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Word.java diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java index 009100391..0ccc4fbcb 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java @@ -26,6 +26,12 @@ import com.jpexs.decompiler.flash.tags.base.DrawableTag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler; import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.dynamictext.CharacterWithStyle; +import com.jpexs.decompiler.flash.tags.dynamictext.DynamicTextModel; +import com.jpexs.decompiler.flash.tags.dynamictext.Paragraph; +import com.jpexs.decompiler.flash.tags.dynamictext.SameStyleTextRecord; +import com.jpexs.decompiler.flash.tags.dynamictext.TextStyle; +import com.jpexs.decompiler.flash.tags.dynamictext.Word; import com.jpexs.decompiler.flash.tags.text.ParseException; import com.jpexs.decompiler.flash.tags.text.ParsedSymbol; import com.jpexs.decompiler.flash.tags.text.TextLexer; @@ -198,6 +204,7 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { private List getTextWithStyle() { String str = ""; TextStyle style = new TextStyle(); + style.font = getFontTag(); style.fontHeight = fontHeight; if (hasTextColor) { style.textColor = textColor; @@ -219,6 +226,10 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { TextStyle style = styles.peek(); switch (qName) { + case "p": + // todo: parse the following attribute: + // align + break; case "b": style = style.clone(); style.bold = true; @@ -242,9 +253,22 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { style.textColor = new RGBA(Color.decode(color)); } } + String size = attributes.getValue("size"); + if (size != null && size.length() > 0) { + char firstChar = size.charAt(0); + if (firstChar != '+' && firstChar != '-') { + int fontSize = Integer.parseInt(size); + style.fontHeight = fontSize * style.font.getDivider(); + } else { + // todo: parse relative sizes + } + } + // todo: parse the following attributes: + // face, letterSpacing, kerning styles.add(style); break; case "br": + case "sbr": // what's this? CharacterWithStyle cs = new CharacterWithStyle(); cs.character = '\n'; cs.style = style; @@ -739,36 +763,31 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { @Override public void toImage(int frame, int ratio, List tags, Map characters, Stack visited, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { - FontTag font = null; - for (Tag tag : tags) { - if (tag instanceof FontTag) { - if (((FontTag) tag).getFontId() == fontId) { - font = (FontTag) tag; - } - } - } if (hasText) { - List> paragraphs = new ArrayList<>(); - List textRecords = new ArrayList<>(); - paragraphs.add(textRecords); - SameStyleTextRecord tr = null; + DynamicTextModel textModel = new DynamicTextModel(); List txt = getTextWithStyle(); TextStyle lastStyle = null; + boolean lastWasWhiteSpace = false; for (int i = 0; i < txt.size(); i++) { CharacterWithStyle cs = txt.get(i); char c = cs.character; if (c != '\n') { + // create new SameStyleTextRecord for all words and all diffrent style text parts + if (lastWasWhiteSpace && !Character.isWhitespace(c)) { + textModel.newWord(); + lastWasWhiteSpace = false; + } if (cs.style != lastStyle) { lastStyle = cs.style; - tr = new SameStyleTextRecord(); - tr.style = lastStyle; - textRecords.add(tr); + textModel.style = lastStyle; + textModel.newRecord(); } Character nextChar = null; if (i + 1 < txt.size()) { nextChar = txt.get(i + 1).character; } int advance; + FontTag font = lastStyle.font; GLYPHENTRY ge = new GLYPHENTRY(); ge.glyphIndex = font.charToGlyph(tags, c); if (font.hasLayout()) { @@ -783,28 +802,77 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { advance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(fontName, font.getFontStyle(), (int) (lastStyle.fontHeight / SWF.unitDivisor), c, nextChar)); } ge.glyphAdvance = advance; - tr.glyphEntries.add(ge); + textModel.addGlyph(c, ge); + if (Character.isWhitespace(c)) { + lastWasWhiteSpace = true; + } } else { if (multiline) { - textRecords = new ArrayList<>(); - lastStyle = null; - tr = null; - paragraphs.add(textRecords); + textModel.newParagraph(); } } } + + textModel.calculateTexWidths(); + List> lines; + if (multiline && wordWrap) { + lines = new ArrayList<>(); + int lineLength = 0; + for (Paragraph paragraph : textModel.paragraphs) { + List line = new ArrayList<>(); + for (Word word : paragraph.words) { + if (lineLength + word.width <= bounds.getWidth()) { + line.addAll(word.records); + } else { + lines.add(line); + line = new ArrayList<>(); + line.addAll(word.records); + } + } + if (!line.isEmpty()) { + lines.add(line); + } + } + } else { + lines = new ArrayList<>(); + for (Paragraph paragraph : textModel.paragraphs) { + List line = new ArrayList<>(); + for (Word word : paragraph.words) { + for (SameStyleTextRecord tr : word.records) { + line.add(tr); + } + } + lines.add(line); + } + } + + // remove spaces after last word + for (List line : lines) { + boolean removed = true; + while (removed) { + removed = false; + while (line.size() > 0 && line.get(line.size() - 1).glyphEntries.isEmpty()) { + line.remove(line.size() - 1); + removed = true; + } + SameStyleTextRecord lastRecord = line.get(line.size() - 1); + while (lastRecord.glyphEntries.size() > 0 && + Character.isWhitespace(lastRecord.glyphEntries.get(lastRecord.glyphEntries.size() - 1).character)) { + lastRecord.glyphEntries.remove(lastRecord.glyphEntries.size() -1); + removed = true; + } + } + } + + textModel.calculateTexWidths(); + List allTextRecords = new ArrayList<>(); int yOffset = 0; - for (List paragraph : paragraphs) { + for (List line : lines) { yOffset += fontHeight; int width = 0; - for (SameStyleTextRecord tr1 : paragraph) { - int trWidth = 0; - for (GLYPHENTRY ge : tr1.glyphEntries) { - trWidth += ge.glyphAdvance; - } - tr1.width = trWidth; - width += trWidth; + for (SameStyleTextRecord tr : line) { + width += tr.width; } int alignOffset = 0; switch (align) { @@ -821,27 +889,30 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { // todo; break; } - for (SameStyleTextRecord tr1 : paragraph) { - tr1.xOffset = alignOffset; - alignOffset += tr1.width; + for (SameStyleTextRecord tr : line) { + tr.xOffset = alignOffset; + alignOffset += tr.width; } - for (SameStyleTextRecord tr1 : paragraph) { + for (SameStyleTextRecord tr : line) { TEXTRECORD tr2 = new TEXTRECORD(); tr2.styleFlagsHasFont = true; tr2.fontId = fontId; - tr2.textHeight = fontHeight; - if (tr1.style.textColor != null) { + tr2.textHeight = tr.style.fontHeight; + if (tr.style.textColor != null) { tr2.styleFlagsHasColor = true; - tr2.textColorA = tr1.style.textColor; + tr2.textColorA = tr.style.textColor; } // always add xOffset, because no xOffset and 0 xOffset is diffrent in text rendering tr2.styleFlagsHasXOffset = true; - tr2.xOffset = tr1.xOffset; + tr2.xOffset = tr.xOffset; if (yOffset != 0) { tr2.styleFlagsHasYOffset = true; tr2.yOffset = yOffset; } - tr2.glyphEntries = tr1.glyphEntries.toArray(new GLYPHENTRY[tr1.glyphEntries.size()]); + tr2.glyphEntries = new GLYPHENTRY[tr.glyphEntries.size()]; + for (int i = 0; i < tr2.glyphEntries.length; i++) { + tr2.glyphEntries[i] = tr.glyphEntries.get(i).glyphEntry; + } allTextRecords.add(tr2); } } @@ -850,6 +921,18 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { } } + private FontTag getFontTag() { + FontTag font = null; + for (Tag tag : swf.tags) { + if (tag instanceof FontTag) { + if (((FontTag) tag).getFontId() == fontId) { + font = (FontTag) tag; + } + } + } + return font; + } + @Override public Point getImagePos(int frame, Map characters, Stack visited) { return new Point(bounds.Xmin / SWF.unitDivisor, bounds.Ymin / SWF.unitDivisor); @@ -859,46 +942,4 @@ public class DefineEditTextTag extends TextTag implements DrawableTag { public int getNumFrames() { return 1; } - - private class TextStyle { - - public int fontHeight; - - public boolean bold; - - public boolean italic; - - public boolean underlined; - - public RGBA textColor; - - @Override - public TextStyle clone() { - TextStyle result = new TextStyle(); - result.fontHeight = fontHeight; - result.bold = bold; - result.italic = italic; - result.underlined = underlined; - result.textColor = textColor; - return result; - } - } - - private class CharacterWithStyle { - - public char character; - - public TextStyle style; - } - - private class SameStyleTextRecord { - - public TextStyle style; - - public int xOffset; - - public int width; - - public List glyphEntries = new ArrayList<>(); - } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/CharacterWithStyle.java b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/CharacterWithStyle.java new file mode 100644 index 000000000..5903962a7 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/CharacterWithStyle.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.dynamictext; + +/** + * + * + * @author JPEXS + */ +public class CharacterWithStyle { + + public char character; + + public TextStyle style; +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/DynamicTextModel.java b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/DynamicTextModel.java new file mode 100644 index 000000000..f74866881 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/DynamicTextModel.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.dynamictext; + +import com.jpexs.decompiler.flash.types.GLYPHENTRY; +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @author JPEXS + */ +public class DynamicTextModel { + + public List paragraphs = new ArrayList<>(); + + private Paragraph paragraph; + public TextStyle style; + public int width; + + public DynamicTextModel() { + + } + + public void addGlyph(char character, GLYPHENTRY glyphEntry) { + + if (paragraph == null) { + paragraph = new Paragraph(this); + paragraphs.add(paragraph); + } + paragraph.addGlyph(character, glyphEntry); + } + + public void newParagraph() { + + paragraph = null; + } + + public void newWord() { + + if (paragraph != null) { + paragraph.newWord(); + } + } + + public void newRecord() { + + if (paragraph != null) { + paragraph.newRecord(); + } + } + + public int calculateTexWidths() { + + int width = 0; + for (Paragraph p : paragraphs) { + width += p.calculateTexWidths(); + } + this.width = width; + return width; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/GlyphCharacter.java b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/GlyphCharacter.java new file mode 100644 index 000000000..6eaa25ac5 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/GlyphCharacter.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.dynamictext; + +import com.jpexs.decompiler.flash.types.GLYPHENTRY; + +/** + * + * + * @author JPEXS + */ +public class GlyphCharacter { + + public GLYPHENTRY glyphEntry; + + public char character; + + public GlyphCharacter(char character, GLYPHENTRY glyphEntry) { + + this.character = character; + this.glyphEntry = glyphEntry; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Paragraph.java b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Paragraph.java new file mode 100644 index 000000000..319c35993 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Paragraph.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.dynamictext; + +import com.jpexs.decompiler.flash.types.GLYPHENTRY; +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @author JPEXS + */ +public class Paragraph { + + public List words = new ArrayList<>(); + + private Word word; + private DynamicTextModel model; + public int width; + + public Paragraph(DynamicTextModel model) { + + this.model = model; + } + + public void addGlyph(char character, GLYPHENTRY glyphEntry) { + + if (word == null) { + word = new Word(model); + words.add(word); + } + word.addGlyph(character, glyphEntry); + } + + public void newWord() { + + word = null; + } + + public void newRecord() { + + if (word != null) { + word.newRecord(); + } + } + + public int calculateTexWidths() { + + int width = 0; + for (Word w : words) { + width += w.calculateTexWidths(); + } + this.width = width; + return width; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/SameStyleTextRecord.java b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/SameStyleTextRecord.java new file mode 100644 index 000000000..b15a9247d --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/SameStyleTextRecord.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.dynamictext; + +import com.jpexs.decompiler.flash.types.GLYPHENTRY; +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @author JPEXS + */ +public class SameStyleTextRecord { + + public TextStyle style; + + public int xOffset; + + public int width; + + public List glyphEntries = new ArrayList<>(); + + public int calculateTexWidths() { + + int width = 0; + for (GlyphCharacter gc : glyphEntries) { + width += gc.glyphEntry.glyphAdvance; + } + this.width = width; + return width; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/TextStyle.java b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/TextStyle.java new file mode 100644 index 000000000..6d92b5d60 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/TextStyle.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.dynamictext; + +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.types.RGBA; + +/** + * + * + * @author JPEXS + */ +public class TextStyle { + + public FontTag font; + + public int fontHeight; + + public boolean bold; + + public boolean italic; + + public boolean underlined; + + public RGBA textColor; + + @Override + public TextStyle clone() { + TextStyle result = new TextStyle(); + result.font = font; + result.fontHeight = fontHeight; + result.bold = bold; + result.italic = italic; + result.underlined = underlined; + result.textColor = textColor; + return result; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Word.java b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Word.java new file mode 100644 index 000000000..b3e6ebf30 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/dynamictext/Word.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.dynamictext; + +import com.jpexs.decompiler.flash.types.GLYPHENTRY; +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @author JPEXS + */ +public class Word { + + public List records = new ArrayList<>(); + + private SameStyleTextRecord record; + private DynamicTextModel model; + public int width; + + public Word(DynamicTextModel model) { + this.model = model; + } + + public void addGlyph(char character, GLYPHENTRY glyphEntry) { + + if (record == null) { + record = new SameStyleTextRecord(); + record.style = model.style; + records.add(record); + } + record.glyphEntries.add(new GlyphCharacter(character, glyphEntry)); + } + + public void newRecord() { + + record = null; + } + + public int calculateTexWidths() { + + int width = 0; + for (SameStyleTextRecord r : records) { + width += r.calculateTexWidths(); + } + this.width = width; + return width; + } +}