// // filename: HtmlDocument.cpp // description: // // --- Include Files ------------------------------------------------------------ #include "HtmlDocument.h" // C headers // Rage headers #include "file/asset.h" #include "file/device.h" #include "file/device_relative.h" #include "flash/err.h" #include "parsercore/attribute.h" #include "parsercore/element.h" #include "system/param.h" // Game headerso #include "Css.h" #include "HtmlLanguage.h" #include "HtmlNode.h" #include "HtmlParser.h" // Framework Headers //#include "fwmaths/Maths.h" #ifndef HTML_RESOURCE #include "HtmlScriptObject.h" #include "HtmlTextFormat.h" #include "HtmlViewport.h" #include "camera/viewports/Viewport.h" #include "debug/debug.h" #include "Text/TextFormat.h" #include "renderer/RenderThread.h" #include "scene/stores/txdstore.h" #endif // --- Defines ------------------------------------------------------------------ // --- Constants ---------------------------------------------------------------- // --- Structure/Class Definitions ---------------------------------------------- // --- Globals ------------------------------------------------------------------ #if __DEV PARAM(htmlpath, "Path to find non-resourced HTML files"); #endif // --- Static Globals ----------------------------------------------------------- // --- Static class members ----------------------------------------------------- CHtmlNode* CHtmlDocument::sm_pCurrent; atArray CHtmlDocument::sm_elements; bool CHtmlDocument::sm_parseScriptObjects = true; bool CHtmlDocument::sm_loadTextures = true; bool CHtmlDocument::sm_inBody = false; atArray CHtmlDocument::sm_inlineObjs; CHtmlDocument* CHtmlDocument::sm_pCurrentDocument = NULL; CHtmlDocument::LoadTextureFn CHtmlDocument::sm_loadTextureFn = NULL; atArray CHtmlDocument::sm_txdIndicesUsed; #ifndef HTML_RESOURCE CTextLayout CHtmlDocument::sm_HtmlTextLayout; #endif #if __DEV s32 CHtmlNode::sm_debugPrintTab = 0; #ifndef HTML_RESOURCE PARAM(debugscripthtml, "Debug html script objects"); #endif #endif // --- Code --------------------------------------------------------------------- // --- CHtmlDocument ------------------------------------------------------------ CHtmlDocument::CHtmlDocument() : m_pRootNode(NULL), m_pBodyNode(NULL), m_pTitle(NULL), m_pTxd(NULL) { } CHtmlDocument::CHtmlDocument(datResource& rsc) : m_pTitle(rsc), m_pTxd(rsc), //m_swfArray(rsc, true), m_links(rsc, true), m_styleSheet(rsc) { // do them in the function because we want to control the order they get done in rsc.PointerFixup(m_pRootNode); ::new ((void*)m_pRootNode) CHtmlElementNode(rsc); rsc.PointerFixup(m_pBodyNode); } CHtmlDocument::~CHtmlDocument() { Delete(); } void CHtmlDocument::Init(unsigned /*initMode*/) { static fiDeviceRelative gHttpDevice; // create html mount #if __DEV const char* pHtmlPath; if(PARAM_htmlpath.Get(pHtmlPath)) gHttpDevice.Init(pHtmlPath, false); else #endif gHttpDevice.Init("platform:/html/", true); gHttpDevice.MountAs("http:/"); } void CHtmlDocument::Place(CHtmlDocument* that, datResource& rsc) { sm_pCurrentDocument = that; ::new (that) CHtmlDocument(rsc); sm_pCurrentDocument = NULL; } #if __DECLARESTRUCT void CHtmlDocument::DeclareStruct(datTypeStruct &s) { STRUCT_BEGIN(CHtmlDocument); if(m_pRootNode) datSwapper(*m_pRootNode); STRUCT_FIELD_VP(m_pRootNode); STRUCT_FIELD_VP(m_pBodyNode); STRUCT_FIELD(m_pTitle); STRUCT_FIELD(m_pTxd); STRUCT_FIELD(m_padArray); STRUCT_FIELD(m_links); STRUCT_FIELD(m_styleSheet); STRUCT_END(); } #endif void CHtmlDocument::Delete() { if(m_pRootNode) { delete m_pRootNode; m_pRootNode = NULL; } // remove title string if(m_pTitle) { delete[] m_pTitle; m_pTitle = NULL; } // remove texture dictionary if(m_pTxd) { #ifndef HTML_RESOURCE // flush render thread so it releases the current visible objects gRenderThreadInterface.Flush(); #endif //HTML_RESOURCE delete m_pTxd; m_pTxd = NULL; } m_links.Reset(); // m_swfArray.Reset(); m_styleSheet.Shutdown(); // reset the txds used sm_txdIndicesUsed.Reset(); } // // name: CHtmlDocument::GetLinkRef // description: Get web page this link references const char* CHtmlDocument::GetLinkRef(s32 link) { CHtmlElementNode* pNode = m_links[link]; return pNode->GetAttributeData(); } // // name: CHtmlDocument::GetLinkAtPosition // description: Return the link at this position. If there is no link then return -1 s32 CHtmlDocument::GetLinkAtPosition(float x, float y) { #define TEMP_LINK_WIDTH (48.0f) #define TEMP_LINK_HEIGHT (16.0f) // temporary until we get proper bounding boxes for the links for(s32 i=0; iGetRenderState().m_currentX; float linkY = pNode->GetRenderState().m_currentY; float linkWidth = pNode->GetRenderState().m_width; float linkHeight = pNode->GetRenderState().m_height; if(x >= linkX && x < linkX+linkWidth && y >= linkY && y < linkY+linkHeight) return i; } return -1; } // // name: CHtmlDocument::AddInlineObject // description: Add to inline object list void CHtmlDocument::AddInlineObject(CHtmlNode* pNode) { sysMemStartTemp(); sm_inlineObjs.PushAndGrow(pNode); sysMemEndTemp(); } // // name: CHtmlDocument::ResetInlineObjectList // description: Reset inline object list void CHtmlDocument::ResetInlineObjectList() { sysMemStartTemp(); sm_inlineObjs.Reset(); sysMemEndTemp(); } // // name: CHtmlDocument::SetupDefaultStyleSheet // description: Setup default style sheet void CHtmlDocument::SetupDefaultStyleSheet() { m_styleSheet.Init(); CCssStyleClass* pClass = m_styleSheet.GetDefaultStyleClass(); CCssStyleGroup* pGroup = pClass->AddStyleGroup(HTML_HTML); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_BODY); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 8)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINLEFT, 8)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINRIGHT, 8)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 8)); pGroup->AddStyle(CCssStyle(CCssStyle::COLOR, Color32(0,0,0))); pGroup->AddStyle(CCssStyle(CCssStyle::BACKGROUNDCOLOR, Color32(255,255,255))); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_HEADING1); pGroup->AddStyle(CCssStyle(CCssStyle::FONTSIZE, 24)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 16)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 16)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_HEADING2); pGroup->AddStyle(CCssStyle(CCssStyle::FONTSIZE, 18)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 13.5f)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 13.5f)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_HEADING3); pGroup->AddStyle(CCssStyle(CCssStyle::FONTSIZE, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 12)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 12)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_HEADING4); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 12)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 15)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_HEADING5); pGroup->AddStyle(CCssStyle(CCssStyle::FONTSIZE, 10)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 15)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_HEADING6); pGroup->AddStyle(CCssStyle(CCssStyle::FONTSIZE, 9)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 15)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_DIV); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); // pGroup = sm_styleSheet.AddStyleGroup(HTML_IMAGE); // pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_TABLE); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERWIDTH, 1.0f)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERSTYLE, HTML_SOLID)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERLEFTCOLOR, Color32(0xcc,0xcc,0xcc))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERTOPCOLOR, Color32(0xcc,0xcc,0xcc))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERRIGHTCOLOR, Color32(0x66,0x66,0x66))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERBOTTOMCOLOR, Color32(0x66,0x66,0x66))); pGroup = pClass->AddStyleGroup(HTML_TABLEROW); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_TABLESECTION)); pGroup = pClass->AddStyleGroup(HTML_TABLEENTRY); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_TABLECELL)); pGroup->AddStyle(CCssStyle(CCssStyle::VERTICALALIGN, HTML_ALIGN_MIDDLE)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERWIDTH, 1.0f)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERSTYLE, HTML_SOLID)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERTOPCOLOR, Color32(0x66,0x66,0x66))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERLEFTCOLOR, Color32(0x66,0x66,0x66))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERBOTTOMCOLOR, Color32(0xcc,0xcc,0xcc))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERRIGHTCOLOR, Color32(0xcc,0xcc,0xcc))); pGroup = pClass->AddStyleGroup(HTML_TABLEHEADER); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_TABLECELL)); pGroup->AddStyle(CCssStyle(CCssStyle::VERTICALALIGN, HTML_ALIGN_MIDDLE)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERWIDTH, 1.0f)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERSTYLE, HTML_SOLID)); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERTOPCOLOR, Color32(0x66,0x66,0x66))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERLEFTCOLOR, Color32(0x66,0x66,0x66))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERBOTTOMCOLOR, Color32(0xcc,0xcc,0xcc))); pGroup->AddStyle(CCssStyle(CCssStyle::BORDERRIGHTCOLOR, Color32(0xcc,0xcc,0xcc))); pGroup = pClass->AddStyleGroup(HTML_DEFLIST); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_DEFTERM); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_DEFDESC); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINLEFT, 40)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_UNORDERED_LIST); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINLEFT, 40)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_ORDERED_LIST); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINLEFT, 40)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_PARAGRAPH); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINTOP, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::MARGINBOTTOM, 14)); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup = pClass->AddStyleGroup(HTML_A); pGroup->AddStyle(CCssStyle(CCssStyle::TEXTDECORATION, HTML_UNDERLINE)); pGroup->AddStyle(CCssStyle(CCssStyle::COLOR, Color32(0x80,0x80,0x80))); pGroup = pClass->AddStyleGroup(HTML_CENTER); pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); pGroup->AddStyle(CCssStyle(CCssStyle::TEXTALIGN, HTML_ALIGN_CENTER)); // pGroup = pClass->AddStyleGroup(HTML_SPAN); // pGroup->AddStyle(CCssStyle(CCssStyle::DISPLAY, HTML_BLOCK)); } void CHtmlDocument::PushHtmlFolder(const char* pFilename) { char pathname[RAGE_MAX_PATH]; ASSET.RemoveNameFromPath(pathname, RAGE_MAX_PATH, pFilename); ASSET.PushFolder(pathname); } bool CHtmlDocument::LoadDocument(const char* pFilename) { sysMemStartTemp(); // set flash warning level #if __DEV // swfSetFlashDebugLevel(eFEL_Fatal, eFWL_Critical); #endif Assert(m_pRootNode == NULL); CHtmlDocument::PushHtmlFolder(pFilename); fiStream* pStream = ASSET.Open(ASSET.FileName(pFilename), NULL); if(pStream == NULL) { return false; } sm_pCurrentDocument = this; CHtmlLanguage::GetCurrent(); sm_pCurrent = NULL; sysMemEndTemp(); SetupDefaultStyleSheet(); sysMemStartTemp(); // If loading texture function isn't setup then set it if(sm_loadTextureFn == 0) sm_loadTextureFn = MakeFunctorRet(*this, &CHtmlDocument::LoadTextureInternal); CHtmlParser parser(pStream); parser.SetBeginElementCallback(parStreamIn::BeginElementCB(this, &CHtmlDocument::ReadBeginElement)); parser.SetEndElementCallback(parStreamIn::EndElementCB(this, &CHtmlDocument::ReadEndElement)); parser.SetDataCallback(parStreamIn::DataCB(this, &CHtmlDocument::ReadData)); parser.ReadWithCallbacks(); pStream->Close(); sysMemEndTemp(); Process(); sysMemStartTemp(); ResetElementArray(); //sm_styleSheet.Shutdown(); sysMemEndTemp(); ASSET.PopFolder(); // reset loading texture function sm_loadTextureFn = NULL; sm_pCurrentDocument = NULL; return true; } void CHtmlDocument::ReadBeginElement(parElement& element, bool leaf) { CHtmlElementNode* pNode; parElement* pElement = rage_new parElement; // ensure element owns it own name pElement->SetName("temp", true); sm_elements.PushAndGrow(pElement); pElement->CopyFrom(element); sysMemEndTemp(); // If this is a table element create a special class because table formatting is a bitch and needs loads // of special code if(!stricmp(element.GetName(), "table")) pNode = rage_new CHtmlTableNode(pElement); else if(!stricmp(element.GetName(), "td") || !stricmp(element.GetName(), "th") || !stricmp(element.GetName(), "tr")) pNode = rage_new CHtmlTableElementNode(pElement); // else if(!stricmp(element.GetName(), "embed")) // assume all embed tags are flash objects // pNode = new CHtmlFlashNode(pElement); else pNode = rage_new CHtmlElementNode(pElement); // if there is no current node then this must be the root node if(sm_pCurrent == NULL) { Assert(m_pRootNode == NULL); m_pRootNode = pNode; } else sm_pCurrent->AddChild(pNode); if(leaf == false) { sm_pCurrent = pNode; } sysMemStartTemp(); } void CHtmlDocument::ReadEndElement(bool leaf) { if(!leaf) sm_pCurrent = sm_pCurrent->GetParent(); } void CHtmlDocument::ReadData(char* pData, int size, bool incomplete) { sysMemEndTemp(); if(incomplete) Quitf("Need to increase datachunk size. Data is too large"); Assert(sm_pCurrent); CHtmlElementNode* pElement = dynamic_cast(sm_pCurrent); // if parent element is a style tag then read style sheet if(pElement && !stricmp(pElement->GetElement().GetName(), "style")) { char filename[RAGE_MAX_PATH]; fiDeviceMemory::MakeMemoryFileName(filename, sizeof(filename), pData, size, false, NULL); m_styleSheet.ReadFile(filename); } else { CHtmlDataNode* pNode = rage_new CHtmlDataNode(pData, size); sm_pCurrent->AddChild(pNode); } sysMemStartTemp(); } // // name: CHtmlDocument::SetActive // description: void CHtmlDocument::SetActive(CHtmlNode* pNode, bool active) { pNode->GetRenderState().m_active = active; for(s32 i=0; iGetNumChildren(); i++) SetActive(pNode->GetChild(i), active); } // // name: CHtmlDocument::Process // description: Extract data from elements void CHtmlDocument::Process() { sm_inBody = false; ProcessNode(m_pRootNode); Assertf(m_pRootNode->GetElementId() == HTML_HTML, "There is no HTML tag in this file"); } // // name: CHtmlDocument::Process // description: Extract data from elements void CHtmlDocument::PostProcess() { PostProcessNode(m_pRootNode); } void CHtmlDocument::ProcessNode(CHtmlNode* pNode) { CHtmlElementNode* pElement = dynamic_cast(pNode); CHtmlDataNode* pData = dynamic_cast(pNode); if(pElement) { ProcessElement(pElement); // process children nodes for(s32 i=0; iGetNumChildren(); i++) { pNode->GetChild(i)->GetRenderState().Inherit(pNode->GetRenderState()); ProcessNode(pNode->GetChild(i)); } // this is called from PostProcessNode now //PostProcessElement(pElement); pElement->ResetParserElement(); } else if(pData) ProcessData(pData); } void CHtmlDocument::ProcessElement(CHtmlElementNode* pElementNode) { s32 elementId = CHtmlLanguage::GetCurrent().GetElementId(pElementNode->GetElement().GetName()); pElementNode->m_elementId = elementId; #if __DEV if(elementId == HTML_ELEMENT_INVALID) Warningf("Unrecognised tag: %s", pElementNode->GetElement().GetName()); #endif // Apply style sheet ApplyDefaultStyleSheet(pElementNode); // inherit table values if(pElementNode->GetRenderState().m_display == HTML_TABLECELL || pElementNode->GetRenderState().m_display == HTML_TABLESECTION) { pElementNode->GetRenderState().InheritTableState(pElementNode->GetParent()->GetRenderState()); } ProcessAttributes(pElementNode); switch(elementId) { case HTML_BODY: ProcessBody(pElementNode); break; case HTML_META: ProcessMetaData(pElementNode); break; case HTML_A: ProcessLink(pElementNode); break; case HTML_LINK: ProcessFileLink(pElementNode); break; case HTML_IMAGE: ProcessImage(pElementNode); break; case HTML_EMBED: ProcessEmbed(pElementNode); break; case HTML_OBJECT: ProcessObject(pElementNode); break; case HTML_SCRIPTOBJ: ProcessScriptObject(pElementNode); break; case HTML_TEXT: ProcessText(pElementNode); break; default: break; } } // // name: CHtmlDocument::ResetElementArray // description: Reset all the parser elements and empty array void CHtmlDocument::ResetElementArray() { for(s32 i=0; iGetStyleGroup(pNode->GetElementId()); if(pGroup) pGroup->Apply(pNode->GetRenderState()); // check for pseudo classes ApplyPseudoClassStyleSheet(pNode, "", ":hover"); } // // name: ApplyDefaultStyleSheet // description: Apply default style sheet for this node void CHtmlDocument::ApplyStyleSheet(CHtmlElementNode* pNode, const char* pClassName) { CCssStyleClass* pClass = m_styleSheet.GetStyleClass(pClassName); if(pClass) { // apply default style group first CCssStyleGroup* pGroup = pClass->GetDefaultStyleGroup(); if(pGroup) pGroup->Apply(pNode->GetRenderState()); // apply element style group pGroup = pClass->GetStyleGroup(pNode->GetElementId()); if(pGroup) pGroup->Apply(pNode->GetRenderState()); // check for pseudo classes ApplyPseudoClassStyleSheet(pNode, pClassName, ":hover"); } } // // name: CHtmlDocument::ApplyPseudoClassStyleSheet // description: Apply style sheet pseudo classes to additional renderstates void CHtmlDocument::ApplyPseudoClassStyleSheet(CHtmlElementNode* pNode, const char* pClassName, const char* pPseudoClassName) { CCssStyleClass* pClass; CCssStyleGroup* pGroup; // check for pseudo classes char pseudoClassName[128]; strcpy(pseudoClassName, pClassName); strcat(pseudoClassName, pPseudoClassName); pClass = m_styleSheet.GetStyleClass(pseudoClassName); if(pClass) { // apply element style group pGroup = pClass->GetStyleGroup(pNode->GetElementId()); if(pGroup) { pGroup->ApplyHoverPseudoClass(pNode->GetRenderState()); } } } void CHtmlDocument::ProcessAttributes(CHtmlElementNode* pElementNode) { parElement& element = pElementNode->GetElement(); HtmlRenderState& renderState = pElementNode->GetRenderState(); sysMemStartTemp(); for(s32 i=0; iGetStyleGroup(HTML_A)->AddStyle(CCssStyle(CCssStyle::COLOR, col)); } break; case HTML_ATTR_BGCOLOR: renderState.m_renderBackground = true; renderState.m_backgroundColor = CHtmlLanguage::GetCurrent().GetColor(attr.GetStringValue()); break; case HTML_ATTR_BACKGROUND: { sysMemEndTemp(); renderState.m_pBackgroundImage = sm_loadTextureFn(attr.GetStringValue()); renderState.m_renderBackground = true; sysMemStartTemp(); } break; case HTML_ATTR_BORDER: attr.ConvertToInt(); { float border = (float)attr.GetIntValue(); if(border == 0.0f) { renderState.m_borderBottomStyle = HTML_NONE; renderState.m_borderLeftStyle = HTML_NONE; renderState.m_borderRightStyle = HTML_NONE; renderState.m_borderTopStyle = HTML_NONE; } else { renderState.m_borderBottomStyle = HTML_SOLID; renderState.m_borderLeftStyle = HTML_SOLID; renderState.m_borderRightStyle = HTML_SOLID; renderState.m_borderTopStyle = HTML_SOLID; } renderState.m_borderBottomWidth = border; renderState.m_borderLeftWidth = border; renderState.m_borderRightWidth = border; renderState.m_borderTopWidth = border; } break; case HTML_ATTR_BORDERCOLOR: renderState.m_borderBottomColor = CHtmlLanguage::GetCurrent().GetColor(attr.GetStringValue()); renderState.m_borderLeftColor = renderState.m_borderBottomColor; renderState.m_borderRightColor = renderState.m_borderBottomColor; renderState.m_borderTopColor = renderState.m_borderBottomColor; break; case HTML_ATTR_CELLSPACING: attr.ConvertToInt(); renderState.m_cellspacing = (float)attr.GetIntValue(); break; case HTML_ATTR_CELLPADDING: attr.ConvertToInt(); renderState.m_cellpadding = (float)attr.GetIntValue(); break; case HTML_ATTR_CLASS: // do some style sheet ApplyStyleSheet(pElementNode, attr.GetStringValue()); break; case HTML_ATTR_COLOR: renderState.m_color = CHtmlLanguage::GetCurrent().GetColor(attr.GetStringValue()); break; case HTML_ATTR_HEIGHT: attr.ConvertToInt(); renderState.m_forcedHeight = (float)attr.GetIntValue(); break; case HTML_ATTR_MARGINHEIGHT: attr.ConvertToInt(); renderState.m_marginBottom = (float)attr.GetIntValue(); renderState.m_marginTop = (float)attr.GetIntValue(); break; case HTML_ATTR_MARGINWIDTH: attr.ConvertToInt(); renderState.m_marginLeft = (float)attr.GetIntValue(); renderState.m_marginRight = (float)attr.GetIntValue(); break; case HTML_ATTR_VALIGN: renderState.m_verticalAlign = CHtmlLanguage::GetCurrent().GetValueId(attr.GetStringValue()); break; case HTML_ATTR_WIDTH: attr.ConvertToInt(); renderState.m_forcedWidth = (float)attr.GetIntValue(); break; case HTML_ATTR_STYLE: ParseStyle(attr.GetStringValue(), renderState); break; case HTML_ATTR_COLSPAN: attr.ConvertToInt(); renderState.m_colSpan = attr.GetIntValue(); break; case HTML_ATTR_ROWSPAN: attr.ConvertToInt(); renderState.m_rowSpan = attr.GetIntValue(); break; case HTML_ATTR_SRC: case HTML_ATTR_ALT: case HTML_ATTR_NAME: case HTML_ATTR_CONTENT: case HTML_ATTR_HREF: //ignore break; default: Warningf("Unrecognised attribute %s", attr.GetName()); break; } } sysMemEndTemp(); } void CHtmlDocument::ParseStyle(const char* pStyle, HtmlRenderState& renderState) { sysMemStartTemp(); char filename[RAGE_MAX_PATH]; fiDeviceMemory::MakeMemoryFileName(filename, sizeof(filename), pStyle, strlen(pStyle), false, NULL); CCssStyleGroup styleGroup; styleGroup.ReadFile(filename); styleGroup.Apply(renderState); sysMemEndTemp(); } void CHtmlDocument::ProcessBody(CHtmlElementNode* pElementNode) { // parElement& element = pElementNode->m_element; sm_inBody = true; // pElementNode->GetRenderState().m_renderBackground = true; m_pBodyNode = pElementNode; } void CHtmlDocument::ProcessLink(CHtmlElementNode* pElementNode) { const parElement& element = pElementNode->GetElement(); // get image texture const parAttribute* pHRef = element.FindAttribute("href"); // parElement& element = pElementNode->m_element; // add to link array, if link has valid href value if(pHRef) { pElementNode->m_data = pHRef->GetStringValue(); // This is now done in the PostProcess stage //m_links.PushAndGrow(pElementNode); } } void CHtmlDocument::ProcessFileLink(CHtmlElementNode* pElementNode) { const parElement& element = pElementNode->GetElement(); // get image texture const parAttribute* pRel = element.FindAttribute("rel"); const parAttribute* pHRef = element.FindAttribute("href"); // parElement& element = pElementNode->m_element; // add to link array, if link has valid href value if(pRel && !stricmp(pRel->GetStringValue(), "stylesheet")) { // if there is a href attribute it must be the stylesheet filename if(pHRef) { m_styleSheet.ReadFile(pHRef->GetStringValue()); } } } void CHtmlDocument::ProcessImage(CHtmlElementNode* pElementNode) { char tempBuffer[512]; HtmlRenderState& rs = pElementNode->GetRenderState(); const parElement& element = pElementNode->GetElement(); grcTexture* pImage = NULL; // get image texture const parAttribute* pSrc = element.FindAttribute("src"); Assertf(pSrc, "\"img\" tag doesn't have \"src\" attribute"); const parAttribute* pTxd = element.FindAttribute("txd"); if(pTxd) { strcpy(tempBuffer, pTxd->GetStringValue()); strcat(tempBuffer, ":"); strcat(tempBuffer, pSrc->GetStringValue()); pElementNode->m_data = tempBuffer; #ifndef HTML_RESOURCE if(pTxd->GetStringValue()[0] == '*') { pImage = grcTextureFactory::GetInstance().LookupTextureReference(pElementNode->GetTextureName()); } else { int slot = g_TxdStore.FindSlot(pTxd->GetStringValue()); Assertf(slot != -1, "%s:Txd file does not exist", pTxd->GetStringValue()); g_TxdStore.PushCurrentTxd(); g_TxdStore.SetCurrentTxd(slot); pImage = sm_loadTextureFn(pElementNode->GetTextureName()); g_TxdStore.PopCurrentTxd(); // add slot to txd array if it isn't already there s32 i; for(i=0; i::SetCurrent(NULL); pElementNode->m_data = pSrc->GetStringValue(); pImage = sm_loadTextureFn(pElementNode->m_data); } // work out width and height of image float width=-1; float height=-1; if(pImage) { width = (float)pImage->GetWidth(); height = (float)pImage->GetHeight(); } const parAttribute* pWidth = element.FindAttribute("width"); const parAttribute* pHeight = element.FindAttribute("height"); if(pWidth && pHeight) { rs.m_forcedWidth = (float)pWidth->GetIntValue(); rs.m_forcedHeight = (float)pHeight->GetIntValue(); } else if(pWidth) { rs.m_forcedWidth = (float)pWidth->GetIntValue(); rs.m_forcedHeight = height * rs.m_width / width; } else if(pHeight) { rs.m_forcedHeight = (float)pHeight->GetIntValue(); rs.m_forcedWidth = width * rs.m_height / height; } else { rs.m_forcedHeight = height; rs.m_forcedWidth = width; } rs.m_width = rs.m_forcedWidth; rs.m_height = rs.m_forcedHeight; } void CHtmlDocument::ProcessEmbed(CHtmlElementNode* UNUSED_PARAM(pElementNode)) { // assume all embed tags are flash movies /* CHtmlFlashNode* pFlashNode = dynamic_cast(pElementNode); HtmlRenderState& rs = pElementNode->GetRenderState(); Assert(pFlashNode); pFlashNode->Load(); m_swfArray.PushAndGrow(pFlashNode->GetContext()); rs.m_width = rs.m_forcedWidth; rs.m_height = rs.m_forcedHeight;*/ } void CHtmlDocument::ProcessObject(CHtmlElementNode* pElementNode) { // we are ignoring this object so we need to reset its renderstate so it doesn't affect its children HtmlRenderState rs; pElementNode->GetRenderState() = rs; } // // name: CHtmlDocument::ProcessScriptObject // description: Process related script object void CHtmlDocument::ProcessScriptObject(CHtmlElementNode* pElementNode) { const parElement& element = pElementNode->GetElement(); const parAttribute* pAttr = element.FindAttribute("name"); Assertf(pAttr, "scriptobj tag doesn't have name attribute"); pElementNode->m_data = pAttr->GetStringValue(); #ifndef HTML_RESOURCE if(sm_parseScriptObjects) { ParseScriptObject(pElementNode); } #endif } #ifndef HTML_RESOURCE // // name: CHtmlDocument::ParseScriptObject // description: void CHtmlDocument::ParseScriptObject(CHtmlElementNode* pElementNode) { sysMemStartTemp(); s32 index = gHtmlScriptObjectMgr.Get(pElementNode->m_data); Assertf(index != -1, "%s:Script object doesn't exists", (const char*)pElementNode->m_data); // create memory stream and load html from memory char filename[64]; #if __DEV if(PARAM_debugscripthtml.Get()) { Displayf("\n%s\n", gHtmlScriptObjectMgr.GetHtml(index)); } #endif fiDeviceMemory::MakeMemoryFileName(filename, 64, gHtmlScriptObjectMgr.GetHtml(index), gHtmlScriptObjectMgr.GetHtmlSize(index), false, NULL); // create file handle and load fiStream* pStream = ASSET.Open(filename, NULL); Assert(pStream); sm_pCurrent = pElementNode; CHtmlParser parser(pStream); parser.SetBeginElementCallback(parStreamIn::BeginElementCB(this, &CHtmlDocument::ReadBeginElement)); parser.SetEndElementCallback(parStreamIn::EndElementCB(this, &CHtmlDocument::ReadEndElement)); parser.SetDataCallback(parStreamIn::DataCB(this, &CHtmlDocument::ReadData)); parser.ReadWithCallbacks(); pStream->Close(); sm_pCurrent = NULL; sysMemEndTemp(); } #endif // // name: CHtmlDocument::ProcessText // description: void CHtmlDocument::ProcessText(CHtmlElementNode* pElementNode) { // get name const parElement& element = pElementNode->GetElement(); const parAttribute* pSrc = element.FindAttribute("name"); Assertf(pSrc, "\"text\" tag doesn't have \"name\" attribute"); pElementNode->m_data = pSrc->GetStringValue(); } void CHtmlDocument::ProcessMetaData(CHtmlElementNode* UNUSED_PARAM(pElementNode)) { } void CHtmlDocument::PostProcessNode(CHtmlNode* pNode) { CHtmlElementNode* pElement = dynamic_cast(pNode); if(pElement) { // process children nodes for(s32 i=0; iGetNumChildren(); i++) { //pNode->GetChild(i)->GetRenderState().Inherit(pNode->GetRenderState()); PostProcessNode(pNode->GetChild(i)); } PostProcessElement(pElement); } } void CHtmlDocument::PostProcessElement(CHtmlElementNode* pElementNode) { switch(pElementNode->m_elementId) { case HTML_TABLE: PostProcessTable(pElementNode); break; case HTML_A: PostProcessLink(pElementNode); break; default: break; } } void CHtmlDocument::PostProcessLink(CHtmlElementNode* pElementNode) { m_links.PushAndGrow(pElementNode); } #define TABLE_NODE_ARRAY_SIZE 1024 // // name: GetTableNodes // description: Fillout array with all the table related nodes void CHtmlDocument::GetTableNodes(CHtmlNode* pNode, CHtmlElementNode** ppNodes, s32& index) { CHtmlElementNode* pElement = dynamic_cast(pNode); CHtmlTableElementNode* pTableElement = dynamic_cast(pNode); if(pElement) { if(pTableElement) { Assertf(index < TABLE_NODE_ARRAY_SIZE, "Too many table tags"); ppNodes[index] = pElement; index++; } // don't process children for table entry as it may contain another table if(pElement->GetRenderState().m_display != HTML_TABLECELL) { // process children nodes for(s32 i=0; iGetNumChildren(); i++) { GetTableNodes(pNode->GetChild(i), ppNodes, index); } } } } // // name: CountTableRowsAndColumns // description: Count the number of rows and columns a table has void CHtmlDocument::CountTableRowsAndColumns(CHtmlElementNode** pNodes, s32 num, CHtmlTableNode* pTableNode) { s32 numColumns = 0; s32 numRows = 0; s32 numColumns2=0; for(s32 i=0; iGetElementId() == HTML_TABLEROW) { if(numColumns2 > numColumns) numColumns = numColumns2; numRows++; numColumns2 = 0; } if(pNodes[i]->GetRenderState().m_display == HTML_TABLECELL) { s32 colspan = pNodes[i]->GetRenderState().m_colSpan; numColumns2 += colspan; } } if(numColumns2 > numColumns) numColumns = numColumns2; // if found entry tags but no row tags if(numColumns > 0 && numRows == 0) numRows = 1; pTableNode->SetNumColumns(numColumns); pTableNode->SetNumRows(numRows); } // // name: CHtmlDocument::CheckTableRowsAndColumns // description: Check that table rows and columns haven't changed number and if so re-allocate void CHtmlDocument::CheckTableRowsAndColumns(CHtmlElementNode** pNodes, s32 num, CHtmlTableNode* pTableNode) { s32 numRows = pTableNode->GetNumRows(); s32 numColumns = pTableNode->GetNumColumns(); CountTableRowsAndColumns(pNodes, num, pTableNode); if(numRows != pTableNode->GetNumRows() || numColumns != pTableNode->GetNumColumns()) { pTableNode->DeleteArrays(); pTableNode->AllocateArrays(); } } #ifndef HTML_RESOURCE // // name: GetTableCellCoords // description: Calculate the cell coords for each table node void CHtmlDocument::GetTableCellCoords(CHtmlElementNode** pNodes, s32 num, CHtmlTableNode* pTableNode) { s32* pRowSpan = Alloca(s32, pTableNode->GetNumColumns()); // set array to 1 for(s32 i=0; iGetNumColumns(); i++) pRowSpan[i] = 1; s32 column = 0; s32 rows = 0; for(s32 i=0; iGetElementId() == HTML_TABLEROW) { CHtmlTableElementNode* pTableElement = dynamic_cast(pNodes[i]); Assert(pTableElement); pTableElement->m_row = rows; rows++; column = 0; } if(pNodes[i]->GetRenderState().m_display == HTML_TABLECELL) { //If we have hit an entry and not hit a row increment row counter if(rows == 0) rows = 1; while(pRowSpan[column] > rows && column < pTableNode->GetNumColumns()) column++; Assert(column != pTableNode->GetNumColumns()); CHtmlTableElementNode* pTableElement = dynamic_cast(pNodes[i]); Assert(pTableElement); pTableElement->m_row = rows-1; pTableElement->m_column = column; s32 colSpan = pTableElement->GetRenderState().m_colSpan; while(colSpan--) { pRowSpan[column] = rows + pTableElement->GetRenderState().m_rowSpan; column++; } // pRowSpan[column] = rows + pTableElement->GetRenderState().m_rowSpan; // column += pTableElement->GetRenderState().m_colSpan; } } } // // name: GetTableWidthsAndHeights // description: Get the column width and row heights for a table void CHtmlDocument::GetTableWidthsAndHeights(CHtmlElementNode** pNodes, s32 num, CHtmlTableNode* pTableNode) { for(s32 i=0; i(pNodes[i]); Assert(pTableElement); if(pTableElement->GetElementId() == HTML_TABLEROW) { if(pTableElement->GetRenderState().m_height > 0.0f) pTableNode->SetRowHeight(pTableElement->m_row, pNodes[i]->GetRenderState().m_height); } if(pTableElement->GetRenderState().m_display == HTML_TABLECELL) { s32 colspan = pTableElement->GetRenderState().m_colSpan; float minWidth, maxWidth; GetNodeMinMaxWidth(pTableElement, minWidth, maxWidth); //GetForcedNodeDimensions(pNodes[i], childSize); // can't force width for entries that span more than one column at the moment if(colspan == 1) { pTableNode->SetColumnMinWidth(pTableElement->m_column, Max(minWidth, pTableNode->GetColumnMinWidth(pTableElement->m_column))); pTableNode->SetColumnMaxWidth(pTableElement->m_column, Max(maxWidth, pTableNode->GetColumnMaxWidth(pTableElement->m_column))); } } } } #endif // // name: CHtmlDocument::PostProcessTable // description: Calculate table size and sizes that are forced void CHtmlDocument::PostProcessTable(CHtmlElementNode* pElementNode) { CHtmlTableNode* pTable = dynamic_cast(pElementNode); CHtmlElementNode* pNodes[TABLE_NODE_ARRAY_SIZE]; s32 numNodes=0; Assert(pTable); GetTableNodes(pTable, pNodes, numNodes); CountTableRowsAndColumns(pNodes, numNodes, pTable); // allocate arrays for row and column sizes pTable->AllocateArrays(); // GetTableCellCoords(pNodes, numNodes, pTable); // GetTableWidthsAndHeights(pNodes, numNodes, pTable); } void CHtmlDocument::SetTitle(GxtChar* pTitle) { s32 length = StringLength(pTitle); // delete old string if it existed if(m_pTitle) delete[] m_pTitle; // allocate space for new one and copy m_pTitle = rage_new GxtChar[length + 1]; safecpy(m_pTitle.ptr, pTitle, length+1); } // // name: CHtmlDocument::ProcessData // description: Process data void CHtmlDocument::ProcessData(CHtmlDataNode* pDataNode) { Assert(pDataNode->GetParent()); CHtmlElementNode* pParentNode = dynamic_cast(pDataNode->GetParent()); Assert(pParentNode); switch(pParentNode->m_elementId) { case HTML_TITLE: SetTitle(pDataNode->GetData()); break; default: break; } /* if(sm_inBody) { p }*/ } #ifndef HTML_RESOURCE // // name: CHtmlDocument::GetNodeMinMaxWidth/GetElementMinMaxWidth/GetDataMinMaxWidth // description: Get minimum and maximum width of node for table dimension calculations void CHtmlDocument::GetNodeMinMaxWidth(CHtmlNode* pNode, float& minWidth, float& maxWidth) { CHtmlElementNode* pElement = dynamic_cast(pNode); CHtmlDataNode* pData = dynamic_cast(pNode); minWidth = 0.0f; maxWidth = 0.0f; if(pNode->GetRenderState().m_forcedWidth > 0.0f) { minWidth = pElement->GetRenderState().m_forcedWidth; maxWidth = pElement->GetRenderState().m_forcedWidth; } if(pElement) { float localMin = 0.0f; float localMax = 0.0f; if(GetElementMinMaxWidth(pElement, minWidth, maxWidth) == false) { // process children nodes for(s32 i=0; iGetNumChildren(); i++) { GetNodeMinMaxWidth(pNode->GetChild(i), localMin, localMax); minWidth = rage::Max(localMin, minWidth); maxWidth = rage::Max(localMax, maxWidth); } } } else if(pData) GetDataMinMaxWidth(pData, minWidth, maxWidth); } bool CHtmlDocument::GetElementMinMaxWidth(CHtmlElementNode* pElement, float& minWidth, float& maxWidth) { if(pElement->GetElementId() == HTML_IMAGE) { minWidth = pElement->GetRenderState().m_width; maxWidth = pElement->GetRenderState().m_width; return true; } // don't parse into tables else if(pElement->GetElementId() == HTML_TABLE) { CHtmlTableNode* pTable = dynamic_cast(pElement); Assert(pTable); minWidth = rage::Max(pTable->GetMinWidth(), minWidth); maxWidth = rage::Max(pTable->GetMaxWidth(), maxWidth); return true; } else if(pElement->GetElementId() == HTML_TEXT) { // get text GxtChar* pString = TheText.Get(pElement->GetAttributeData()); pElement->GetRenderState().SetFontProperties(); #ifndef HTML_RESOURCE minWidth = (float)CTextFormat::GetMaxWordLength(&sm_HtmlTextLayout, pString); maxWidth = (float)(sm_HtmlTextLayout.GetStringWidthOnScreen(pString, true)*GetFormattedWidth()); // 1st screen done #else pString; minWidth = maxWidth = 0.0f; #endif } return false; } void CHtmlDocument::GetDataMinMaxWidth(CHtmlDataNode* pData, float& minWidth, float& maxWidth) { pData->GetRenderState().SetFontProperties(); #ifndef HTML_RESOURCE minWidth = (float)CTextFormat::GetMaxWordLength(&sm_HtmlTextLayout, pData->GetData()); maxWidth = (float)(sm_HtmlTextLayout.GetStringWidthOnScreen(pData->GetData(), true)*GetFormattedWidth()); // 1st screen done #else minWidth = maxWidth = 0.0f; #endif } // // name: CHtmlDocument::GetForcedNodeDimensions // description: Get dimensions of node if it is forced void CHtmlDocument::GetForcedNodeDimensions(CHtmlNode* pNode, Vector2& size) { CHtmlElementNode* pElement = dynamic_cast(pNode); size.x = -1.0f; size.y = -1.0f; // if this is an image node return width of the image if(pElement) { HtmlRenderState& rs = pElement->GetRenderState(); if(pElement->GetElementId() == HTML_IMAGE) { size.x = rs.m_width; size.y = rs.m_height; return; } // don't parse into tables if(pElement->GetElementId() == HTML_TABLE) return; } // if this isn't an element node then return invalid width else { return; } // process children nodes. If one returns an invalid width then return as we cannot force the width // of this node for(s32 i=0; iGetNumChildren(); i++) { GetForcedNodeDimensions(pElement->GetChild(i), size); if(size.x == -1.0f) return; } } // // name: CHtmlDocument::GetMinimumNodeDimensions // description: Get mnimum dimensions of node if it is forced /*void CHtmlDocument::GetMinimumNodeDimensions(CHtmlNode* pNode, Vector2& size) { CHtmlElementNode* pElement = dynamic_cast(pNode); // if this is an image node return width of the image if(pElement) { HtmlRenderState& rs = pElement->GetRenderState(); if(rs.m_width > size.x) size.x = rs.m_width; if(rs.m_height > size.y) size.y = rs.m_height; // process children nodes. If one returns an invalid width then return as we cannot force the width // of this node for(s32 i=0; iGetNumChildren(); i++) { GetMinimumNodeDimensions(pElement->GetChild(i), size); } } }*/ // // name: CHtmlDocument::GetFormattedDimensions // description: Get dimensions document has been formatted to void CHtmlDocument::GetFormattedDimensions(float& x, float& y) { x = m_pRootNode->GetRenderState().m_forcedWidth; y = m_pRootNode->GetRenderState().m_forcedHeight; } // // name: CHtmlDocument::GetFormattedWidth // description: Get width document has been formatted to float CHtmlDocument::GetFormattedWidth() { return m_pRootNode->GetRenderState().m_forcedWidth; } // // name: CHtmlDocument::FormatDocument // description: Setups renderstate and positional info for each node based on window size void CHtmlDocument::FormatDocument(s32 width, s32 height) { Vector2 posn(0.0f, 0.0f); Vector2 size(0.0f, 0.0f); m_pRootNode->GetRenderState().m_forcedWidth = (float)width; m_pRootNode->GetRenderState().m_forcedHeight = (float)height; m_pRootNode->GetRenderState().m_currentX = 0.0f; m_pRootNode->GetRenderState().m_currentY = 0.0f; sm_inBody = false; //GetNodeSize(m_pRootNode, size); FormatNode(m_pRootNode, posn); } // // name: CHtmlDocument::FormatNode // description: Format positions of node void CHtmlDocument::FormatNode(CHtmlNode* pNode, Vector2& posn) { Vector2 posn2 = posn; //CHtmlNode* pParent = pNode->GetParent(); // If not the root node //if(pParent) { CHtmlElementNode* pElement = dynamic_cast(pNode); if(pNode->GetRenderState().m_display != HTML_INLINE) FormatInlineObjectList(posn2); if(pNode->GetNumChildren() > 0 || pNode->GetRenderState().m_display != HTML_INLINE) { posn = posn2; if(pNode->GetRenderState().m_display == HTML_INLINE) AddInlineObject(pNode);//sm_inlineObjs.PushAndGrow(pNode); Assert(pElement); FormatElement(pElement, posn2); UpdatePosition(pNode, posn, posn2); } else AddInlineObject(pNode);//sm_inlineObjs.PushAndGrow(pNode); } } void CHtmlDocument::UpdatePosition(CHtmlNode* pNode, Vector2& posn, const Vector2& newPosn) { CHtmlNode* pParentNode = pNode->GetParent(); if(pParentNode == NULL) return; HtmlRenderState& rs = pNode->GetRenderState(); float x, y; float width, height; pParentNode->GetDimensions(x, y, width, height); // If render state width is valid then use that as a delta on the position // otherwise just use the returned position from FormatNode() Vector2 delta; if(rs.m_width != -1) delta.x = (float)rs.m_width; else delta.x = newPosn.x - posn.x; if(rs.m_height != -1) delta.y = (float)rs.m_height; else delta.y = newPosn.y - posn.y; /*if(rs.m_currentX + delta.x + rs.m_marginRight <= x + width && rs.m_display == HTML_INLINE) posn.x = rs.m_currentX + delta.x; else*/ { posn.x = x; posn.y = rs.m_currentY + delta.y;// + rs.m_marginBottom + rs.m_borderBottomWidth; posn.y += rs.m_borderBottomWidth; // if not last child add margin bottom CHtmlNode* pLastChild = NULL; if(pNode->GetParent()) { pLastChild = pNode->GetParent()->GetChild(pNode->GetParent()->GetNumChildren()-1); } if(pLastChild != pNode) posn.y += rs.m_marginBottom; } } // // name: CHtmlDocument::FormatElement // description: void CHtmlDocument::FormatElement(CHtmlElementNode* pElementNode, Vector2& posn) { HtmlRenderState& rs = pElementNode->GetRenderState(); float x, y, width, height; rs.m_width = rs.m_forcedWidth; rs.m_height = rs.m_forcedHeight; pElementNode->GetDimensions(x,y,width,height); // force width and height on all block display nodes if(rs.m_display == HTML_BLOCK) { if(pElementNode->GetElementId() != HTML_TABLE) { if(rs.m_width < 0.0f) rs.m_width = width; } // the first child of block nodes can't set left and top margins if(pElementNode->GetParent() && pElementNode->GetParent()->GetRenderState().m_display != HTML_INLINE && pElementNode->GetParent()->GetChild(0) == pElementNode) { rs.m_marginLeft = 0.0f; rs.m_marginTop = 0.0f; } } // non-block nodes can't set the margins or have borders else if(rs.m_display == HTML_INLINE) { rs.m_marginLeft = 0.0f; rs.m_marginRight = 0.0f; rs.m_paddingLeft = 0.0f; rs.m_paddingRight = 0.0f; rs.m_borderLeftStyle = HTML_NONE; rs.m_borderRightStyle = HTML_NONE; rs.m_borderTopStyle = HTML_NONE; rs.m_borderBottomStyle = HTML_NONE; } // set border widths if(rs.m_borderLeftStyle == HTML_NONE) rs.m_borderLeftWidth = 0.0f; if(rs.m_borderRightStyle == HTML_NONE) rs.m_borderRightWidth = 0.0f; if(rs.m_borderTopStyle == HTML_NONE) rs.m_borderTopWidth = 0.0f; if(rs.m_borderBottomStyle == HTML_NONE) rs.m_borderBottomWidth = 0.0f; // set border style if(rs.m_borderLeftWidth == 0.0f) rs.m_borderLeftStyle = HTML_NONE; if(rs.m_borderRightWidth == 0.0f) rs.m_borderRightStyle = HTML_NONE; if(rs.m_borderTopWidth == 0.0f) rs.m_borderTopStyle = HTML_NONE; if(rs.m_borderBottomWidth == 0.0f) rs.m_borderBottomStyle = HTML_NONE; // Add margin to position. Padding doesn't affect position of this node only nodes that are // children of this node posn.x += rs.m_marginLeft + rs.m_borderLeftWidth; posn.y += rs.m_marginTop + rs.m_borderTopWidth; // margins affect width and height if(rs.m_width > 0.0f) { rs.m_width -= rs.m_marginLeft + rs.m_marginRight + rs.m_borderLeftWidth + rs.m_borderRightWidth; if(rs.m_width < 0.0f) rs.m_width = 0.0f; } /* if(rs.m_height > 0.0f) { rs.m_height -= rs.m_marginTop + rs.m_marginBottom; if(rs.m_height < 0.0f) rs.m_height = 0.0f; }*/ // set current position rs.m_currentX = posn.x; rs.m_currentY = posn.y; switch(pElementNode->m_elementId) { case HTML_BODY: sm_inBody = true; break; case HTML_TABLE: FormatTable(pElementNode, posn); AlignNode(pElementNode); return; default: break; } posn.x += rs.m_paddingLeft; posn.y += rs.m_paddingTop; // process children nodes for(s32 i=0; iGetNumChildren(); i++) { FormatNode(pElementNode->GetChild(i), posn); } if(rs.m_display != HTML_INLINE) FormatInlineObjectList(posn); if(sm_inBody) { // Set block size if(rs.m_display == HTML_BLOCK) { posn.y += rs.m_paddingBottom; rs.m_height = posn.y - rs.m_currentY; /* posn.y += rs.m_borderBottomWidth; // if not last child CHtmlNode* pLastChild = NULL; if(pElementNode->GetParent()) { pLastChild = pElementNode->GetParent()->GetChild(pElementNode->GetParent()->GetNumChildren()-1); } if(pLastChild != pElementNode) posn.y += rs.m_marginBottom;*/ } if(pElementNode->m_elementId == HTML_BODY) { // rs.m_height = height; sm_inBody = false; } AlignNode(pElementNode); } } // // name: FilloutTableColumnWidths // description: Fillout table element widths void CHtmlDocument::FilloutTableColumnWidths(CHtmlElementNode** pNodes, s32 numNodes, CHtmlTableNode* pTableNode) { HtmlRenderState& trs = pTableNode->GetRenderState(); for(s32 i=0; iGetRenderState().m_display == HTML_TABLECELL) { CHtmlTableElementNode* pTableElement = dynamic_cast(pNodes[i]); Assert(pTableElement); HtmlRenderState& rs = pTableElement->GetRenderState(); // get column span s32 colspan = pTableElement->GetRenderState().m_colSpan; // add column widths onto table entry width rs.m_forcedWidth = 0; for(s32 j=0; jGetColumnWidth(pTableElement->m_column+j) + trs.m_cellspacing; // take off last spacing values rs.m_forcedWidth -= trs.m_cellspacing; } } } // // name: FormatTableEntries // description: Calculate table entry positions and format their children void CHtmlDocument::FormatTableEntries(CHtmlElementNode** pNodes, s32 numNodes, CHtmlTableNode* pTable) { HtmlRenderState& trs = pTable->GetRenderState(); //int rows = 0; int columns = 0; Vector2 tableRootPosn = Vector2(trs.m_currentX, trs.m_currentY); Vector2 currRowPosn = tableRootPosn; float maxHeight = tableRootPosn.y; CHtmlTableElementNode* pTableElement = NULL; currRowPosn.x += trs.m_cellspacing; currRowPosn.y += trs.m_cellspacing; for(s32 i=0; i(pNodes[i]); Assert(pTableElement); if(pTableElement->GetElementId() == HTML_TABLEROW) { // store previous row height if(pTableElement->m_row > 0) { maxHeight += trs.m_cellpadding; pTable->SetCalculatedRowHeight(pTableElement->m_row-1, maxHeight - currRowPosn.y - trs.m_borderTopWidth - trs.m_borderBottomWidth); } // set current row position currRowPosn.x = tableRootPosn.x + trs.m_cellspacing; currRowPosn.y = maxHeight + trs.m_cellspacing; // set new max height // if row height is set then set max height HtmlRenderState& rs = pTableElement->GetRenderState(); if(rs.m_height > 0.0f) maxHeight = currRowPosn.y + rs.m_height; columns = 0; } else if(pTableElement->GetRenderState().m_display == HTML_TABLECELL) { HtmlRenderState& rs = pTableElement->GetRenderState(); // get position of cell for(s32 j=columns; jm_column; j++) currRowPosn.x += pTable->GetColumnWidth(j) + trs.m_cellspacing; Vector2 posn(currRowPosn); Vector2 posn2(currRowPosn); rs.m_paddingLeft = trs.m_cellpadding; rs.m_paddingRight = trs.m_cellpadding; rs.m_paddingTop = trs.m_cellpadding; rs.m_paddingBottom = trs.m_cellpadding; // format table entry and its children //FormatNode(pTableElement, posn); FormatElement(pTableElement, posn2); rs.m_cellHeight = posn2.y - currRowPosn.y; rs.m_height = rage::Max(rs.m_cellHeight, rs.m_height); UpdatePosition(pTableElement, posn, posn2); //if(rs.m_height + currRowPosn.y + trs.m_borderTopWidth + trs.m_borderBottomWidth > posn.y) // posn.y = rs.m_height + currRowPosn.y + trs.m_borderTopWidth + trs.m_borderBottomWidth; // has it increased the height of this row? if(rs.m_rowSpan < 2) maxHeight = rage::Max(posn.y, maxHeight); columns = pTableElement->m_column; } } // store previous row height maxHeight += trs.m_cellpadding; if(pTableElement) pTable->SetCalculatedRowHeight(pTableElement->m_row, maxHeight - currRowPosn.y - trs.m_borderTopWidth - trs.m_borderBottomWidth); // set table height float height = maxHeight - pTable->GetRenderState().m_currentY + trs.m_cellspacing; float cellscaling = 1.0f; // if calculated height is greater than current set height then set table height. if(height >= pTable->GetRenderState().m_height) pTable->GetRenderState().m_height = height; // otherwise scale up all else if(pTable->GetNumRows() > 0) { float forcedHeight = pTable->GetRenderState().m_height; float currentHeight = 0.0f; forcedHeight -= trs.m_cellspacing * (pTable->GetNumRows() + 1); forcedHeight -= (trs.m_borderTopWidth + trs.m_borderBottomWidth) * (pTable->GetNumRows() + 1); //forcedHeight -= trs.m_cellpadding * pTable->GetNumRows() * 2.0f; for(s32 i=0; iGetNumRows(); i++) currentHeight += pTable->GetRowHeight(i); cellscaling = forcedHeight / currentHeight; Assert(cellscaling >= 1.0f); } Vector2 offset(0.0f, 0.0f); float nextYOffset = 0.0f; // set table cell positions based on new row heights for(s32 i=0; i(pNodes[i]); HtmlRenderState& rs = pTableElement->GetRenderState(); if(pTableElement->GetElementId() == HTML_TABLEROW) { offset.y = nextYOffset; // calculate how much we have to offset the table cells by nextYOffset += pTable->GetRowHeight(pTableElement->m_row) * (cellscaling - 1.0f); pTable->SetCalculatedRowHeight(pTableElement->m_row, pTable->GetRowHeight(pTableElement->m_row) * cellscaling); } else if(rs.m_display == HTML_TABLECELL) { // If cell is to be offset, offset the cell and then all its children if(offset.y > 0.0f) { pTableElement->GetRenderState().m_currentY += offset.y; for(s32 i=0; iGetNumChildren(); i++) OffsetPositions(pTableElement->GetChild(i), offset); } } } // set table entry heights, and vertically align contents for(s32 i=0; i(pNodes[i]); HtmlRenderState& rs = pTableElement->GetRenderState(); if(rs.m_display == HTML_TABLECELL) { // vertical align pTableElement->GetRenderState().m_height = 0.0f; for(s32 j=0; jm_row+j < pTable->GetNumRows()) { rs.m_height += (pTable->GetRowHeight(pTableElement->m_row+j)); rs.m_height += trs.m_cellspacing + trs.m_borderTopWidth + trs.m_borderBottomWidth; } } rs.m_height -= trs.m_cellspacing + trs.m_borderTopWidth + trs.m_borderBottomWidth; VerticalAlignNode(pTableElement); } } } void CHtmlDocument::FormatTable(CHtmlElementNode* pNode, Vector2& posn) { CHtmlTableNode* pTable = dynamic_cast(pNode); CHtmlElementNode* pNodes[TABLE_NODE_ARRAY_SIZE]; s32 numNodes=0; Assert(pTable); // get list of table rows and entries GetTableNodes(pTable, pNodes, numNodes); // count table rows and columns and if they are different re-allocate arrays CheckTableRowsAndColumns(pNodes, numNodes, pTable); GetTableCellCoords(pNodes, numNodes, pTable); GetTableWidthsAndHeights(pNodes, numNodes, pTable); // calculate the size of the columns that don't have any size info pTable->CalculateColumnWidths(); // fillout their width values FilloutTableColumnWidths(pNodes, numNodes, pTable); // format contents of the table entries and set table height FormatTableEntries(pNodes, numNodes, pTable); posn.y += pTable->GetRenderState().m_height; } // // name: CHtmlDocument::AlignNode // description: Align node according to alignment render state void CHtmlDocument::AlignNode(CHtmlElementNode* pNode) { CHtmlNode* pBlockNode = pNode->GetNonInlineNode(); HtmlRenderState& rs = pNode->GetRenderState(); HtmlRenderState& brs = pBlockNode->GetRenderState(); Vector2 offset(0.0f, 0.0f); // cannot align table entries or rows if(pNode->GetRenderState().m_display == HTML_TABLECELL || pNode->GetRenderState().m_display == HTML_TABLESECTION) return; if(rs.m_display == HTML_BLOCK) { s32 textAlign = rs.m_textAlign; if(textAlign == HTML_UNDEFINED) textAlign = brs.m_textAlign; // Assert(brs.m_width >= rs.m_width); float diffWidth = brs.m_width - rs.m_width; if(brs.m_width < rs.m_width) diffWidth = 0.0f; if(textAlign == HTML_ALIGN_RIGHT) offset.x = diffWidth; else if(textAlign == HTML_ALIGN_CENTER) offset.x = diffWidth / 2.0f; if(offset.x != 0.0f) OffsetPositions(pNode, offset); } } // // name: CHtmlDocument::VerticalAlignNode // description: Vertical align children nodes according to alignment render state and the height passed in void CHtmlDocument::VerticalAlignNode(CHtmlElementNode* pNode) { HtmlRenderState& rs = pNode->GetRenderState(); Vector2 offset(0.0f, 0.0f); // Assert(rs.m_cellHeight <= rs.m_height); if(rs.m_cellHeight == 0.0f) { return; } float diffHeight = rage::Max(rs.m_height - rs.m_cellHeight, 0.0f); if(rs.m_verticalAlign == HTML_ALIGN_BOTTOM) offset.y = diffHeight; else if(rs.m_verticalAlign == HTML_ALIGN_MIDDLE) offset.y = diffHeight / 2.0f; // offset children if(offset.y != 0.0f) { for(s32 i=0; iGetNumChildren(); i++) OffsetPositions(pNode->GetChild(i), offset); } } // // name: CHtmlDocument::FormatData // description: void CHtmlDocument::FormatData(CHtmlDataNode* pDataNode, Vector2& posn) { HtmlRenderState& rs = pDataNode->GetRenderState(); // set current position rs.m_currentX = posn.x; rs.m_currentY = posn.y; Vector2 vPos; float width, height; float formattedWidth, formattedHeight; pDataNode->GetDimensions(vPos.x,vPos.y,width,height); GetFormattedDimensions(formattedWidth, formattedHeight); rs.SetFontProperties(); vPos.x = rs.m_currentX; vPos.y = rs.m_currentY; #ifndef HTML_RESOURCE sm_HtmlTextLayout.SetWrap(Vector2(vPos.x/formattedWidth, (vPos.x+width)/formattedWidth)); #endif if(rs.m_textAlign == HTML_ALIGN_RIGHT) vPos.x += width; else if(rs.m_textAlign == HTML_ALIGN_CENTER) vPos.x += width / 2.0f; // temporary stuff to get size of lines #ifndef HTML_RESOURCE s32 lines = sm_HtmlTextLayout.GetNumberOfLines(&vPos, pDataNode->GetData()); posn.y += lines * ((CTextFormat::GetLineHeight(sm_HtmlTextLayout)*formattedHeight)); #endif } // // name: CHtmlDocument::FormatInlineObjectList // description: Format the list of data objec void CHtmlDocument::FormatInlineObjectList(Vector2& posn) { Vector2 posn2 = posn; if(sm_inBody) { float formattedWidth, formattedHeight; GetFormattedDimensions(formattedWidth, formattedHeight); if(GetInlineObjectCount() > 0) { float x,y; float width, height; CHtmlNode* pNonInlineNode = GetInlineObject(0)->GetNonInlineNode();//sm_inlineObjs[0]->GetNonInlineNode(); CHtmlNode* pLastLinkNode = NULL; // get dimensions from first objects parent pNonInlineNode->GetDimensions(x,y,width,height); CHtmlTextFormat fn(x, posn.y, width, pNonInlineNode->GetRenderState().m_textAlign, formattedWidth, formattedHeight); for(s32 i=0; i(pNode); CHtmlDataNode* pData = dynamic_cast(pNode); if(pData) { HtmlRenderState& rs = pData->GetRenderState(); rs.SetFontProperties(); fn.SetHtmlNode(pNode); #ifndef HTML_RESOURCE sm_HtmlTextLayout.SetWrap(Vector2(x/formattedWidth, (x+width)/formattedWidth)); // 1st screen done maybe sm_HtmlTextLayout.SetOrientation(FONT_LEFT); CTextFormat::ProcessCurrentString(&sm_HtmlTextLayout, (posn.x + fn.GetCurrentX())/formattedWidth, posn.y/formattedHeight, pData->GetData(), fn); #endif } else if(pElement) { // only know how to format images, flash movies if(pElement->GetElementId() == HTML_IMAGE || pElement->GetElementId() == HTML_EMBED) { fn.SetHtmlNode(pNode); fn.AddNonTextBlock(pElement->GetRenderState().m_width, pElement->GetRenderState().m_height); } else if(pElement->GetElementId() == HTML_TEXT) { HtmlRenderState& rs = pElement->GetRenderState(); rs.SetFontProperties(); fn.SetHtmlNode(pNode); #ifndef HTML_RESOURCE sm_HtmlTextLayout.SetWrap(Vector2(x/formattedWidth, (x+width)/formattedWidth)); sm_HtmlTextLayout.SetOrientation(FONT_LEFT); CTextFormat::ProcessCurrentString(&sm_HtmlTextLayout, (posn.x + fn.GetCurrentX())/formattedWidth, posn.y/formattedHeight, TheText.Get(pElement->GetAttributeData()), fn); #endif } else if(pElement->GetElementId() == HTML_BREAK) { pElement->GetRenderState().SetFontProperties(); fn.SetHtmlNode(pNode); #ifndef HTML_RESOURCE fn.AddNonTextBlock(1, (CTextFormat::GetLineHeight(sm_HtmlTextLayout)*formattedHeight)); #endif fn.NewLine(); } else { pElement->GetRenderState().m_currentX = posn.x + fn.GetCurrentX(); pElement->GetRenderState().m_currentY = posn.y + fn.GetCurrentY(); } } // If we are working out the size of a link and the current node's parent was this link then calculate size of link if(pLastLinkNode && pNode->GetParent() == pLastLinkNode) { if(fn.GetCurrentY() != pLastLinkNode->GetRenderState().m_currentY - posn.y) { pLastLinkNode->GetRenderState().m_width = fn.GetCurrentX(); pLastLinkNode->GetRenderState().m_height = posn.y + fn.GetCurrentY() + fn.GetCurrentLineHeight() - pLastLinkNode->GetRenderState().m_currentY; } else { pLastLinkNode->GetRenderState().m_width = posn.x + fn.GetCurrentX() - pLastLinkNode->GetRenderState().m_currentX; pLastLinkNode->GetRenderState().m_height = fn.GetCurrentLineHeight(); } } else pLastLinkNode = NULL; // Have to do this after getting size of link from child if(pElement && pElement->GetElementId() == HTML_A) pLastLinkNode = pElement; } fn.NewLine(); posn.y += fn.GetCurrentY(); // work out link positions for(s32 i=0; i(pNode); // Position of link is defined by the position of the first entry if(pLastLinkNode && pNode->GetParent() == pLastLinkNode) { pLastLinkNode->GetRenderState().m_currentX = pNode->GetRenderState().m_currentX; pLastLinkNode->GetRenderState().m_currentY = pNode->GetRenderState().m_currentY; } pLastLinkNode = NULL; // Have to do this after getting size of link from child if(pElement && pElement->GetElementId() == HTML_A) pLastLinkNode = pElement; } } } ResetInlineObjectList();//sm_inlineObjs.Reset(); } #endif //HTML_FORMAT // // name: OffsetPositions // description: Offset the position of children nodes void CHtmlDocument::OffsetPositions(CHtmlNode* pNode, const Vector2& posn) { pNode->GetRenderState().m_currentX += posn.x; pNode->GetRenderState().m_currentY += posn.y; for(s32 i=0; iGetNumChildren(); i++) { CHtmlNode* pChild = pNode->GetChild(i); OffsetPositions(pChild, posn); } } // // name: CHtmlDocument::GetLinkPosition // description: Return position of link void CHtmlDocument::GetLinkPosition(s32 link, float& x, float& y) { x = m_links[link]->GetRenderState().m_currentX; y= m_links[link]->GetRenderState().m_currentY; } // // name: CHtmlDocument::LoadTextureInternal // description: Load a texture and add it to the texture dictionary grcTexture* CHtmlDocument::LoadTextureInternal(const char* pName) { grcTexture* pImage; // If we have a current txd then check inside that for the texture if(pgDictionary::GetCurrent()) { pImage = pgDictionary::GetCurrent()->Lookup(pName); if(pImage) return pImage; } // If texture dictionary isn't constructed build it and reserve 256 slots if(m_pTxd == NULL) m_pTxd = rage_new pgDictionary(256); // Is image already in texture dictionary pImage = m_pTxd->Lookup(pName); if(pImage) return pImage; char filename[RAGE_MAX_PATH]; ASSET.RemoveExtensionFromPath(filename, RAGE_MAX_PATH, pName); pImage = grcTextureFactory::GetInstance().Create(filename); if(pImage) { // Add entry to texture dictionary m_pTxd->AddEntry(pName, pImage); } else { Errorf("Cannot load image %s", pName); } return pImage; } #if __DEV void CHtmlDocument::DebugPrint() { Printf("Title: %s\n", (const char*)(GxtChar*)m_pTitle); m_pRootNode->DebugPrint(); } #endif