|
MythTV
0.26-pre
|
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 }
1.7.6.1