Permalink
Browse files

[TextField] Partial support for HTML formating

Support <br>, <p> and <font>. Only one <font> tag is supported and
only in the beginning of the string.
  • Loading branch information...
aajanki committed Feb 23, 2013
1 parent 128954b commit b8eb8d4191285f5dd8084c55355a84a1648fe27b
View
@@ -341,6 +341,8 @@ ASObject* DefineEditTextTag::instance(Class_base* c) const
//TODO: check
assert_and_throw(bindedTo==NULL);
TextField* ret=new (c->memoryAccount) TextField(c, textData, !NoSelect, ReadOnly);
+ if (HTML)
+ ret->setHtmlText((const char*)InitialText);
return ret;
}
@@ -17,6 +17,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
+#include <libxml++/nodes/element.h>
+#include <libxml++/parsers/domparser.h>
+#include <libxml++/exceptions/exception.h>
+#include <libxml/tree.h>
#include "scripting/flash/text/flashtext.h"
#include "scripting/class.h"
#include "compat.h"
@@ -107,6 +111,8 @@ void TextField::sinit(Class_base* c)
c->setDeclaredMethodByQName("width","",Class<IFunction>::getFunction(TextField::_setWidth),SETTER_METHOD,true);
c->setDeclaredMethodByQName("height","",Class<IFunction>::getFunction(TextField::_getHeight),GETTER_METHOD,true);
c->setDeclaredMethodByQName("height","",Class<IFunction>::getFunction(TextField::_setHeight),SETTER_METHOD,true);
+ c->setDeclaredMethodByQName("htmlText","",Class<IFunction>::getFunction(TextField::_getHtmlText),GETTER_METHOD,true);
+ c->setDeclaredMethodByQName("htmlText","",Class<IFunction>::getFunction(TextField::_setHtmlText),SETTER_METHOD,true);
c->setDeclaredMethodByQName("textHeight","",Class<IFunction>::getFunction(TextField::_getTextHeight),GETTER_METHOD,true);
c->setDeclaredMethodByQName("textWidth","",Class<IFunction>::getFunction(TextField::_getTextWidth),GETTER_METHOD,true);
c->setDeclaredMethodByQName("text","",Class<IFunction>::getFunction(TextField::_getText),GETTER_METHOD,true);
@@ -274,6 +280,21 @@ ASFUNCTIONBODY(TextField,_getTextHeight)
return abstract_i(th->textHeight);
}
+ASFUNCTIONBODY(TextField,_getHtmlText)
+{
+ TextField* th=Class<TextField>::cast(obj);
+ return Class<ASString>::getInstanceS(th->toHtmlText());
+}
+
+ASFUNCTIONBODY(TextField,_setHtmlText)
+{
+ TextField* th=Class<TextField>::cast(obj);
+ tiny_string value;
+ ARG_UNPACK(value);
+ th->setHtmlText(value);
+ return NULL;
+}
+
ASFUNCTIONBODY(TextField,_getText)
{
TextField* th=Class<TextField>::cast(obj);
@@ -397,6 +418,45 @@ void TextField::updateSizes()
textHeight=th;
}
+tiny_string TextField::toHtmlText()
+{
+ xmlpp::DomParser parser;
+ xmlpp::Document *doc = parser.get_document();
+ xmlpp::Element *root = doc->create_root_node("font");
+
+ ostringstream ss;
+ ss << fontSize;
+ root->set_attribute("size", ss.str());
+ root->set_attribute("color", textColor.toString());
+ root->set_attribute("face", font);
+
+ //Split text into paragraphs and wraps them into <p> tags
+ uint32_t para_start = 0;
+ uint32_t para_end;
+ do
+ {
+ para_end = text.find("\n", para_start);
+ if (para_end == text.npos)
+ para_end = text.numChars();
+
+ xmlpp::Element *pNode = root->add_child("p");
+ pNode->add_child_text(text.substr(para_start, para_end));
+ para_start = para_end + 1;
+ } while (para_end < text.numChars());
+
+ xmlBufferPtr buf = xmlBufferCreateSize(4096);
+ xmlNodeDump(buf, doc->cobj(), doc->get_root_node()->cobj(), 0, 0);
+ tiny_string ret = tiny_string((char*)buf->content,true);
+ xmlBufferFree(buf);
+ return ret;
+}
+
+void TextField::setHtmlText(const tiny_string& html)
+{
+ HtmlTextParser parser;
+ parser.parseTextAndFormating(html, this);
+}
+
void TextField::updateText(const tiny_string& new_text)
{
text = new_text;
@@ -448,6 +508,138 @@ void TextField::renderImpl(RenderContext& ctxt) const
defaultRender(ctxt);
}
+void TextField::HtmlTextParser::parseTextAndFormating(const tiny_string& html,
+ TextData *dest)
+{
+ textdata = dest;
+ if (!textdata)
+ return;
+
+ textdata->text = "";
+
+ tiny_string rooted = tiny_string("<root>") + html + tiny_string("</root>");
+ try
+ {
+ parse_memory_raw((const unsigned char*)rooted.raw_buf(), rooted.numBytes());
+ }
+ catch (xmlpp::exception& exc)
+ {
+ LOG(LOG_ERROR, "TextField HTML parser error");
+ return;
+ }
+}
+
+void TextField::HtmlTextParser::on_start_element(const Glib::ustring& name,
+ const xmlpp::SaxParser::AttributeList& attributes)
+{
+ if (!textdata)
+ return;
+
+ if (name == "root")
+ {
+ return;
+ }
+ else if (name == "br")
+ {
+ if (textdata->multiline)
+ textdata->text += "\n";
+
+ }
+ else if (name == "p")
+ {
+ if (textdata->multiline)
+ {
+ if (!textdata->text.empty() &&
+ !textdata->text.endsWith("\n"))
+ textdata->text += "\n";
+ }
+ }
+ else if (name == "font")
+ {
+ if (!textdata->text.empty())
+ {
+ LOG(LOG_NOT_IMPLEMENTED, "Font can be defined only in the beginning");
+ return;
+ }
+
+ for (auto it=attributes.begin(); it!=attributes.end(); ++it)
+ {
+ if (it->name == "face")
+ {
+ textdata->font = it->value;
+ }
+ else if (it->name == "size")
+ {
+ textdata->fontSize = parseFontSize(it->value, textdata->fontSize);
+ }
+ else if (it->name == "color")
+ {
+ textdata->textColor = RGB(tiny_string(it->value));
+ }
+ }
+ }
+ else if (name == "a" || name == "img" || name == "u" ||
+ name == "li" || name == "b" || name == "i" ||
+ name == "span" || name == "textformat" || name == "tab")
+ {
+ LOG(LOG_NOT_IMPLEMENTED, _("Unsupported tag in TextField: ") + name);
+ }
+ else
+ {
+ LOG(LOG_NOT_IMPLEMENTED, _("Unknown tag in TextField: ") + name);
+ }
+}
+
+void TextField::HtmlTextParser::on_end_element(const Glib::ustring& name)
+{
+ if (!textdata)
+ return;
+
+ if (name == "p")
+ {
+ if (textdata->multiline)
+ {
+ if (!textdata->text.empty() &&
+ !textdata->text.endsWith("\n"))
+ textdata->text += "\n";
+ }
+ }
+}
+
+void TextField::HtmlTextParser::on_characters(const Glib::ustring& characters)
+{
+ if (!textdata)
+ return;
+
+ textdata->text += characters;
+}
+
+uint32_t TextField::HtmlTextParser::parseFontSize(const Glib::ustring& sizestr,
+ uint32_t currentFontSize)
+{
+ const char *s = sizestr.c_str();
+ if (!s)
+ return currentFontSize;
+
+ uint32_t basesize = 0;
+ int multiplier = 1;
+ if (s[0] == '+' || s[0] == '-')
+ {
+ // relative size
+ basesize = currentFontSize;
+ if (s[0] == '-')
+ multiplier = -1;
+ }
+
+ int64_t size = basesize + multiplier*g_ascii_strtoll(s, NULL, 10);
+ if (size < 1)
+ size = 1;
+ if (size > G_MAXUINT32)
+ size = G_MAXUINT32;
+
+ return (uint32_t)size;
+}
+
void TextFieldAutoSize ::sinit(Class_base* c)
{
c->setVariableByQName("CENTER","",Class<ASString>::getInstanceS("center"),DECLARED_TRAIT);
@@ -20,6 +20,7 @@
#ifndef SCRIPTING_FLASH_TEXT_FLASHTEXT_H
#define SCRIPTING_FLASH_TEXT_FLASHTEXT_H 1
+#include <libxml++/parsers/saxparser.h>
#include "compat.h"
#include "asobject.h"
#include "scripting/flash/display/flashdisplay.h"
@@ -53,6 +54,24 @@ class ASFont: public ASObject
class TextField: public InteractiveObject, public TextData
{
+private:
+ /*
+ * A parser for the HTML subset supported by TextField.
+ */
+ class HtmlTextParser : public xmlpp::SaxParser {
+ protected:
+ TextData *textdata;
+
+ uint32_t parseFontSize(const Glib::ustring& s, uint32_t currentFontSize);
+ void on_start_element(const Glib::ustring& name, const xmlpp::SaxParser::AttributeList& attributes);
+ void on_end_element(const Glib::ustring& name);
+ void on_characters(const Glib::ustring& characters);
+ public:
+ HtmlTextParser() : textdata(NULL) {};
+ //Stores the text and formating into a TextData object
+ void parseTextAndFormating(const tiny_string& html, TextData *dest);
+ };
+
public:
enum EDIT_TYPE {READ_ONLY, EDITABLE};
private:
@@ -64,16 +83,20 @@ class TextField: public InteractiveObject, public TextData
void updateText(const tiny_string& new_text);
//Computes and changes (text)width and (text)height using Pango
void updateSizes();
+ tiny_string toHtmlText();
EDIT_TYPE type;
public:
TextField(Class_base* c, const TextData& textData=TextData(), bool _selectable=true, bool readOnly=true);
static void sinit(Class_base* c);
static void buildTraits(ASObject* o);
+ void setHtmlText(const tiny_string& html);
ASFUNCTION(appendText);
ASFUNCTION(_getWidth);
ASFUNCTION(_setWidth);
ASFUNCTION(_getHeight);
ASFUNCTION(_setHeight);
+ ASFUNCTION(_getHtmlText);
+ ASFUNCTION(_setHtmlText);
ASFUNCTION(_getText);
ASFUNCTION(_setText);
ASFUNCTION(_setAutoSize);
View
@@ -26,6 +26,8 @@
#include <stdlib.h>
#include <cmath>
#include <cairo.h>
+#include <glib.h>
+#include <iomanip>
#include "exceptions.h"
#include "compat.h"
@@ -1420,3 +1422,29 @@ const nsNameAndKindImpl& nsNameAndKind::getImpl() const
{
return getSys()->getNamespaceFromUniqueId(nsRealId);
}
+
+RGB::RGB(const tiny_string& colorstr):Red(0),Green(0),Blue(0)
+{
+ if (colorstr.empty())
+ return;
+
+ const char *s = colorstr.raw_buf();
+ if (s[0] == '#')
+ s++;
+
+ gint64 color = g_ascii_strtoll(s, NULL, 16);
+ Red = (color >> 16) & 0xFF;
+ Green = (color >> 8) & 0xFF;
+ Blue = color & 0xFF;
+}
+
+tiny_string RGB::toString() const
+{
+ ostringstream ss;
+ ss << "#" << std::hex << std::setfill('0') <<
+ std::setw(2) << (int)Red <<
+ std::setw(2) << (int)Green <<
+ std::setw(2) << (int)Blue;
+
+ return ss.str();
+}
View
@@ -433,10 +433,14 @@ class RGB
RGB(){};
RGB(int r,int g, int b):Red(r),Green(g),Blue(b){};
RGB(uint32_t color):Red((color>>16)&0xFF),Green((color>>8)&0xFF),Blue(color&0xFF){}
+ //Parses a color from hex triplet string #RRGGBB
+ RGB(const tiny_string& colorstr);
UI8 Red;
UI8 Green;
UI8 Blue;
uint32_t toUInt() const { return Blue + (Green<<8) + (Red<<16); }
+ //Return a string representation in #RRGGBB format
+ tiny_string toString() const;
};
class RGBA
View
@@ -349,6 +349,12 @@ friend std::ostream& operator<<(std::ostream& s, const tiny_string& r);
{
return strncmp(buf,o,strlen(o)) == 0;
}
+ bool endsWith(const char* o) const
+ {
+ size_t olen = strlen(o);
+ return (numBytes() >= olen) &&
+ (strncmp(buf+numBytes()-olen,o,olen) == 0);
+ }
/* idx is an index of utf-8 characters */
uint32_t charAt(uint32_t idx) const
{
@@ -30,6 +30,13 @@
Tests.assertEquals(0xFF00FF,field.getTextFormat().color, "TextField.setTextFormat with null does not change");
Tests.assertEquals(0,field.textColor, "TextField.setTextFormat does not change TextField.textColor");
+
+ field = new TextField();
+ field.htmlText = "<font face='Arial' size='30' color='#123456'>aaa</font>";
+ Tests.assertEquals(0x123456, field.getTextFormat().color, "HTML formating: textColor");
+ Tests.assertEquals(30, field.getTextFormat().size, "HTML formating: font size");
+ Tests.assertEquals("Arial", field.getTextFormat().font, "HTML formating: font face");
+
Tests.report(visual, this.name);
}
]]>

0 comments on commit b8eb8d4

Please sign in to comment.