MythTV  0.26-pre
Text.cpp
Go to the documentation of this file.
00001 /* Text.cpp
00002 
00003    Copyright (C)  David C. J. Matthews 2004, 2008  dm at prolingua.co.uk
00004 
00005    This program is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU General Public License
00007    as published by the Free Software Foundation; either version 2
00008    of the License, or (at your option) any later version.
00009 
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013    GNU General Public License for more details.
00014 
00015    You should have received a copy of the GNU General Public License
00016    along with this program; if not, write to the Free Software
00017    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00018    Or, point your browser to http://www.gnu.org/copyleft/gpl.html
00019 
00020 */
00021 #include "compat.h"
00022 
00023 #include "Text.h"
00024 #include "Visible.h"
00025 #include "Presentable.h"
00026 #include "Ingredients.h"
00027 #include "Root.h"
00028 #include "BaseClasses.h"
00029 #include "ParseNode.h"
00030 #include "ASN1Codes.h"
00031 #include "Engine.h"
00032 #include "Logging.h"
00033 #include "freemheg.h"
00034 
00035 MHText::MHText()
00036 {
00037     m_nCharSet = -1;
00038     m_HorizJ = m_VertJ = Start;
00039     m_LineOrientation = Horizontal;
00040     m_StartCorner = UpperLeft;
00041     m_fTextWrap = false;
00042     m_pDisplay = NULL;
00043     m_fNeedsRedraw = false;
00044 }
00045 
00046 MHText::MHText(const MHText &ref): MHVisible(ref) // Copy constructor for cloning.
00047 {
00048     m_OrigFont.Copy(ref.m_OrigFont);
00049     m_OriginalFontAttrs.Copy(ref.m_OriginalFontAttrs);
00050     m_OriginalTextColour.Copy(ref.m_OriginalTextColour);
00051     m_OriginalBgColour.Copy(ref.m_OriginalBgColour);
00052     m_nCharSet = ref.m_nCharSet;
00053     m_HorizJ = ref.m_HorizJ;
00054     m_VertJ = ref.m_VertJ;
00055     m_LineOrientation = ref.m_LineOrientation;
00056     m_StartCorner = ref.m_StartCorner;
00057     m_fTextWrap = ref.m_fTextWrap;
00058     m_pDisplay = NULL;
00059     m_fNeedsRedraw = ref.m_fNeedsRedraw;
00060 }
00061 
00062 MHText::~MHText()
00063 {
00064     delete(m_pDisplay);
00065 }
00066 
00067 
00068 void MHText::Initialise(MHParseNode *p, MHEngine *engine)
00069 {
00070     MHVisible::Initialise(p, engine);
00071     // Font and attributes.
00072     MHParseNode *pFontBody = p->GetNamedArg(C_ORIGINAL_FONT);
00073 
00074     if (pFontBody)
00075     {
00076         m_OrigFont.Initialise(pFontBody->GetArgN(0), engine);
00077     }
00078 
00079     MHParseNode *pFontAttrs = p->GetNamedArg(C_FONT_ATTRIBUTES);
00080 
00081     if (pFontAttrs)
00082     {
00083         pFontAttrs->GetArgN(0)->GetStringValue(m_OriginalFontAttrs);
00084     }
00085 
00086     // Colours
00087     MHParseNode *pTextColour = p->GetNamedArg(C_TEXT_COLOUR);
00088 
00089     if (pTextColour)
00090     {
00091         m_OriginalTextColour.Initialise(pTextColour->GetArgN(0), engine);
00092     }
00093 
00094     MHParseNode *pBGColour = p->GetNamedArg(C_BACKGROUND_COLOUR);
00095 
00096     if (pBGColour)
00097     {
00098         m_OriginalBgColour.Initialise(pBGColour->GetArgN(0), engine);
00099     }
00100 
00101     // Character set
00102     MHParseNode *pChset = p->GetNamedArg(C_CHARACTER_SET);
00103 
00104     if (pChset)
00105     {
00106         m_nCharSet = pChset->GetArgN(0)->GetIntValue();
00107     }
00108 
00109     // Justification
00110     MHParseNode *pHJust = p->GetNamedArg(C_HORIZONTAL_JUSTIFICATION);
00111 
00112     if (pHJust)
00113     {
00114         m_HorizJ = (enum Justification)pHJust->GetArgN(0)->GetEnumValue();
00115     }
00116 
00117     MHParseNode *pVJust = p->GetNamedArg(C_VERTICAL_JUSTIFICATION);
00118 
00119     if (pVJust)
00120     {
00121         m_VertJ = (enum Justification)pVJust->GetArgN(0)->GetEnumValue();
00122     }
00123 
00124     // Line orientation
00125     MHParseNode *pLineO = p->GetNamedArg(C_LINE_ORIENTATION);
00126 
00127     if (pLineO)
00128     {
00129         m_LineOrientation = (enum LineOrientation)pLineO->GetArgN(0)->GetEnumValue();
00130     }
00131 
00132     // Start corner
00133     MHParseNode *pStartC = p->GetNamedArg(C_START_CORNER);
00134 
00135     if (pStartC)
00136     {
00137         m_StartCorner = (enum StartCorner)pStartC->GetArgN(0)->GetEnumValue();
00138     }
00139 
00140     // Text wrapping
00141     MHParseNode *pTextWrap = p->GetNamedArg(C_TEXT_WRAPPING);
00142 
00143     if (pTextWrap)
00144     {
00145         m_fTextWrap = pTextWrap->GetArgN(0)->GetBoolValue();
00146     }
00147 
00148     m_pDisplay = engine->GetContext()->CreateText();
00149     m_fNeedsRedraw = true;
00150 }
00151 
00152 static const char *rchJustification[] =
00153 {
00154     "start", // 1
00155     "end",
00156     "centre",
00157     "justified" // 4
00158 };
00159 
00160 // Look up the Justification. Returns zero if it doesn't match.  Used in the text parser only.
00161 int MHText::GetJustification(const char *str)
00162 {
00163     for (int i = 0; i < (int)(sizeof(rchJustification) / sizeof(rchJustification[0])); i++)
00164     {
00165         if (strcasecmp(str, rchJustification[i]) == 0)
00166         {
00167             return (i + 1);    // Numbered from 1
00168         }
00169     }
00170 
00171     return 0;
00172 }
00173 
00174 static const char *rchlineOrientation[] =
00175 {
00176     "vertical", // 1
00177     "horizontal"
00178 };
00179 
00180 int MHText::GetLineOrientation(const char *str)
00181 {
00182     for (int i = 0; i < (int)(sizeof(rchlineOrientation) / sizeof(rchlineOrientation[0])); i++)
00183     {
00184         if (strcasecmp(str, rchlineOrientation[i]) == 0)
00185         {
00186             return (i + 1);
00187         }
00188     }
00189 
00190     return 0;
00191 }
00192 
00193 static const char *rchStartCorner[] =
00194 {
00195     "upper-left", // 1
00196     "upper-right",
00197     "lower-left",
00198     "lower-right" // 4
00199 };
00200 
00201 int MHText::GetStartCorner(const char *str)
00202 {
00203     for (int i = 0; i < (int)(sizeof(rchStartCorner) / sizeof(rchStartCorner[0])); i++)
00204     {
00205         if (strcasecmp(str, rchStartCorner[i]) == 0)
00206         {
00207             return (i + 1);
00208         }
00209     }
00210 
00211     return 0;
00212 }
00213 
00214 void MHText::PrintMe(FILE *fd, int nTabs) const
00215 {
00216     PrintTabs(fd, nTabs);
00217     fprintf(fd, "{:Text ");
00218     MHVisible::PrintMe(fd, nTabs + 1);
00219 
00220     if (m_OrigFont.IsSet())
00221     {
00222         PrintTabs(fd, nTabs + 1);
00223         fprintf(fd, ":OrigFont ");
00224         m_OrigFont.PrintMe(fd, nTabs + 1);
00225         fprintf(fd, "\n");
00226     }
00227 
00228     if (m_OriginalFontAttrs.Size() > 0)
00229     {
00230         PrintTabs(fd, nTabs + 1);
00231         fprintf(fd, ":FontAttributes ");
00232         m_OriginalFontAttrs.PrintMe(fd, nTabs + 1);
00233         fprintf(fd, "\n");
00234     }
00235 
00236     if (m_OriginalTextColour.IsSet())
00237     {
00238         PrintTabs(fd, nTabs + 1);
00239         fprintf(fd, ":TextColour ");
00240         m_OriginalTextColour.PrintMe(fd, nTabs + 1);
00241         fprintf(fd, "\n");
00242     }
00243 
00244     if (m_OriginalBgColour.IsSet())
00245     {
00246         PrintTabs(fd, nTabs + 1);
00247         fprintf(fd, ":BackgroundColour ");
00248         m_OriginalBgColour.PrintMe(fd, nTabs + 1);
00249         fprintf(fd, "\n");
00250     }
00251 
00252     if (m_nCharSet >= 0)
00253     {
00254         PrintTabs(fd, nTabs + 1);
00255         fprintf(fd, ":CharacterSet %d\n", m_nCharSet);
00256     }
00257 
00258     if (m_HorizJ != Start)
00259     {
00260         PrintTabs(fd, nTabs + 1);
00261         fprintf(fd, ":HJustification %s\n", rchJustification[m_HorizJ-1]);
00262     }
00263 
00264     if (m_VertJ != Start)
00265     {
00266         PrintTabs(fd, nTabs + 1);
00267         fprintf(fd, ":VJustification %s\n", rchJustification[m_VertJ-1]);
00268     }
00269 
00270     if (m_LineOrientation != Horizontal)
00271     {
00272         PrintTabs(fd, nTabs + 1);
00273         fprintf(fd, ":LineOrientation %s\n", rchlineOrientation[m_LineOrientation-1]);
00274     }
00275 
00276     if (m_StartCorner != UpperLeft)
00277     {
00278         PrintTabs(fd, nTabs + 1);
00279         fprintf(fd, ":StartCorner %s\n", rchStartCorner[m_StartCorner-1]);
00280     }
00281 
00282     if (m_fTextWrap)
00283     {
00284         PrintTabs(fd, nTabs + 1);
00285         fprintf(fd, ":TextWrapping true\n");
00286     }
00287 
00288     PrintTabs(fd, nTabs);
00289     fprintf(fd, "}\n");
00290 }
00291 
00292 void MHText::Preparation(MHEngine *engine)
00293 {
00294     if (m_fAvailable)
00295     {
00296         return;
00297     }
00298 
00299     // Set the colours and font up from the originals if specified otherwise use the application defaults.
00300     //  if (m_OrigFont.IsSet()) m_Font.Copy(m_OrigFont);
00301     //  else m_Font.Copy(engine->m_DefaultFont);
00302     if (m_OriginalTextColour.IsSet())
00303     {
00304         m_textColour.Copy(m_OriginalTextColour);
00305     }
00306     else
00307     {
00308         engine->GetDefaultTextColour(m_textColour);
00309     }
00310 
00311     if (m_OriginalBgColour.IsSet())
00312     {
00313         m_bgColour.Copy(m_OriginalBgColour);
00314     }
00315     else
00316     {
00317         engine->GetDefaultBGColour(m_bgColour);
00318     }
00319 
00320     if (m_OriginalFontAttrs.Size() > 0)
00321     {
00322         m_fontAttrs.Copy(m_OriginalFontAttrs);
00323     }
00324     else
00325     {
00326         engine->GetDefaultFontAttrs(m_fontAttrs);
00327     }
00328 
00329     MHVisible::Preparation(engine);
00330 
00331     m_pDisplay->SetSize(m_nBoxWidth, m_nBoxHeight);
00332     m_fNeedsRedraw = true;
00333 }
00334 
00335 // Content preparation.  If it's included we can set up the content.
00336 void MHText::ContentPreparation(MHEngine *engine)
00337 {
00338     MHVisible::ContentPreparation(engine);
00339 
00340     if (m_ContentType == IN_NoContent)
00341     {
00342         MHERROR("Text object must have content");
00343     }
00344 
00345     if (m_ContentType == IN_IncludedContent)
00346     {
00347         CreateContent(m_IncludedContent.Bytes(), m_IncludedContent.Size(), engine);
00348     }
00349 }
00350 
00351 // Called when external content is available.
00352 void MHText::ContentArrived(const unsigned char *data, int length, MHEngine *engine)
00353 {
00354     CreateContent(data, length, engine);
00355     // Now signal that the content is available.
00356     engine->EventTriggered(this, EventContentAvailable);
00357     m_fNeedsRedraw = true;
00358 }
00359 
00360 //
00361 void MHText::CreateContent(const unsigned char *p, int s, MHEngine *engine)
00362 {
00363     m_Content.Copy(MHOctetString((const char *)p, s));
00364     engine->Redraw(GetVisibleArea()); // Have to redraw if the content has changed.
00365     m_fNeedsRedraw = true;
00366     //  fprintf(fd, "Text content is now "); m_Content.PrintMe(0); fprintf(fd, "\n");
00367 }
00368 
00369 void MHText::SetTextColour(const MHColour &colour, MHEngine *engine)
00370 {
00371     m_textColour.Copy(colour);
00372     m_fNeedsRedraw = true;
00373     engine->Redraw(GetVisibleArea());
00374 }
00375 
00376 void MHText::SetBackgroundColour(const MHColour &colour, MHEngine *engine)
00377 {
00378     m_bgColour.Copy(colour);
00379     // Setting the background colour doesn't affect the text image but we have to
00380     // redraw it onto the display.
00381     engine->Redraw(GetVisibleArea());
00382 }
00383 
00384 void MHText::SetFontAttributes(const MHOctetString &fontAttrs, MHEngine *engine)
00385 {
00386     m_fontAttrs.Copy(fontAttrs);
00387     m_fNeedsRedraw = true;
00388     engine->Redraw(GetVisibleArea());
00389 }
00390 
00391 // UK MHEG. Interpret the font attributes.
00392 static void InterpretAttributes(const MHOctetString &attrs, int &style, int &size, int &lineSpace, int &letterSpace)
00393 {
00394     // Set the defaults.
00395     style = 0;
00396     size = 0x18;
00397     lineSpace = 0x18;
00398     letterSpace = 0;
00399 
00400     if (attrs.Size() == 5)   // Short form.
00401     {
00402         style = attrs.GetAt(0); // Only the bottom nibble is significant.
00403         size = attrs.GetAt(1);
00404         lineSpace = attrs.GetAt(2);
00405         // Is this big-endian or little-endian?  Assume big.
00406         letterSpace = attrs.GetAt(3) * 256 + attrs.GetAt(4);
00407 
00408         if (letterSpace > 32767)
00409         {
00410             letterSpace -= 65536;    // Signed.
00411         }
00412     }
00413     else   // Textual form.
00414     {
00415         const unsigned char *str = attrs.Bytes();
00416         char *p = (char *)str;
00417         char *q = strchr(p, '.'); // Find the terminating dot
00418 
00419         if (q != NULL)   // plain, italic etc.
00420         {
00421             if (q - p == 6 && strncmp(p, "italic", 6) == 0)
00422             {
00423                 style = 1;
00424             }
00425             else if (q - p == 4 && strncmp(p, "bold", 4) == 0)
00426             {
00427                 style = 2;
00428             }
00429             else if (q - p == 11 && strncmp(p, "bold-italic", 11) == 0)
00430             {
00431                 style = 3;
00432             }
00433 
00434             // else it's plain.
00435             p = q + 1;
00436             q = strchr(p, '.'); // Find the next dot.
00437         }
00438 
00439         if (q != NULL)   // Size
00440         {
00441             size = atoi(p);
00442 
00443             if (size == 0)
00444             {
00445                 size = 0x18;
00446             }
00447 
00448             p = q + 1;
00449             q = strchr(p, '.'); // Find the next dot.
00450         }
00451 
00452         if (q != NULL)   // lineSpacing
00453         {
00454             lineSpace = atoi(p);
00455 
00456             if (lineSpace == 0)
00457             {
00458                 size = 0x18;
00459             }
00460 
00461             p = q + 1;
00462             q = strchr(p, '.'); // Find the next dot.
00463         }
00464 
00465         if (q != NULL)   // letter spacing.  May be zero or negative
00466         {
00467             letterSpace = atoi(p);
00468         }
00469     }
00470 }
00471 
00472 // We break the text up into pieces of line segment.
00473 class MHTextItem
00474 {
00475   public:
00476     MHTextItem();
00477     MHOctetString m_Text; // UTF-8 text
00478     QString m_Unicode; // Unicode text
00479     int m_nUnicode; // Number of characters in it
00480     int m_Width; // Size of this block
00481     MHRgba m_Colour; // Colour of the text
00482     int m_nTabCount; // Number of tabs immediately before this (usually zero)
00483 
00484     // Generate new items inheriting properties from the previous
00485     MHTextItem *NewItem();
00486 };
00487 
00488 MHTextItem::MHTextItem()
00489 {
00490     m_nUnicode = 0;
00491     m_Width = 0; // Size of this block
00492     m_Colour = MHRgba(0, 0, 0, 255);
00493     m_nTabCount = 0;
00494 }
00495 
00496 MHTextItem *MHTextItem::NewItem()
00497 {
00498     MHTextItem *pItem = new MHTextItem;
00499     pItem->m_Colour = m_Colour;
00500     return pItem;
00501 }
00502 
00503 // A line consists of one or more sequences of text items.
00504 class MHTextLine
00505 {
00506   public:
00507     MHTextLine(): m_nLineWidth(0), m_nLineHeight(0), m_nDescent(0) {}
00508     ~MHTextLine();
00509     MHSequence <MHTextItem *> m_Items;
00510     int m_nLineWidth;
00511     int m_nLineHeight;
00512     int m_nDescent;
00513 };
00514 
00515 MHTextLine::~MHTextLine()
00516 {
00517     for (int i = 0; i < m_Items.Size(); i++)
00518     {
00519         delete(m_Items.GetAt(i));
00520     }
00521 }
00522 
00523 // Tabs are set every 56 points (45 pixels).
00524 #define TABSTOP 45
00525 
00526 // I attempted to use QSimpleRichText but that does not give sufficient fine control over
00527 // the layout.  UK MHEG specifies the use of the Tiresias font and broadcasters appear to
00528 // assume that all MHEG applications will lay the text out in the same way.
00529 
00530 // Recreate the image.
00531 void MHText::Redraw()
00532 {
00533     if (! m_fRunning || !m_pDisplay)
00534     {
00535         return;
00536     }
00537 
00538     if (m_nBoxWidth == 0 || m_nBoxHeight == 0)
00539     {
00540         return;    // Can't draw zero sized boxes.
00541     }
00542 
00543     m_pDisplay->SetSize(m_nBoxWidth, m_nBoxHeight);
00544     m_pDisplay->Clear();
00545 
00546     MHRgba textColour = GetColour(m_textColour);
00547     // Process any escapes in the text and construct the text arrays.
00548     MHSequence <MHTextLine *> theText;
00549     // Set up the first item on the first line.
00550     MHTextItem *pCurrItem = new MHTextItem;
00551     MHTextLine *pCurrLine = new MHTextLine;
00552     pCurrLine->m_Items.Append(pCurrItem);
00553     theText.Append(pCurrLine);
00554     MHStack <MHRgba> m_ColourStack; // Stack to handle nested colour codes.
00555     m_ColourStack.Push(textColour);
00556     pCurrItem->m_Colour = textColour;
00557 
00558     int i = 0;
00559 
00560     while (i < m_Content.Size())
00561     {
00562         unsigned char ch = m_Content.GetAt(i++);
00563 
00564         if (ch == '\t')   // Tab - start a new item if we have any text in the existing one.
00565         {
00566             if (pCurrItem->m_Text.Size() != 0)
00567             {
00568                 pCurrItem = pCurrItem->NewItem();
00569                 pCurrLine->m_Items.Append(pCurrItem);
00570             }
00571 
00572             pCurrItem->m_nTabCount++;
00573         }
00574 
00575         else if (ch == '\r')   // CR - line break.
00576         {
00577             // TODO: Two CRs next to one another are treated as </P> rather than <BR><BR>
00578             // This should also include the sequence CRLFCRLF.
00579             pCurrLine = new MHTextLine;
00580             theText.Append(pCurrLine);
00581             pCurrItem = pCurrItem->NewItem();
00582             pCurrLine->m_Items.Append(pCurrItem);
00583         }
00584 
00585         else if (ch == 0x1b)   // Escape - special codes.
00586         {
00587             if (i == m_Content.Size())
00588             {
00589                 break;
00590             }
00591 
00592             unsigned char code = m_Content.GetAt(i);
00593             // The only codes we are interested in are the start and end of colour.
00594             // TODO: We may also need "bold" and some hypertext colours.
00595 
00596             if (code >= 0x40 && code <= 0x5e)   // Start code
00597             {
00598                 // Start codes are followed by a parameter count and a number of parameter bytes.
00599                 if (++i == m_Content.Size())
00600                 {
00601                     break;
00602                 }
00603 
00604                 unsigned char paramCount = m_Content.GetAt(i);
00605                 i++;
00606 
00607                 if (code == 0x43 && paramCount == 4 && i + paramCount <= m_Content.Size())
00608                 {
00609                     // Start of colour.
00610                     if (pCurrItem->m_Text.Size() != 0)
00611                     {
00612                         pCurrItem = pCurrItem->NewItem();
00613                         pCurrLine->m_Items.Append(pCurrItem);
00614                     }
00615 
00616                     pCurrItem->m_Colour = MHRgba(m_Content.GetAt(i), m_Content.GetAt(i + 1),
00617                                                  m_Content.GetAt(i + 2), 255 - m_Content.GetAt(i + 3));
00618                     // Push this colour onto the colour stack.
00619                     m_ColourStack.Push(pCurrItem->m_Colour);
00620                 }
00621                 else
00622                 {
00623                     MHLOG(MHLogWarning, QString("Unknown text escape code 0x%1").arg(code, 2, 16));
00624                 }
00625 
00626                 i += paramCount; // Skip the parameters
00627             }
00628             else if (code >= 0x60 && code <= 0x7e)   // End code.
00629             {
00630                 i++;
00631 
00632                 if (code == 0x63)
00633                 {
00634                     if (m_ColourStack.Size() > 1)
00635                     {
00636                         m_ColourStack.Pop();
00637 
00638                         // Start a new item since we're using a new colour.
00639                         if (pCurrItem->m_Text.Size() != 0)
00640                         {
00641                             pCurrItem = pCurrItem->NewItem();
00642                             pCurrLine->m_Items.Append(pCurrItem);
00643                         }
00644 
00645                         // Set the subsequent text in the colour we're using now.
00646                         pCurrItem->m_Colour = m_ColourStack.Top();
00647                     }
00648                 }
00649             }
00650         }
00651 
00652         else if (ch <= 0x1f)
00653         {
00654             // Certain characters including LF and the marker codes between 0x1c and 0x1f are
00655             // explicitly intended to be ignored.  Include all the other codes.
00656         }
00657 
00658         else   // Add to the current text.
00659         {
00660             int nStart = i - 1;
00661 
00662             while (i < m_Content.Size() && m_Content.GetAt(i) >= 0x20)
00663             {
00664                 i++;
00665             }
00666 
00667             pCurrItem->m_Text.Append(MHOctetString(m_Content, nStart, i - nStart));
00668         }
00669     }
00670 
00671     // Set up the initial attributes.
00672     int style, size, lineSpace, letterSpace;
00673     InterpretAttributes(m_fontAttrs, style, size, lineSpace, letterSpace);
00674     // Create a font with this information.
00675     m_pDisplay->SetFont(size, (style & 2) != 0, (style & 1) != 0);
00676 
00677     // Calculate the layout of each section.
00678     for (i = 0; i < theText.Size(); i++)
00679     {
00680         MHTextLine *pLine = theText.GetAt(i);
00681         pLine->m_nLineWidth = 0;
00682 
00683         for (int j = 0; j < pLine->m_Items.Size(); j++)
00684         {
00685             MHTextItem *pItem = pLine->m_Items.GetAt(j);
00686 
00687             // Set any tabs.
00688             for (int k = 0; k < pItem->m_nTabCount; k++)
00689             {
00690                 pLine->m_nLineWidth += TABSTOP - pLine->m_nLineWidth % TABSTOP;
00691             }
00692 
00693             if (pItem->m_Unicode.isEmpty())   // Convert UTF-8 to Unicode.
00694             {
00695                 int s = pItem->m_Text.Size();
00696                 pItem->m_Unicode = QString::fromUtf8((const char *)pItem->m_Text.Bytes(), s);
00697                 pItem->m_nUnicode = pItem->m_Unicode.length();
00698             }
00699 
00700             // Fit the text onto the line.
00701             int nFullText = pItem->m_nUnicode;
00702             // Get the box size and update pItem->m_nUnicode to the number that will fit.
00703             QRect rect = m_pDisplay->GetBounds(pItem->m_Unicode, pItem->m_nUnicode, m_nBoxWidth - pLine->m_nLineWidth);
00704 
00705             if (nFullText != pItem->m_nUnicode && m_fTextWrap)   // Doesn't fit, we have to word-wrap.
00706             {
00707                 int nTruncated = pItem->m_nUnicode; // Just in case.
00708                 // Now remove characters until we find a word-break character.
00709                 while (pItem->m_nUnicode > 0 && pItem->m_Unicode[pItem->m_nUnicode] != ' ')
00710                 {
00711                     pItem->m_nUnicode--;
00712                 }
00713 
00714                 // If there are now word-break characters we truncate the text.
00715                 if (pItem->m_nUnicode == 0)
00716                 {
00717                     pItem->m_nUnicode = nTruncated;
00718                 }
00719 
00720                 // Special case to avoid infinite loop if the box is very narrow.
00721                 if (pItem->m_nUnicode == 0)
00722                 {
00723                     pItem->m_nUnicode = 1;
00724                 }
00725 
00726                 // We need to move the text we've cut off this line into a new line.
00727                 int nNewWidth = nFullText - pItem->m_nUnicode;
00728                 int nNewStart = pItem->m_nUnicode;
00729 
00730                 // Remove any spaces at the start of the new section.
00731                 while (nNewWidth != 0 && pItem->m_Unicode[nNewStart] == ' ')
00732                 {
00733                     nNewStart++;
00734                     nNewWidth--;
00735                 }
00736 
00737                 if (nNewWidth != 0)
00738                 {
00739                     // Create a new line from the extra text.
00740                     MHTextLine *pNewLine = new MHTextLine;
00741                     theText.InsertAt(pNewLine, i + 1);
00742                     // The first item on the new line is the rest of the text.
00743                     MHTextItem *pNewItem = pItem->NewItem();
00744                     pNewLine->m_Items.Append(pNewItem);
00745                     pNewItem->m_Unicode = pItem->m_Unicode.mid(nNewStart, nNewWidth);
00746                     pNewItem->m_nUnicode = nNewWidth;
00747 
00748                     // Move any remaining items, e.g. in a different colour, from this line onto the new line.
00749                     while (pLine->m_Items.Size() > j + 1)
00750                     {
00751                         pNewLine->m_Items.Append(pLine->m_Items.GetAt(j + 1));
00752                         pLine->m_Items.RemoveAt(j + 1);
00753                     }
00754                 }
00755 
00756                 // Remove any spaces at the end of the old section.  If we don't do that and
00757                 // we are centering or right aligning the text we'll get it wrong.
00758                 while (pItem->m_nUnicode > 1 && pItem->m_Unicode[pItem->m_nUnicode-1] == ' ')
00759                 {
00760                     pItem->m_nUnicode--;
00761                 }
00762 
00763                 rect = m_pDisplay->GetBounds(pItem->m_Unicode, pItem->m_nUnicode);
00764             }
00765 
00766             pItem->m_Width = rect.width();
00767             pLine->m_nLineWidth += rect.width();
00768 
00769             if (rect.height() > pLine->m_nLineHeight)
00770             {
00771                 pLine->m_nLineHeight = rect.height();
00772             }
00773 
00774             if (rect.bottom() > pLine->m_nDescent)
00775             {
00776                 pLine->m_nDescent = rect.bottom();
00777             }
00778         }
00779     }
00780 
00781     // Now output the text.
00782     int yOffset = 0;
00783     // If there isn't space for all the lines we should drop extra lines.
00784     int nNumLines = theText.Size();
00785 
00786     do
00787     {
00788         if (m_VertJ == End)
00789         {
00790             yOffset = m_nBoxHeight - nNumLines * lineSpace;
00791         }
00792         else if (m_VertJ == Centre)
00793         {
00794             yOffset = (m_nBoxHeight - nNumLines * lineSpace) / 2;
00795         }
00796 
00797         if (yOffset < 0)
00798         {
00799             nNumLines--;
00800         }
00801     }
00802     while (yOffset < 0);
00803 
00804     for (i = 0; i < nNumLines; i++)
00805     {
00806         MHTextLine *pLine = theText.GetAt(i);
00807         int xOffset = 0;
00808 
00809         if (m_HorizJ == End)
00810         {
00811             xOffset = m_nBoxWidth - pLine->m_nLineWidth;
00812         }
00813         else if (m_HorizJ == Centre)
00814         {
00815             xOffset = (m_nBoxWidth - pLine->m_nLineWidth) / 2;
00816         }
00817 
00818         for (int j = 0; j < pLine->m_Items.Size(); j++)
00819         {
00820             MHTextItem *pItem = pLine->m_Items.GetAt(j);
00821 
00822             // Tab across if necessary.
00823             for (int k = 0; k < pItem->m_nTabCount; k++)
00824             {
00825                 xOffset += TABSTOP - xOffset % TABSTOP;
00826             }
00827 
00828             if (! pItem->m_Unicode.isEmpty())   // We may have blank lines.
00829             {
00830                 m_pDisplay->AddText(xOffset, yOffset + (pLine->m_nLineHeight + lineSpace) / 2 - pLine->m_nDescent,
00831                                     pItem->m_Unicode.left(pItem->m_nUnicode), pItem->m_Colour);
00832             }
00833 
00834             xOffset += pItem->m_Width;
00835         }
00836 
00837         yOffset += lineSpace;
00838 
00839         if (yOffset + lineSpace > m_nBoxHeight)
00840         {
00841             break;
00842         }
00843     }
00844 
00845     // Clean up.
00846     for (int k = 0; k < theText.Size(); k++)
00847     {
00848         delete(theText.GetAt(k));
00849     }
00850 }
00851 
00852 void MHText::Display(MHEngine *engine)
00853 {
00854     if (! m_fRunning || ! m_pDisplay || m_nBoxWidth == 0 || m_nBoxHeight == 0)
00855     {
00856         return;    // Can't draw zero sized boxes.
00857     }
00858 
00859     // We only need to recreate the display if something has changed.
00860     if (m_fNeedsRedraw)
00861     {
00862         Redraw();
00863         m_fNeedsRedraw = false;
00864     }
00865 
00866     // Draw the background first, then the text.
00867     engine->GetContext()->DrawRect(m_nPosX, m_nPosY, m_nBoxWidth, m_nBoxHeight, GetColour(m_bgColour));
00868     m_pDisplay->Draw(m_nPosX, m_nPosY);
00869 }
00870 
00871 // Return the area actually obscured.  This is empty unless the background is opaque.
00872 QRegion MHText::GetOpaqueArea()
00873 {
00874     if (! m_fRunning || (GetColour(m_bgColour)).alpha() != 255)
00875     {
00876         return QRegion();
00877     }
00878     else
00879     {
00880         return QRegion(QRect(m_nPosX, m_nPosY, m_nBoxWidth, m_nBoxHeight));
00881     }
00882 }
00883 
00884 
00885 MHHyperText::MHHyperText(): MHInteractible(this)
00886 {
00887 
00888 }
00889 
00890 MHHyperText::~MHHyperText()
00891 {
00892 
00893 }
00894 
00895 void MHHyperText::Initialise(MHParseNode *p, MHEngine *engine)
00896 {
00897     MHText::Initialise(p, engine);
00898     MHInteractible::Initialise(p, engine);
00899     //
00900 }
00901 
00902 void MHHyperText::PrintMe(FILE *fd, int nTabs) const
00903 {
00904     PrintTabs(fd, nTabs);
00905     fprintf(fd, "{:HyperText ");
00906     MHText::PrintMe(fd, nTabs + 1);
00907     MHInteractible::PrintMe(fd, nTabs + 1);
00908     fprintf(fd, "****TODO\n");
00909     PrintTabs(fd, nTabs);
00910     fprintf(fd, "}\n");
00911 }
00912 
00913 
00914 void MHSetFontAttributes::Initialise(MHParseNode *p, MHEngine *engine)
00915 {
00916     MHElemAction::Initialise(p, engine); // Target
00917     m_FontAttrs.Initialise(p->GetArgN(1), engine); // New font attrs
00918 }
00919 
00920 void MHSetFontAttributes::Perform(MHEngine *engine)
00921 {
00922     // Get the new font attributes.
00923     MHOctetString newAttrs;
00924     m_FontAttrs.GetValue(newAttrs, engine);
00925     Target(engine)->SetFontAttributes(newAttrs, engine);
00926 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends