summaryrefslogtreecommitdiff
path: root/src/player/WordsNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/player/WordsNode.cpp')
-rw-r--r--src/player/WordsNode.cpp816
1 files changed, 816 insertions, 0 deletions
diff --git a/src/player/WordsNode.cpp b/src/player/WordsNode.cpp
new file mode 100644
index 0000000..ba67720
--- /dev/null
+++ b/src/player/WordsNode.cpp
@@ -0,0 +1,816 @@
+//
+// libavg - Media Playback Engine.
+// Copyright (C) 2003-2014 Ulrich von Zadow
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// Current versions can be found at www.libavg.de
+//
+
+#include "WordsNode.h"
+#include "OGLSurface.h"
+#include "TypeDefinition.h"
+#include "TextEngine.h"
+
+#include "../base/Logger.h"
+#include "../base/Exception.h"
+#include "../base/ScopeTimer.h"
+#include "../base/XMLHelper.h"
+#include "../base/StringHelper.h"
+#include "../base/MathHelper.h"
+#include "../base/ObjectCounter.h"
+
+#include "../graphics/Filterfill.h"
+#include "../graphics/GLContext.h"
+#include "../graphics/GLTexture.h"
+#include "../graphics/TextureMover.h"
+
+#include <pango/pangoft2.h>
+
+#include <iostream>
+#include <algorithm>
+
+using namespace std;
+
+namespace avg {
+
+void WordsNode::registerType()
+{
+ static const string sDTDElements =
+ "<!ELEMENT span (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ATTLIST span\n"
+ " font_desc CDATA #IMPLIED\n"
+ " font_family CDATA #IMPLIED\n"
+ " face CDATA #IMPLIED\n"
+ " size CDATA #IMPLIED\n"
+ " style CDATA #IMPLIED\n"
+ " weight CDATA #IMPLIED\n"
+ " variant CDATA #IMPLIED\n"
+ " stretch CDATA #IMPLIED\n"
+ " foreground CDATA #IMPLIED\n"
+ " background CDATA #IMPLIED\n"
+ " underline CDATA #IMPLIED\n"
+ " rise CDATA #IMPLIED\n"
+ " strikethrough CDATA #IMPLIED\n"
+ " fallback CDATA #IMPLIED\n"
+ " lang CDATA #IMPLIED\n"
+ " letter_spacing CDATA #IMPLIED\n"
+ " rawtextmode CDATA #IMPLIED >\n"
+
+ "<!ELEMENT b (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT big (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT i (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT s (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT sub (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT sup (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT small (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT tt (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT u (#PCDATA|span|b|big|i|s|sub|sup|small|tt|u)*>\n"
+ "<!ELEMENT br (#PCDATA)*>\n";
+
+ string sChildArray[] = {"#PCDATA", "span", "b", "big", "i", "s", "sup", "sub",
+ "small", "tt", "u", "br"};
+ vector<string> sChildren = vectorFromCArray(sizeof(sChildArray)/sizeof(*sChildArray),
+ sChildArray);
+ TypeDefinition def = TypeDefinition("words", "rasternode",
+ ExportedObject::buildObject<WordsNode>)
+ .addChildren(sChildren)
+ .addDTDElements(sDTDElements)
+ .addArg(Arg<string>("font", "sans"))
+ .addArg(Arg<string>("variant", ""))
+ .addArg(Arg<UTF8String>("text", ""))
+ .addArg(Arg<string>("color", "FFFFFF"))
+ .addArg(Arg<float>("aagamma", 1.0f))
+ .addArg(Arg<float>("fontsize", 15))
+ .addArg(Arg<int>("indent", 0, false))
+ .addArg(Arg<float>("linespacing", 0))
+ .addArg(Arg<string>("alignment", "left"))
+ .addArg(Arg<string>("wrapmode", "word"))
+ .addArg(Arg<bool>("justify", false))
+ .addArg(Arg<bool>("rawtextmode", false, false,
+ offsetof(WordsNode, m_bRawTextMode)))
+ .addArg(Arg<float>("letterspacing", 0))
+ .addArg(Arg<bool>("hint", true))
+ .addArg(Arg<FontStyle>("fontstyle", FontStyle()))
+ ;
+ TypeRegistry::get()->registerType(def);
+}
+
+WordsNode::WordsNode(const ArgList& args)
+ : m_LogicalSize(0,0),
+ m_pFontDescription(0),
+ m_pLayout(0),
+ m_bRenderNeeded(true)
+{
+ m_bParsedText = false;
+ args.setMembers(this);
+
+ m_FontStyle = args.getArgVal<FontStyle>("fontstyle");
+ m_FontStyle.setDefaultedArgs(args);
+#ifdef _WIN32
+ if (m_FontStyle.getFont() == "sans") {
+ m_FontStyle.setFont("Arial");
+ m_FontStyle.setFontVariant("Regular");
+ }
+#endif
+ updateFont();
+ setText(args.getArgVal<UTF8String>("text"));
+
+ ObjectCounter::get()->incRef(&typeid(*this));
+}
+
+WordsNode::~WordsNode()
+{
+ if (m_pFontDescription) {
+ pango_font_description_free(m_pFontDescription);
+ }
+ if (m_pLayout) {
+ g_object_unref(m_pLayout);
+ }
+ ObjectCounter::get()->decRef(&typeid(*this));
+}
+
+void WordsNode::setTextFromNodeValue(const string& sText)
+{
+ // Gives priority to Node Values only if they aren't empty
+ UTF8String sTemp = removeExcessSpaces(sText);
+ if (sTemp.length() != 0) {
+ setText(sText);
+ }
+}
+
+void WordsNode::connectDisplay()
+{
+ RasterNode::connectDisplay();
+ getSurface()->setAlphaGamma(m_FontStyle.getAAGamma());
+}
+
+void WordsNode::connect(CanvasPtr pCanvas)
+{
+ RasterNode::connect(pCanvas);
+ checkReload();
+}
+
+void WordsNode::disconnect(bool bKill)
+{
+ if (m_pFontDescription) {
+ pango_font_description_free(m_pFontDescription);
+ m_pFontDescription = 0;
+ updateFont();
+ }
+ RasterNode::disconnect(bKill);
+}
+
+string WordsNode::getAlignment() const
+{
+ return m_FontStyle.getAlignment();
+}
+
+void WordsNode::setAlignment(const string& sAlign)
+{
+ m_FontStyle.setAlignment(sAlign);
+ updateLayout();
+}
+
+bool WordsNode::getJustify() const
+{
+ return m_FontStyle.getJustify();
+}
+
+void WordsNode::setJustify(bool bJustify)
+{
+ m_FontStyle.setJustify(bJustify);
+ updateLayout();
+}
+
+float WordsNode::getLetterSpacing() const
+{
+ return m_FontStyle.getLetterSpacing();
+}
+
+void WordsNode::setLetterSpacing(float letterSpacing)
+{
+ m_FontStyle.setLetterSpacing(letterSpacing);
+ updateLayout();
+}
+
+bool WordsNode::getHint() const
+{
+ return m_FontStyle.getHint();
+}
+
+void WordsNode::setHint(bool bHint)
+{
+ m_FontStyle.setHint(bHint);
+ updateLayout();
+}
+
+float WordsNode::getWidth() const
+{
+ return AreaNode::getWidth();
+}
+
+void WordsNode::setWidth(float width)
+{
+ AreaNode::setWidth(width);
+ updateLayout();
+}
+
+float WordsNode::getHeight() const
+{
+ return AreaNode::getHeight();
+}
+
+void WordsNode::setHeight(float width)
+{
+ AreaNode::setHeight(width);
+ updateLayout();
+}
+
+glm::vec2 WordsNode::getSize() const
+{
+ return AreaNode::getSize();
+}
+
+void WordsNode::setSize(const glm::vec2& pt)
+{
+ AreaNode::setSize(pt);
+ updateLayout();
+}
+
+glm::vec2 WordsNode::toLocal(const glm::vec2& globalPos) const
+{
+ glm::vec2 localPos = globalPos - getRelViewport().tl - glm::vec2(m_AlignOffset, 0);
+ return getRotatedPivot(localPos, -getAngle(), getPivot());
+}
+
+glm::vec2 WordsNode::toGlobal(const glm::vec2& localPos) const
+{
+ glm::vec2 alignPos = localPos + glm::vec2(m_AlignOffset, 0);
+ glm::vec2 globalPos = getRotatedPivot(alignPos, getAngle(), getPivot());
+ return globalPos + getRelViewport().tl;
+}
+
+const FontStyle& WordsNode::getFontStyle() const
+{
+ return m_FontStyle;
+}
+
+void WordsNode::setFontStyle(const FontStyle& fontStyle)
+{
+ m_FontStyle = fontStyle;
+ updateFont();
+}
+
+const std::string& WordsNode::getFont() const
+{
+ return m_FontStyle.getFont();
+}
+
+void WordsNode::setFont(const std::string& sName)
+{
+ m_FontStyle.setFont(sName);
+ updateFont();
+}
+
+const std::string& WordsNode::getFontVariant() const
+{
+ return m_FontStyle.getFontVariant();
+}
+
+void WordsNode::addFontDir(const std::string& sDir)
+{
+ TextEngine::get(true).addFontDir(sDir);
+ TextEngine::get(false).addFontDir(sDir);
+}
+
+void WordsNode::setFontVariant(const std::string& sVariant)
+{
+ m_FontStyle.setFontVariant(sVariant);
+ updateFont();
+}
+
+const UTF8String& WordsNode::getText() const
+{
+ return m_sRawText;
+}
+
+void WordsNode::setText(const UTF8String& sText)
+{
+ if (sText.length() > 32767) {
+ throw(Exception(AVG_ERR_INVALID_ARGS,
+ string("WordsNode::setText: string too long (")
+ + toString(sText.length()) + ")"));
+ }
+ if (m_sRawText != sText) {
+ m_sRawText = sText;
+ m_sText = m_sRawText;
+ if (m_bRawTextMode) {
+ m_bParsedText = false;
+ updateLayout();
+ } else {
+ setParsedText(sText);
+ }
+ }
+}
+
+const std::string& WordsNode::getColor() const
+{
+ return m_FontStyle.getColor();
+}
+
+void WordsNode::setColor(const string& sColor)
+{
+ m_FontStyle.setColor(sColor);
+}
+
+float WordsNode::getAAGamma() const
+{
+ return m_FontStyle.getAAGamma();
+}
+
+void WordsNode::setAAGamma(float gamma)
+{
+ m_FontStyle.setAAGamma(gamma);
+ if (getState() == Node::NS_CANRENDER) {
+ getSurface()->setAlphaGamma(gamma);
+ }
+}
+
+float WordsNode::getFontSize() const
+{
+ return m_FontStyle.getFontSize();
+}
+
+void WordsNode::setFontSize(float size)
+{
+ m_FontStyle.setFontSize(size);
+ updateFont();
+}
+
+int WordsNode::getIndent() const
+{
+ return m_FontStyle.getIndent();
+}
+
+void WordsNode::setIndent(int indent)
+{
+ m_FontStyle.setIndent(indent);
+ updateLayout();
+}
+
+float WordsNode::getLineSpacing() const
+{
+ return m_FontStyle.getLineSpacing();
+}
+
+void WordsNode::setLineSpacing(float lineSpacing)
+{
+ m_FontStyle.setLineSpacing(lineSpacing);
+ updateLayout();
+}
+
+bool WordsNode::getRawTextMode() const
+{
+ return m_bRawTextMode;
+}
+
+void WordsNode::setRawTextMode(bool rawTextMode)
+{
+ if (rawTextMode != m_bRawTextMode) {
+ m_sText = m_sRawText;
+ if (rawTextMode) {
+ m_bParsedText = false;
+ } else {
+ setParsedText(m_sText);
+ }
+ m_bRawTextMode = rawTextMode;
+ updateLayout();
+ }
+}
+
+glm::vec2 WordsNode::getGlyphPos(int i)
+{
+ PangoRectangle rect = getGlyphRect(i);
+ return glm::vec2(float(rect.x)/PANGO_SCALE, float(rect.y)/PANGO_SCALE);
+}
+
+glm::vec2 WordsNode::getGlyphSize(int i)
+{
+ PangoRectangle rect = getGlyphRect(i);
+ return glm::vec2(float(rect.width)/PANGO_SCALE, float(rect.height)/PANGO_SCALE);
+}
+
+int WordsNode::getNumLines()
+{
+ if(m_sText.length() != 0) {
+ return pango_layout_get_line_count(m_pLayout);
+ }
+ return 0;
+}
+
+PyObject* WordsNode::getCharIndexFromPos(glm::vec2 p)
+{
+ int index;
+ int trailing;
+ gboolean bXyToIndex = pango_layout_xy_to_index(m_pLayout,
+ int(p.x*PANGO_SCALE), int(p.y*PANGO_SCALE), &index, &trailing);
+ if (bXyToIndex) {
+ const char* pText = pango_layout_get_text(m_pLayout);
+ return Py_BuildValue("l",(g_utf8_pointer_to_offset(pText,pText+index)));
+ } else {
+ return Py_BuildValue("");
+ }
+}
+
+std::string WordsNode::getTextAsDisplayed()
+{
+ return pango_layout_get_text(m_pLayout);
+}
+
+glm::vec2 WordsNode::getLineExtents(int line)
+{
+ if (line < 0 || line >= getNumLines()) {
+ throw Exception(AVG_ERR_OUT_OF_RANGE, "WordsNode.getLineExtents: line index "
+ +toString(line)+" is out of range.");
+ }
+ PangoRectangle logical_rect;
+ PangoRectangle ink_rect;
+ PangoLayoutLine *layoutLine = pango_layout_get_line_readonly(m_pLayout, line);
+ pango_layout_line_get_pixel_extents(layoutLine, &ink_rect, &logical_rect);
+ return glm::vec2(float(logical_rect.width), float(logical_rect.height));
+}
+
+void WordsNode::setWrapMode(const string& sWrapMode)
+{
+ m_FontStyle.setWrapMode(sWrapMode);
+ updateLayout();
+}
+
+string WordsNode::getWrapMode() const
+{
+ return m_FontStyle.getWrapMode();
+}
+
+void WordsNode::parseString(PangoAttrList** ppAttrList, char** ppText)
+{
+ UTF8String sTextWithoutBreaks = applyBR(m_sText);
+ bool bOk;
+ GError * pError = 0;
+ bOk = (pango_parse_markup(sTextWithoutBreaks.c_str(),
+ int(sTextWithoutBreaks.length()), 0,
+ ppAttrList, ppText, 0, &pError) != 0);
+ if (!bOk) {
+ string sError;
+ if (getID() != "") {
+ sError = string("Can't parse string in node with id '")+getID()+"' ("
+ +pError->message+")";
+ } else {
+ sError = string("Can't parse string '")+m_sRawText+"' ("+pError->message+")";
+ }
+ throw Exception(AVG_ERR_CANT_PARSE_STRING, sError);
+ }
+
+}
+
+void WordsNode::calcMaskCoords()
+{
+ // Calculate texture coordinates for the mask texture, normalized to
+ // the extents of the text.
+ glm::vec2 normMaskSize;
+ glm::vec2 normMaskPos;
+ glm::vec2 mediaSize = glm::vec2(getMediaSize());
+ glm::vec2 effMaskPos = getMaskPos()-glm::vec2(m_InkOffset);
+ glm::vec2 maskSize = getMaskSize();
+
+ if (maskSize == glm::vec2(0,0)) {
+ normMaskSize = glm::vec2(getSize().x/mediaSize.x, getSize().y/mediaSize.y);
+ normMaskPos = glm::vec2(effMaskPos.x/getSize().x, effMaskPos.y/getSize().y);
+ } else {
+ normMaskSize = glm::vec2(maskSize.x/mediaSize.x, maskSize.y/mediaSize.y);
+ normMaskPos = glm::vec2(effMaskPos.x/getMaskSize().x,
+ effMaskPos.y/getMaskSize().y);
+ }
+/*
+ cerr << "calcMaskCoords" << endl;
+ cerr << " mediaSize: " << getMediaSize() << endl;
+ cerr << " effMaskPos: " << effMaskPos << endl;
+ cerr << " m_AlignOffset: " << m_AlignOffset << endl;
+ cerr << " maskSize: " << maskSize << endl;
+ cerr << " normMaskSize: " << normMaskSize << endl;
+ cerr << " normMaskPos: " << normMaskPos << endl;
+*/
+ getSurface()->setMaskCoords(normMaskPos, normMaskSize);
+}
+
+static ProfilingZoneID UpdateFontProfilingZone("WordsNode: Update font");
+
+void WordsNode::updateFont()
+{
+ {
+ ScopeTimer timer(UpdateFontProfilingZone);
+
+ if (m_pFontDescription) {
+ pango_font_description_free(m_pFontDescription);
+ }
+ TextEngine& engine = TextEngine::get(m_FontStyle.getHint());
+ m_pFontDescription = engine.getFontDescription(m_FontStyle.getFont(),
+ m_FontStyle.getFontVariant());
+ pango_font_description_set_absolute_size(m_pFontDescription,
+ (int)(m_FontStyle.getFontSize() * PANGO_SCALE));
+ }
+ updateLayout();
+}
+
+static ProfilingZoneID UpdateLayoutProfilingZone("WordsNode: Update layout");
+
+void WordsNode::updateLayout()
+{
+ ScopeTimer timer(UpdateLayoutProfilingZone);
+
+ if (m_sText.length() == 0) {
+ m_LogicalSize = IntPoint(0,0);
+ m_bRenderNeeded = true;
+ } else {
+ TextEngine& engine = TextEngine::get(m_FontStyle.getHint());
+ PangoContext* pContext = engine.getPangoContext();
+ pango_context_set_font_description(pContext, m_pFontDescription);
+
+ if (m_pLayout) {
+ g_object_unref(m_pLayout);
+ }
+ m_pLayout = pango_layout_new(pContext);
+
+ PangoAttrList * pAttrList = 0;
+#if PANGO_VERSION > PANGO_VERSION_ENCODE(1,18,2)
+ PangoAttribute * pLetterSpacing = pango_attr_letter_spacing_new
+ (int(m_FontStyle.getLetterSpacing()*1024));
+#endif
+ if (m_bParsedText) {
+ char * pText = 0;
+ parseString(&pAttrList, &pText);
+#if PANGO_VERSION > PANGO_VERSION_ENCODE(1,18,2)
+ // Workaround for pango bug.
+ pango_attr_list_insert_before(pAttrList, pLetterSpacing);
+#endif
+ pango_layout_set_text(m_pLayout, pText, -1);
+ g_free(pText);
+ } else {
+ pAttrList = pango_attr_list_new();
+#if PANGO_VERSION > PANGO_VERSION_ENCODE(1,18,2)
+ pango_attr_list_insert_before(pAttrList, pLetterSpacing);
+#endif
+ pango_layout_set_text(m_pLayout, m_sText.c_str(), -1);
+ }
+ pango_layout_set_attributes(m_pLayout, pAttrList);
+ pango_attr_list_unref(pAttrList);
+
+ pango_layout_set_wrap(m_pLayout, m_FontStyle.getWrapModeVal());
+ pango_layout_set_alignment(m_pLayout, m_FontStyle.getAlignmentVal());
+ pango_layout_set_justify(m_pLayout, m_FontStyle.getJustify());
+ if (getUserSize().x != 0) {
+ pango_layout_set_width(m_pLayout, int(getUserSize().x * PANGO_SCALE));
+ }
+ int indent = m_FontStyle.getIndent() * PANGO_SCALE;
+ pango_layout_set_indent(m_pLayout, indent);
+ if (indent < 0) {
+ // For hanging indentation, we add a tabstop to support lists
+ PangoTabArray* pTabs = pango_tab_array_new_with_positions(1, false,
+ PANGO_TAB_LEFT, -indent);
+ pango_layout_set_tabs(m_pLayout, pTabs);
+ pango_tab_array_free(pTabs);
+ }
+ pango_layout_set_spacing(m_pLayout,
+ (int)(m_FontStyle.getLineSpacing()*PANGO_SCALE));
+ PangoRectangle logical_rect;
+ PangoRectangle ink_rect;
+ pango_layout_get_pixel_extents(m_pLayout, &ink_rect, &logical_rect);
+
+ /*
+ cerr << getID() << endl;
+ cerr << "Ink: " << ink_rect.x << ", " << ink_rect.y << ", "
+ << ink_rect.width << ", " << ink_rect.height << endl;
+ cerr << "Logical: " << logical_rect.x << ", " << logical_rect.y << ", "
+ << logical_rect.width << ", " << logical_rect.height << endl;
+ cerr << "User Size: " << getUserSize() << endl;
+ */
+ m_InkSize.y = ink_rect.height;
+ if (getUserSize().x == 0) {
+ m_InkSize.x = ink_rect.width;
+ } else {
+ m_InkSize.x = int(getUserSize().x);
+ }
+ if (m_InkSize.x == 0) {
+ m_InkSize.x = 1;
+ }
+ if (m_InkSize.y == 0) {
+ m_InkSize.y = 1;
+ }
+ m_LogicalSize.y = logical_rect.height;
+ m_LogicalSize.x = logical_rect.width;
+ m_InkOffset = IntPoint(ink_rect.x-logical_rect.x, ink_rect.y-logical_rect.y);
+ m_bRenderNeeded = true;
+ setViewport(-32767, -32767, -32767, -32767);
+ }
+}
+
+static ProfilingZoneID RenderTextProfilingZone("WordsNode: render text");
+
+void WordsNode::renderText()
+{
+ if (!(getState() == NS_CANRENDER)) {
+ return;
+ }
+ if (m_bRenderNeeded) {
+ if (m_sText.length() != 0) {
+ ScopeTimer timer(RenderTextProfilingZone);
+ TextEngine& engine = TextEngine::get(m_FontStyle.getHint());
+ PangoContext* pContext = engine.getPangoContext();
+ pango_context_set_font_description(pContext, m_pFontDescription);
+ int maxTexSize = GLContext::getMain()->getMaxTexSize();
+ if (m_InkSize.x > maxTexSize || m_InkSize.y > maxTexSize) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "WordsNode size exceeded maximum (Size="
+ + toString(m_InkSize) + ", max=" + toString(maxTexSize) + ")");
+ }
+ GLTexturePtr pTex(new GLTexture(m_InkSize, A8));
+ getSurface()->create(A8, pTex);
+ TextureMoverPtr pMover = TextureMover::create(m_InkSize, A8, GL_DYNAMIC_DRAW);
+
+ BitmapPtr pBmp = pMover->lock();
+ FilterFill<unsigned char>(0).applyInPlace(pBmp);
+ FT_Bitmap bitmap;
+ bitmap.rows = m_InkSize.y;
+ bitmap.width = m_InkSize.x;
+ unsigned char * pLines = pBmp->getPixels();
+ bitmap.pitch = pBmp->getStride();
+ bitmap.buffer = pLines;
+ bitmap.num_grays = 256;
+ bitmap.pixel_mode = ft_pixel_mode_grays;
+
+ PangoRectangle logical_rect;
+ PangoRectangle ink_rect;
+ pango_layout_get_pixel_extents(m_pLayout, &ink_rect, &logical_rect);
+ pango_ft2_render_layout(&bitmap, m_pLayout, -ink_rect.x, -ink_rect.y);
+ switch (m_FontStyle.getAlignmentVal()) {
+ case PANGO_ALIGN_LEFT:
+ m_AlignOffset = 0;
+ break;
+ case PANGO_ALIGN_CENTER:
+ m_AlignOffset = -logical_rect.width/2;
+ break;
+ case PANGO_ALIGN_RIGHT:
+ m_AlignOffset = -logical_rect.width;
+ break;
+ default:
+ AVG_ASSERT(false);
+ }
+
+ pMover->unlock();
+ pMover->moveToTexture(*pTex);
+ newSurface();
+ }
+ m_bRenderNeeded = false;
+ }
+}
+
+void WordsNode::redraw()
+{
+ AVG_ASSERT(m_sText.length() < 32767);
+
+ renderText();
+}
+
+void WordsNode::preRender(const VertexArrayPtr& pVA, bool bIsParentActive,
+ float parentEffectiveOpacity)
+{
+ Node::preRender(pVA, bIsParentActive, parentEffectiveOpacity);
+ if (isVisible()) {
+ redraw();
+ }
+ Pixel32 color = m_FontStyle.getColorVal();
+ if (m_sText.length() != 0 && isVisible()) {
+ renderFX(getSize(), color, false);
+ }
+ calcVertexArray(pVA, color);
+}
+
+static ProfilingZoneID RenderProfilingZone("WordsNode::render");
+
+void WordsNode::render()
+{
+ ScopeTimer timer(RenderProfilingZone);
+ if (m_sText.length() != 0 && isVisible()) {
+ IntPoint offset = m_InkOffset + IntPoint(m_AlignOffset, 0);
+ glm::mat4 transform;
+ if (offset == IntPoint(0,0)) {
+ transform = getTransform();
+ } else {
+ transform = glm::translate(getTransform(), glm::vec3(offset.x, offset.y, 0));
+ }
+ blta8(transform, glm::vec2(getSurface()->getSize()), getEffectiveOpacity(),
+ m_FontStyle.getColorVal(), getBlendMode());
+ }
+}
+
+IntPoint WordsNode::getMediaSize()
+{
+ return m_LogicalSize;
+}
+
+const vector<string>& WordsNode::getFontFamilies()
+{
+ return TextEngine::get(true).getFontFamilies();
+}
+
+const vector<string>& WordsNode::getFontVariants(const string& sFontName)
+{
+ return TextEngine::get(true).getFontVariants(sFontName);
+}
+
+string WordsNode::removeExcessSpaces(const string & sText)
+{
+ string s = sText;
+ string::size_type lastPos = s.npos;
+ string::size_type pos = s.find_first_of(" \n\r");
+ while (pos != s.npos) {
+ s[pos] = ' ';
+ if (pos == lastPos+1) {
+ s.erase(pos, 1);
+ pos--;
+ }
+ lastPos = pos;
+ pos = s.find_first_of(" \n\r", pos+1);
+ }
+ return s;
+}
+
+PangoRectangle WordsNode::getGlyphRect(int i)
+{
+
+ if (i >= int(g_utf8_strlen(m_sText.c_str(), -1)) || i < 0) {
+ throw(Exception(AVG_ERR_INVALID_ARGS,
+ string("getGlyphRect: Index ") + toString(i) + " out of range."));
+ }
+ const char* pText = pango_layout_get_text(m_pLayout);
+ char * pChar = g_utf8_offset_to_pointer(pText, i);
+ int byteOffset = pChar-pText;
+ PangoRectangle rect;
+
+ if (m_pLayout) {
+ pango_layout_index_to_pos(m_pLayout, byteOffset, &rect);
+ } else {
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = 0;
+ rect.height = 0;
+ }
+ return rect;
+}
+
+void WordsNode::setParsedText(const UTF8String& sText)
+{
+ m_sText = removeExcessSpaces(sText);
+
+ // This just does a syntax check and throws an exception if appropriate.
+ // The results are discarded.
+ PangoAttrList * pAttrList = 0;
+ char * pText = 0;
+ parseString(&pAttrList, &pText);
+ pango_attr_list_unref(pAttrList);
+ g_free(pText);
+ m_bParsedText = true;
+ updateLayout();
+}
+
+UTF8String WordsNode::applyBR(const UTF8String& sText)
+{
+ UTF8String sResult(sText);
+ UTF8String sLowerText = toLowerCase(sResult);
+ string::size_type pos=sLowerText.find("<br/>");
+ while (pos != string::npos) {
+ sResult.replace(pos, 5, "\n");
+ sLowerText.replace(pos, 5, "\n");
+ if (sLowerText[pos+1] == ' ') {
+ sLowerText.erase(pos+1, 1);
+ sResult.erase(pos+1, 1);
+ }
+ pos=sLowerText.find("<br/>");
+ }
+ return sResult;
+}
+
+}
+