/* * @doc INTERNAL * * @module LAYOUT.CPP -- CLayout class | * * Recursive structure which contains an array of lines. * * Owner: * Murray Sargent: Initial table implementation * Keith Curtis: Factored into a separate class for * performance, simplicity * * Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved. */ //FUTURE: (KeithCu) More stuff should be put into here, e.g., RecalcLines, //The CDisplayML should just be a class that knows about Device descriptors, //pagination and scrolling, etc., i.e., things that are the same for all //layouts and things that apply only to the outermost layout. This code knows //how to manage and update recursive arrays of lines. #include "_common.h" #include "_dispml.h" #include "_select.h" #include "_measure.h" #include "_render.h" void CLayout::DeleteSubLayouts( LONG ili, LONG cLine) { CLine *pli = Elem(ili); if(cLine < 0) cLine = Count(); LONG cLineMax = Count() - ili; cLine = min(cLine, cLineMax); AssertSz(ili >= 0 && cLine >= 0, "DeleteSubLayouts: illegal line count"); // Delete sublayouts for(; cLine--; pli++) delete pli->GetPlo(); } /* * CLayout::VposFromLine(pdp, ili) * * @mfunc * Computes top of line position * * @rdesc * top position of given line (relative to the first line) */ LONG CLayout::VposFromLine( CDisplayML *pdp, //@parm Parent display LONG ili) //@parm Line we're interested in { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::VposFromLine"); LONG cli = 0, vPos = 0; CLine *pli = 0; if (IsNestedLayout()) { Assert(!IsTableRow()); // _iPFCell layouts are horizontal Assert(ili < Count()); cli = ili; pli = Elem(0); vPos = 0; } else { if(!pdp->WaitForRecalcIli(ili)) // out of range, use last valid line { ili = Count() - 1; ili = (ili > 0) ? ili : 0; } cli = ili - pdp->_iliFirstVisible; pli = Elem(pdp->_iliFirstVisible); vPos = pdp->_vpScroll + pdp->_dvpFirstVisible; } while(cli > 0) { vPos += pli->GetHeight(); cli--; pli++; } while(cli < 0) { pli--; vPos -= pli->GetHeight(); cli++; } AssertSz(vPos >= 0, "VposFromLine height less than 0"); return vPos; } /* * CLayout::LineFromVPos(pdp, vPos, pdvpLine, pcpFirst) * * @mfunc * Computes line at given y position. Returns top of line vPos * cp at start of line cp, and line index. * * @rdesc * index of line found */ LONG CLayout::LineFromVpos( CDisplayML *pdp, //@parm Parent display LONG vPos, //@parm Vpos to look for (relative to first line) LONG *pdvpLine, //@parm Returns vPos at top of line /r first line (can be NULL) LONG *pcpFirst) //@parm Returns cp at start of line (can be NULL) { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::LineFromVpos"); LONG cpLi; LONG dy; LONG ili = 0; LONG yLi; CLine *pli; if(IsNestedLayout()) goto BindFrom0; yLi = pdp->_vpScroll; if(!pdp->WaitForRecalc(-1, pdp->_vpScroll)) { yLi = 0; cpLi = 0; goto done; } cpLi = pdp->_cpFirstVisible; ili = pdp->_iliFirstVisible; if(!pdp->IsInPageView()) yLi += pdp->_dvpFirstVisible; dy = vPos - yLi; if(dy < 0 && -dy <= pdp->_vpScroll) { // Closer to first visible line than to first line: // go backwards from first visible line. while(vPos < yLi && ili > 0) { pli = Elem(--ili); yLi -= pli->GetHeight(); cpLi -= pli->_cch; } } else { if(dy < 0) { // Closer to first line than to first visible line: // so start at first line. BindFrom0: cpLi = _cpMin; yLi = 0; ili = 0; } pli = Elem(ili); while(vPos > yLi && ili < Count()-1) { yLi += pli->GetHeight(); cpLi += pli->_cch; ili++; pli++; } if(vPos < yLi && ili > 0) { ili--; pli--; yLi -= pli->GetHeight(); cpLi -= pli->_cch; } } done: if(pdvpLine) *pdvpLine = yLi; if(pcpFirst) *pcpFirst = cpLi; return ili; } /* * CLayout::FindTopCell(&cch, pli, &ili, dul, &dy, pdvp, pliMain,iliMain, pcLine) * * @mfunc * Find cch and height change back to current position in * top cell corresponding to the current vertically merged cell. * Enter with cch = cch from current cell back to start of row. * * @rdesc * target line in top cell */ CLine * CLayout::FindTopCell( LONG & cch, //@parm In/out parm for cch back to top CLine * pli, //@parm Table-row line LONG & ili, //@parm Corresponding line index & return ili LONG dul, //@parm Current cell x offset LONG & dy, //@parm In/Out parm for y offset in top cell LONG * pdvp, //@parm TopCellHeight - heights of inbetween rows CLine * pliMain, //@parm Line preceding first line accessible by pli LONG iliMain, //@parm Line index corresponding to pliMain LONG * pcLine) //@parm Count() of possible CLayout for returned pli { LONG cCell; LONG iCell; CLayout * plo; const CELLPARMS *prgCellParms; const CParaFormat *pPF; #ifdef DEBUG BYTE bTableLevel = pli->GetPlo()->GetPFCells()->_bTableLevel; #endif if(pcLine) *pcLine = 0; // Default no lines in case of error // Need to use uCell to identify cell rather than iCell, since // horizontal merge can change iCell from row to row do // Backup row by row { if(ili > 0) { pli--; // Go to previous row ili--; } else if(pliMain) { pli = pliMain; ili = iliMain; pliMain = NULL; // Switch to pliMain only once! } else { AssertSz(FALSE, "CLayout::FindTopCell: no accessible top cell"); return NULL; } plo = pli->GetPlo(); // Get its cell display if(!plo || !plo->IsTableRow()) // Illegal structure or not table row { AssertSz(FALSE, "CLayout::FindTopCell: no accessible top cell"); return NULL; } pPF = plo->GetPFCells(); AssertSz(pPF->_bTableLevel == bTableLevel, "CLayout::FindTopCell: no accessible top cell"); prgCellParms = pPF->GetCellParms(); cCell = plo->Count(); iCell = prgCellParms->ICellFromUCell(dul, cCell); dy += pli->GetHeight(); // Add row height cch += pli->_cch; // Add in cch for whole row } while(!IsTopCell(prgCellParms[iCell].uCell)); cch -= 2; // Sub cch for StartRow delim pli = plo->Elem(0); // Point at 1st cell in row for(ili = 0; ili < iCell; ili++)// Sub cch's for cells cch -= (pli++)->_cch; // preceding iCellth cell if(pdvp) // Return top-cell height - heights of *pdvp = pli->GetHeight() - dy;// cells in between LONG cLine = 0; LONG dvpBrdrTop = plo->_dvpBrdrTop; ili = 0; dy -= dvpBrdrTop; plo = pli->GetPlo(); if(plo) // Top cell is multiline { cLine = plo->Count(); pli = plo->Elem(0); // Advance pli to line in plo if(pli->IsNestedLayout()) dy += dvpBrdrTop; while(ili < cLine && dy >= pli->GetHeight()) // nearest to input position { dy -= pli->GetHeight(); ili++; if(ili == cLine) // Done: leave pli pointing at last line break; cch -= pli->_cch; pli++; } } if(pcLine) *pcLine = cLine; return pli; } /* * CLayout::FindTopRow(pli, ili, pliMain, iliMain, pPF) * * @mfunc * Find CLine for top row in a table * * @rdesc * CLine for top row in table */ CLine * CLayout::FindTopRow( CLine * pli, //@parm Entry table-row line LONG ili, //@parm Corresponding line index CLine * pliMain, //@parm Line preceding first line accessible by pli LONG iliMain, //@parm Line index corresponding to pliMain const CParaFormat *pPF) //@parm CParaFormat for entry plo { BYTE bAlignment = pPF->_bAlignment; // Target row must have same BYTE bTableLevel = pPF->_bTableLevel; // alignment and level CLine * pliLast; CLayout *plo; do // Backup row by row { pliLast = pli; // Last line pointing at row in table if(ili > 0) { pli--; // Go to previous line ili--; } else if(pliMain) // More lines to go back to { pli = pliMain; ili = iliMain; pliMain = NULL; // Switch to pliMain only once! } else break; plo = pli->GetPlo(); // Get its cell display if(!plo || !plo->IsTableRow()) break; pPF = plo->GetPFCells(); } while(pPF->_bAlignment == bAlignment && pPF->_bTableLevel == bTableLevel); return pliLast; } /* * CLayout::GetCFCells() * * @mfunc * Return CCharFormat for the table row described by this CLayout * * @rdesc * Table row CCharFormat */ const CCharFormat* CLayout::GetCFCells() { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CLayout::GetCFCells"); Assert(_iCFCells >= 0); const CCharFormat *pCF; if(FAILED(GetCharFormatCache()->Deref(_iCFCells, &pCF))) { AssertSz(FALSE, "CLayout::GetCFCells: couldn't deref _iCFCells"); pCF = NULL; } return pCF; } /* * CLayout::GetPFCells() * * @mfunc * Return CParaFormat for the table row described by this CLayout * * @rdesc * Table row CParaFormat */ const CParaFormat* CLayout::GetPFCells() const { TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CLayout::GetPFCells"); Assert(_iPFCells >= 0); const CParaFormat *pPF; if(FAILED(GetParaFormatCache()->Deref(_iPFCells, &pPF))) { AssertSz(FALSE, "CLayout::GetPF: couldn't deref _iPFCells"); pPF = NULL; } return pPF; } /* * CLayout::GetLORowAbove(pli, ili, pliMain, iliMain) * * @mfunc * Return CLayout for the table row described by the line above pli. * If not a table row, return NULL. * * @rdesc * Table row CLayout for row above pli's */ const CLayout* CLayout::GetLORowAbove( CLine * pli, //@parm Entry table-row line LONG ili, //@parm Corresponding line index CLine * pliMain, //@parm Line preceding first line accessible by pli LONG iliMain) //@parm Line index corresponding to pliMain { if(!ili && pliMain && iliMain) // More lines to go back to { pli = pliMain; ili = iliMain; } if(ili) { CLayout *plo = (pli - 1)->GetPlo(); // Get cell display for row above if(plo && plo->IsTableRow()) return plo; } return NULL; // No line above } /* * CLayout::CpFromPoint(&me, pt, prcClient, prtp, prp, fAllowEOL, phit, * pdispdim, pcpActual, pliParent, iliParent) * @mfunc * Determine cp at given point * * @devnote * --- Use when in-place active only --- * * @rdesc * Computed cp, -1 if failed */ LONG CLayout::CpFromPoint( CMeasurer &me, //@parm Measurer POINTUV pt, //@parm Point to compute cp at (client coords) const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active). CRchTxtPtr * const prtp,//@parm Returns text pointer at cp (may be NULL) CLinePtr * const prp, //@parm Returns line pointer at cp (may be NULL) BOOL fAllowEOL, //@parm Click at EOL returns cp after CRLF HITTEST * phit, //@parm Out parm for hit-test value CDispDim * pdispdim, //@parm Out parm for display dimensions LONG *pcpActual, //@parm Out cp that pt is above CLine * pliParent, //@parm Parent pli for table row displays LONG iliParent) //@parm Parent ili corresponding to pli { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::CpFromPoint"); LONG cch = 0; LONG cp = 0; HITTEST hit = HT_Nothing; LONG ili; CLine * pli; CLayout *plo = NULL; RECTUV rcView; int v = pt.v; // Save input y coordinate LONG yLine = 0; CDisplayML *pdp = (CDisplayML*) me.GetPdp(); if (IsNestedLayout()) rcView = *prcClient; else { pdp->GetViewRect(rcView, prcClient); pt.v += pdp->GetVpScroll(); if(pt.u >= 0) // If x coordinate is within view, pt.u += pdp->GetUpScroll(); // adjust by scroll value } if(phit) *phit = HT_Nothing; // Default in case early return // Get line under hit if(IsTableRow()) // This display is a table row { // Shrink to cell text boundaries pli = Elem(0); // Point at starting cell CLine // Move over to start of cells const CParaFormat *pPFCells = GetPFCells(); LONG dul = 0; LONG dulRTLRow = pPFCells->GetRTLRowLength(); LONG dup = 0; BOOL fCellLow; LONG h = me.LUtoDU(pPFCells->_dxOffset); const CELLPARMS *prgCellParms = pPFCells->GetCellParms(); LONG u; // Tracks start of text in cell LONG u0 = pli->_upStart; LONG uCell = 0; pt.v -= _dvpBrdrTop; // Subtract off border top cp = _cpMin; if(dulRTLRow) u0 += me.LUtoDU(dulRTLRow); ili = 0; while(1) { u = u0 + dup + h; // Indent in current cell cch = cp - _cpMin; uCell = prgCellParms[ili].uCell; fCellLow = IsLowCell(uCell); dul += GetCellWidth(uCell); me.SetDulLayout(GetCellWidth(uCell) - 2*pPFCells->_dxOffset); dup = me.LUtoDU(dul); if(!dulRTLRow && pt.u < u0 + dup ||// pt.u is inside current cell dulRTLRow && pt.u > u0 - dup) { LONG ili0 = iliParent; if(fCellLow) // Cell merged vertically { // with the one above it LONG dy = pt.v; CLine *pli0 = FindTopCell(cch, pliParent, ili0, dul, dy, NULL, NULL, 0, NULL); if(pli0) { // Found top cell cch += 2; // Include cch of row-start delim pli = pli0; // Use its pli and backup ili = ili0; cp -= cch; // Backup to start of pli pt.v += dy; } } if(!dulRTLRow && pt.u < u) { // In cell gap, so select cell hit = HT_LeftOfText; cch = 0; // Setup for start of row goto finish; } break; } cp += pli->_cch; // Add in cell's cch ili++; if(ili == Count()) { hit = HT_RightOfText; goto finish; } pli++; } LONG dupCell = me.LUtoDU(GetCellWidth(uCell)); if(dulRTLRow) pt.u -= me.LUtoDU(dulRTLRow - dul) + h; else pt.u -= dup - dupCell + h; rcView.right = dupCell - 2*h; pt.v -= GetVertAlignShift(uCell, pli->GetHeight()); } else // This display isn't a table row { // Adjust coordinates relative to view origin rcView.right -= rcView.left; pt.u -= rcView.left; pt.v -= rcView.top; ili = LineFromVpos(pdp, pt.v, &yLine, &cp); if(ili < 0) return -1; pli = Elem(ili); if(yLine + pli->GetHeight() < pt.v) hit = HT_BelowText; // Return hit below text } rcView.left = 0; rcView.top = 0; AssertSz(pli || !ili, "CLayout::CpFromPoint invalid line pointer"); if(pli) // Line exists, even it it's { // above or below current screen HITTEST hit0; if(v < rcView.top) // Note if hit occurs above or hit = HT_AboveScreen; // below text if(v > rcView.bottom && !IsNestedLayout()) hit = HT_BelowText; plo = pli->GetPlo(); pt.v -= yLine; if(plo) // Child layout { pt.u -= pli->_upStart; plo->_cpMin = cp; // Update child's _cpMin if(plo->IsTableRow()) // Table row { plo->_cpMin += 2; // Bypass TR start delimiter if(pt.u < 0) { plo = NULL; hit = HT_LeftOfText; // Return hit left of text Assert(cch >= 0); // (should be row) goto finish; } } cp = plo->CpFromPoint(me, pt, &rcView, prtp, prp, fAllowEOL, &hit0, pdispdim, pcpActual, pli, ili); if(cp == -1) return -1; cch = cp - _cpMin; } else // Leaf line { me.SetLayout(this); me.SetCp(cp); // Support khyphChangeAfter me.SetIhyphPrev(ili > 0 ? (pli - 1)->_ihyph : 0); // Get character in line cch = pli->CchFromUp(me, pt, pdispdim, &hit0, pcpActual); // Don't allow click at EOL to select EOL marker and take into // account single line edits as well if(cch == pli->_cch && pli->_cchEOP && (!fAllowEOL || me.GetPrevChar() == CELL)) { // Adjust position on line by amount backed up. OK for // me._rpCF and me._rpPF to get out of sync with me._rpTX, // since they're not needed for me.GetCp(). cch += me._rpTX.BackupCRLF(); } cp = me.GetCp(); } if(hit != HT_BelowText && hit != HT_AboveScreen || hit0 == HT_RightOfText) hit = hit0; } finish: if(!plo) // Store info from leaf line { if(prtp) prtp->SetCp(cp); if(prp) { Assert(cch >= 0); prp->Set(ili, cch, this); } } if (phit) *phit = hit; return cp; } /* * CLayout::PointFromTp(&me, rtp, prcClient, fAtEnd, pt, prp, taMode, pdispdim) * * @mfunc * Determine coordinates at given tp * * @devnote * --- Use when in-place active only --- * * @rdesc * line index at cp, -1 if error */ LONG CLayout::PointFromTp( CMeasurer &me, //@parm Measurer const CRchTxtPtr &rtp, //@parm Text ptr to get coordinates at const RECTUV *prcClient,//@parm Client rectangle (can be NULL if active). BOOL fAtEnd, //@parm Return end of prev line for ambiguous cp POINTUV & pt, //@parm Returns point at cp in client coords CLinePtr * const prp, //@parm Returns line pointer at tp (may be null) UINT taMode, //@parm Text Align mode: top, baseline, bottom CDispDim * pdispdim) //@parm Out parm for display dimensions { LONG cp = rtp.GetCp(); LONG dy = 0; RECTUV rcView; CDisplayML *pdp = (CDisplayML*) me.GetPdp(); CLinePtr rp(pdp); if(!pdp->WaitForRecalc(cp, -1)) return -1; if(!IsNestedLayout()) // Main display { if(!rp.SetCp(cp, fAtEnd)) return -1; pdp->GetViewRect(rcView, prcClient); pt.u = rcView.left - pdp->_upScroll; pt.v = rcView.top - pdp->_vpScroll; } else // Subdisplay { rp.Init(*this); rp.BindToCp(cp - _cpMin); if(fAtEnd && !IsTableRow()) // Ambiguous-cp caret position rp.AdjustBackward(); // belongs at prev EOL rcView = *prcClient; pt.u = rcView.left; pt.v = rcView.top; } AssertSz(pdp->_ped->_fInPlaceActive || prcClient, "Invalid client rect"); LONG ili = rp.GetLineIndex(); CLine *pli = NULL; CLayout *plo = NULL; LONG xEnd = -1; // pt.u to use at end of table row if(IsTableRow()) // This layout is a table row { // Shrink to cell text boundaries const CParaFormat *pPFCells = GetPFCells(); const CELLPARMS * prgCellParms = pPFCells->GetCellParms(); LONG dul = 0; LONG dulRTLRow = pPFCells->GetRTLRowLength(); LONG h = me.LUtoDU(pPFCells->_dxOffset); LONG i; cp = _cpMin; pli = Elem(0); for(i = 0; i < ili; i++, pli++) { dul += GetCellWidth(prgCellParms[i].uCell); cp += pli->_cch; } LONG uCell = prgCellParms[ili].uCell; me.SetDulLayout(GetCellWidth(uCell) - 2 * pPFCells->_dxOffset); if(dulRTLRow) { if(dul < dulRTLRow) { uCell = prgCellParms[ili + 1].uCell; dul += GetCellWidth(prgCellParms[i].uCell); } dul = dulRTLRow - dul; } rcView.left = pt.u + me.LUtoDU(dul) + h; rcView.right = pt.u + me.LUtoDU(dul + GetCellWidth(uCell)) - h; pt.u = rcView.left; if(!GetCellWidth(uCell)) { pt.v += _dvp; goto done; } if(ili + 1 == Count() && rp->_cch == rp.GetIch()) { xEnd = rcView.right + h + 1; if(dulRTLRow) xEnd = rcView.left - h - 1; } pt.v += GetVertAlignShift(uCell, pli->GetHeight()); if(!(taMode & TA_CELLTOP)) pt.v += _dvpBrdrTop; } else // This layout isn't a table row { pt.v += VposFromLine(pdp, ili); cp -= rp.GetIch(); } pli = Elem(ili); plo = pli->GetPlo(); if(plo) // Line has child display { // Define child rcView and delegate RECTUV rc; // to child pt.u += pli->_upStart; rc.left = pt.u; rc.right = pt.u + rcView.right - rcView.left; rc.top = pt.v; rc.bottom = pt.v + pli->GetHeight(); plo->_cpMin = cp; // Update child display's _cpMin if(plo->IsTableRow()) plo->_cpMin += 2; // Bypass table row start code if(plo->PointFromTp(me, rtp, &rc, fAtEnd, pt, prp, taMode, pdispdim) == -1) return -1; } else // Line is a leaf line { me.SetLayout(this); me.Move(-rp.GetIch()); // Backup to start of line me.NewLine(*rp); // Measure from there to where we are //Support khyphChangeAfter me.SetIhyphPrev(ili > 0 ? (pli - 1)->_ihyph : 0); LONG xCalc = rp->UpFromCch(me, rp.GetIch(), taMode, pdispdim, &dy); if(pt.u + xCalc <= rcView.right || !pdp->GetWordWrap() || pdp->GetTargetDev()) { // Width is in view or there is no wordwrap so just // add the length to the point. pt.u += xCalc; } else pt.u = rcView.right; //Hit-test went too far, limit it. pt.v += dy; } if(xEnd != -1) pt.u = xEnd; // Return x coord at end of table row done: if(prp && !plo) *prp = rp; // Return innermost rp return rp; // Return outermost iRun } /* * CLayout::Measure(&me, pli, ili, uiFlags, pliTarget, iliMain, pliMain, pdvpExtra) * * @mfunc * Computes line break (based on target device) and fills * in *pli with resulting metrics on rendering device * * @rdesc * TRUE if OK */ BOOL CLayout::Measure ( CMeasurer& me, //@parm Measurer pointing at text to measure CLine * pli, //@parm Line to store result in LONG ili, //@parm Line index corresponding to pli UINT uiFlags, //@parm Flags CLine * pliTarget, //@parm Returns target-device line metrics (optional) LONG iliMain, //@parm Line index corresponding to pliMain CLine * pliMain, //@parm Line preceding 1st line in pli layout (optional) LONG * pdvpExtra) //@parm Returns extra line height for vmrged cells (opt) //REVIEW (keithcu) pliTarget is busted in the recursive case. { CTxtEdit * ped = me.GetPed(); LONG cchText = ped->GetTextLength(); LONG cpSave = me.GetCp(); CLine * pliNew; const CDisplayML * pdp = (const CDisplayML *)me.GetPdp(); const CParaFormat *pPF = me.GetPF(); // Measure one line, which is either a table row or a line in a paragraph if(pPF->IsTableRowDelimiter()) { // Measure table row, which is modeled as a CLayout with one // CLine per cell. In the backing store, table rows start with // the two chars STARTFIELD CR and end with ENDFIELD CR. Cells // are delimited by CELL. LONG cpStart = me.GetCp(); LONG dul = 0; LONG dxCell = 0; LONG dvp = 0; LONG dvpMax = 0; CLayout * plo = new CLayout(); const CLayout * ploAbove = GetLORowAbove(pli, ili, pliMain, iliMain); const CELLPARMS *prgCellParms = pPF->GetCellParms(); if(!plo) return FALSE; plo->_iCFCells = me.Get_iCF(); plo->_iPFCells = me.Get_iPF(); pli->SetPlo(plo); AssertSz(pPF->_bTabCount && me.GetChar() == STARTFIELD, "Invalid table-row header"); me.Move(2); AssertSz(me.GetPrevChar() == CR, "Invalid table-row header"); plo->_cpMin = me.GetCp(); // Save current values LONG dulLayoutOld = me.GetDulLayout(); LONG dvlBrdrTop = 0; LONG dvlBrdrBot = 0; const CLayout *ploOld = me.GetLayout(); CArray rgpobjWrapOld; me._rgpobjWrap.TransferTo(rgpobjWrapOld); // Create CLines for each cell and measure them for(LONG iCell = 0; iCell < pPF->_bTabCount; iCell++) { me.SetNumber(0); LONG uCell = prgCellParms[iCell].uCell; dxCell = GetCellWidth(uCell); dul += dxCell; // Add a line for the next cell pliNew = plo->Add(1, NULL); if(!pliNew) return FALSE; LONG dvl = prgCellParms[iCell].GetBrdrWidthTop(); dvlBrdrTop = max(dvlBrdrTop, dvl); dvl = prgCellParms[iCell].GetBrdrWidthBottom(); dvlBrdrBot = max(dvlBrdrBot, dvl); if(!ploAbove) uCell &= ~fLowCell; // Can't be a low cell if no row above AssertSz(!IsLowCell(uCell) || me.GetChar() == NOTACHAR, "CLayout::Measure: invalid low cell"); me.SetLayout(plo); me.SetDulLayout(dxCell - 2*pPF->_dxOffset); plo->Measure(me, pliNew, iCell, uiFlags | MEASURE_FIRSTINPARA, pliTarget, iliMain, pliMain); if(IsLowCell(uCell)) { // If a low cell in set of vertically merged cells, check // if corresponding cell on next row is also merged CPFRunPtr rp(me); rp.FindRowEnd(pPF->_bTableLevel); const CParaFormat *pPF1 = rp.GetPF(); BOOL fBottomCell = !pPF1->IsTableRowDelimiter(); if(!fBottomCell) { const CELLPARMS *prgCellParms1 = pPF1->GetCellParms(); LONG iCell1 = prgCellParms1->ICellFromUCell(dul, pPF1->_bTabCount); if(iCell1 >= 0 && !IsLowCell(prgCellParms1[iCell1].uCell)) fBottomCell = TRUE; } if(fBottomCell) { // Need to include top cell in current row height // calculation LONG cch = me.GetCp() - cpStart; LONG dy1 = 0; LONG iliT = ili; LONG dvpCell = 0; if(!FindTopCell(cch, pli, iliT, dul, dy1, &dvpCell, pliMain, iliMain, NULL)) uCell &= ~fLowCell; // Not a valid low cell else if(dvpCell > 0) dvp = max(dvp, dvpCell); } } if(!IsVertMergedCell(uCell) && dxCell || !dvp && iCell == pPF->_bTabCount - 1) dvp = max(pliNew->GetHeight(), dvp); dvpMax = max(dvpMax, pliNew->GetHeight()); } //Restore original values me.SetDulLayout(dulLayoutOld); me.SetLayout(ploOld); me.SetIhyphPrev(0); me._rgpobjWrap.Clear(AF_DELETEMEM); rgpobjWrapOld.TransferTo(me._rgpobjWrap); #ifdef DEBUG // Bypass table-row terminator if(me.GetChar() != ENDFIELD) me._rpTX.MoveGapToEndOfBlock(); AssertSz(me.GetPrevChar() == CELL && pPF->_bTabCount == plo->Count(), "Incorrect table cell count"); AssertSz(me.GetChar() == ENDFIELD, "CLayout::Measure: invalid table-row terminator"); me._rpPF.AdjustForward(); const CParaFormat *pPFme = me.GetPF(); AssertSz(pPFme->IsTableRowDelimiter(), "CLayout::Measure: invalid table-row terminator"); #endif me.UpdatePF(); // me._pPF points at TRD PF me.Move(2); // Bypass table row terminator AssertSz(me.GetPrevChar() == CR, "CLayout::Measure: invalid table-row terminator"); if(me.IsHidden()) { CCFRunPtr rp(me); me.Move(rp.FindUnhiddenForward()); } if(me.GetChar() == CELL) // Bypass possible CELL delimeter { // at end of table row (happens Assert(pPF->_bTableLevel > 1); // when table row is last line CTxtSelection *psel = ped->GetSelNC(); // of cell if(!psel || psel->GetCch() || // Don't bypass CELL if selection psel->GetCp() !=me.GetCp() ||// is an IP at this position, !psel->GetShowCellLine()) // i.e., display a blank line { me.Move(1); pli->_fIncludeCell = TRUE; } } plo->_dvpBrdrBot = me.GetPBorderWidth(dvlBrdrBot); plo->_dvpBrdrTop = me.GetPBorderWidth(dvlBrdrTop); if(ploAbove) plo->_dvpBrdrTop = max(plo->_dvpBrdrTop, ploAbove->_dvpBrdrBot); dvp += plo->_dvpBrdrTop; // Add top border width if(!me.GetPF()->IsTableRowDelimiter())// End of table: add in dvp += plo->_dvpBrdrBot; // bottom border width // Define CLine parameters for table row if(pPF->_dyLineSpacing) { LONG dvpLine = me.LUtoDU(pPF->_dyLineSpacing); if(dvpLine < 0) // Negative row height means use dvp = -dvpLine; // the magnitude exactly else dvp = max(dvp, dvpLine); // Positive row height means } // "at least" plo->_dvp = dvp; dvpMax = max(dvpMax, dvp); if(pdvpExtra) *pdvpExtra = dvpMax - dvp; // Fill in CLine structure for row pli->_cch = me.GetCp() - cpSave; pli->_fFirstInPara = pli->_fHasEOP = TRUE; pli->_dup = me.LUtoDU(dul); me._li._fFirstInPara = TRUE; pli->_upStart = me.MeasureLeftIndent(); me.MeasureRightIndent(); // Define me._upEnd pli->_cObjectWrapLeft = me._li._cObjectWrapLeft; pli->_cObjectWrapRight = me._li._cObjectWrapRight; USHORT dvpLine = plo->_dvp; USHORT dvpDescent = 0; me.UpdateWrapState(dvpLine, dvpDescent); pli->_fFirstWrapLeft = me._li._fFirstWrapLeft; pli->_fFirstWrapRight = me._li._fFirstWrapRight; if(!pdp->IsInOutlineView() && IN_RANGE(PFA_RIGHT, pPF->_bAlignment, PFA_CENTER)) { // Normal view with center or flush-right para. Move right accordingly // If not top row of like-aligned rows, use indent of top row CLine *pliFirst = FindTopRow(pli, ili, pliMain, iliMain, pPF); if(pli != pliFirst) pli->_upStart = pliFirst->_upStart; else { LONG uShift = me.LUtoDU(dulLayoutOld - dul); uShift = max(uShift, 0); // Don't allow alignment to go < 0 // Can happen with a target device if(pPF->_bAlignment == PFA_CENTER) uShift /= 2; pli->_upStart = uShift; } } me.SetNumber(0); // Update me._wNumber in case next } // para is numbered else if(!pli->Measure(me, uiFlags, pliTarget)) // Not a table row return FALSE; // Measure failed if(pli->_fFirstInPara && pPF->_wEffects & PFE_PAGEBREAKBEFORE) pli->_fPageBreakBefore = TRUE; me.SetIhyphPrev(pli->_ihyph); if(!IsTableRow() || me.GetPrevChar() == CELL)// Not a table row display or return TRUE; // cell text fits on 1 line // Multiline table cell: allocate its CLayout CLayout *plo = new CLayout(); if(!plo) return FALSE; // Not enuf RAM plo->_cpMin = cpSave; pliNew = plo->Add(1, NULL); if(!pliNew) { ped->GetCallMgr()->SetOutOfMemory(); TRACEWARNSZ("Out of memory Recalc'ing lines"); return FALSE; } *pliNew = *pli; // Copy first line of cell layout pli->SetPlo(plo); // Turn line into a layout line // Calculate remaining lines in cell. // Eventually would be nice to share this code with RecalcLines() BOOL fFirstInPara; LONG dvp = pliNew->GetHeight(); LONG iliNew = 0; while(me.GetCp() < cchText) { fFirstInPara = pliNew->_fHasEOP; pliNew = plo->Add(1, NULL); iliNew++; if(!pliNew) { ped->GetCallMgr()->SetOutOfMemory(); TRACEWARNSZ("Out of memory Recalc'ing lines"); return FALSE; } // New table row can start after EOP, i.e., allow recursion here uiFlags = MEASURE_BREAKATWORD | (fFirstInPara ? MEASURE_FIRSTINPARA : 0); if(!plo->Measure(me, pliNew, iliNew, uiFlags, pliTarget)) { Assert(FALSE); return FALSE; } dvp += pliNew->GetHeight(); if(me.GetPrevChar() == CELL) break; // Done with current cell } pli->_upStart = 0; plo->_dvp = dvp; pli->_cch = me.GetCp() - cpSave; return TRUE; } /* * CLayout::Render(&re, pli, prcView, fLastLine, ili, cLine) * * @mfunc * Render visible part of the line *pli * * @rdesc * TRUE iff successful * * @devnote * re is moved past line (to beginning of next line). * FUTURE: the RenderLine functions return success/failure. * Could do something on failure, e.g., be specific and fire * appropriate notifications like out of memory or character * not in font. Note that CLayout::_cpMin isn't used in * rendering, so we don't have to update it the way we do in * the query functions. */ BOOL CLayout::Render( CRenderer & re, //@parm Renderer to use CLine * pli, //@parm Line to render const RECTUV *prcView, //@parm View rect to use BOOL fLastLine,//@parm TRUE iff last line of control LONG ili, //@parm Line index of pli LONG cLine) //@parm # lines in pli's CLayout { TRACEBEGIN(TRCSUBSYSDISP, TRCSCOPEINTERN, "CLayout::Render"); CLayout *plo = pli->GetPlo(); if(!plo) return pli->Render(re, fLastLine); // Render leaf line LONG cLine1 = plo->Count(); // Count of lines in sublayout LONG ili1; // Index of first line in sublayout CLine * pli1 = plo->Elem(0); // Ptr to first line in sublayout POINTUV pt; if(plo->IsTableRow()) // Line's nested display is a table row { // Render table row, which is modeled as a CLayout with one // CLine per cell. In the backing store, table rows start with // the two chars STARTFIELD CR and end with ENDFIELD CR. Cells // are terminated by CELL. const CLayout * ploAbove = GetLORowAbove(pli, ili); const CParaFormat * pPF = plo->GetPFCells(); const CELLPARMS * prgCellParms = pPF->GetCellParms(); LONG cpStart = re.GetCp(); LONG dul = 0; BOOL fSetErase = FALSE; LONG hl = pPF->_dxOffset; // Logical half gap LONG h = re.LUtoDU(hl); // Device half gap RECTUV rcView; LONG u = prcView->left + pli->_upStart - re._pdp->GetUpScroll(); // Bypass table-row start AssertSz(pPF->_bTabCount && re.GetChar() == STARTFIELD, "Invalid table-row header"); AssertSz(pPF == re.GetPF(), "Invalid table-row pPF"); re.Move(2); AssertSz(re.GetPrevChar() == CR, "Invalid table-row header"); // Save current state LONG crBackOld = re.GetDefaultBackColor(); LONG crTextOld = re.GetDefaultTextColor(); LONG dulLayoutOld = re.GetDulLayout(); LONG dulRTLRow = pPF->GetRTLRowLength(); LONG dvpBrdrTop = plo->_dvpBrdrTop; CLine * pli0; POINTUV ptOld = re.GetCurPoint(); RECTUV rcRender; RECTUV rcRenderOld = re.GetRcRender(); RECTUV rcViewOld = re.GetRcView(); const CLayout *ploOld = re.GetLayout(); rcView.left = u + h; // Default for LTR row rcView.right = rcView.left; // Suppress compiler warning rcView.top = ptOld.v; rcRender.top = rcView.top; rcView.bottom = rcView.top + pli->GetHeight(); rcRender.bottom = rcView.bottom; if(dulRTLRow) rcView.right = u + re.LUtoDU(dulRTLRow); // Render each cell for(ili1 = 0; ili1 < cLine1; ili1++, pli1++) { LONG dvp = 0; // Additional cell height if LONG uCell = prgCellParms[ili1].uCell; dul += GetCellWidth(uCell); re.SetLayout(pli1->GetPlo()); re.SetDulLayout(GetCellWidth(uCell) - 2*hl); // Reduce roundoff by converting dul instead of multiple uCell if(dulRTLRow) // Right-To-Left row rcView.left = u + h + re.LUtoDU(dulRTLRow - dul); // Convert horizontal coords else rcView.right = u + re.LUtoDU(dul); rcRender.left = rcView.left - h; rcRender.right = rcView.right; //Set state re.StartRender(rcView, rcRender); pt.u = rcView.left; pt.v = rcView.top + plo->GetVertAlignShift(uCell, pli1->GetHeight()); if(!IsLowCell(uCell)) pt.v += dvpBrdrTop; re.SetRcViewTop(pt.v); // Clear to top of cell re.SetCurPoint(pt); if(IsTopCell(uCell)) { // Calculate bottom of set of vertically merged cells LONG ili0; LONG iCell; CLayout *plo0; const CELLPARMS *prgCellParms0; for(ili0 = ili + 1, pli0 = pli + 1; ili0 < cLine; ili0++, pli0++) { plo0 = pli0->GetPlo(); if(!plo0 || !plo0->IsTableRow()) break; prgCellParms0 = plo0->GetPFCells()->GetCellParms(); iCell = prgCellParms0->ICellFromUCell(dul, plo0->Count()); if(iCell < 0 || !IsLowCell(prgCellParms0[iCell].uCell)) break; dvp += pli0->GetHeight(); // Add row height } if(dvp) { rcView.bottom += dvp; rcRender.bottom += dvp; re.SetRcBottoms(rcView.bottom, rcRender.bottom); } } COLORREF crf = crTextOld; LONG icrf = prgCellParms[ili1].GetColorIndexForegound(); LONG icrb = prgCellParms[ili1].GetColorIndexBackgound(); if(icrf | icrb) // If any nonzero bits, { // calc special color BYTE bS = prgCellParms[ili1].bShading; COLORREF crb = re.GetShadedColorFromIndices(icrf, icrb, bS, pPF); fSetErase = re.EraseRect(&rcRender, crb); if(IsTooSimilar(crf, crb)) crf = re.GetShadedColorFromIndices(icrb, icrf, bS, pPF); } else re.SetDefaultBackColor(crBackOld); re.SetDefaultTextColor(crf); if(!ploAbove) uCell &= ~fLowCell; // Can't be low cell if no row above if(IsLowCell(uCell)) // Cell merged vertically with { // the one above it LONG cch = re.GetCp() -cpStart; // Use cLine0, ili0, pli0 to LONG cLine0; // refer to text in set LONG cpNext = re.GetCp() // of vert merged cells + (re.GetChar() == NOTACHAR ? 2 : 1); LONG dy = 0; LONG ili0 = ili; // Get target line to display pli0 = FindTopCell(cch, pli, ili0, dul, dy, NULL, NULL, 0, &cLine0); if(!pli0) uCell &= ~fLowCell; // Whoops, no cell above else { pt.v -= dy; re.SetCurPoint(pt); re.Move(-cch); for(; ili0 < cLine0; ili0++, pli0++) { //Support khyphChangeAfter re.SetIhyphPrev(ili0 > 0 ? (pli0 - 1)->_ihyph : 0); if(!Render(re, pli0, &rcView, ili0 == cLine0 - 1, ili0, cLine0)) return FALSE; } re.SetCp(cpNext); // Bypass [NOTACHAR] CELL } } if(!IsLowCell(uCell)) // Solo cell or top cell of { // vertically merged set if(!Render(re, pli1, &rcView, !pli1->GetPlo(), ili1, cLine1)) return FALSE; if(dvp) // Rendered set of vmerged cells { rcView.bottom -= dvp; // Restore rcView/rcRender bottoms rcRender.bottom -= dvp; re.SetRcBottoms(rcView.bottom, rcRender.bottom); } } if(fSetErase) re.SetErase(TRUE); // Restore CRenderer::_fErase re.SetRcViewTop(rcView.top); // Restore re._rcView.top in case changed if(dulRTLRow) // Restore rcView.right rcView.right = rcView.left - h; else rcView.left = rcView.right + h; } //Restore previous state re.SetLayout(ploOld); re.SetDulLayout(dulLayoutOld); re.SetDefaultBackColor(crBackOld); re.SetDefaultTextColor(crTextOld); re.StartRender(rcViewOld, rcRenderOld); re.SetCurPoint(ptOld); // Bypass table-row terminator AssertSz(re.GetPrevChar() == CELL && pPF->_bTabCount == plo->Count(), "CLayout::Render:: incorrect table cell count"); AssertSz(re.GetChar() == ENDFIELD, "CLayout::Render: invalid table-row terminator"); re.Move(2); // Bypass table row terminator AssertSz(re.GetPrevChar() == CR, "invalid table-row terminator"); BOOL fDrawBottomLine = !re._rpTX.IsAtTRD(STARTFIELD); LONG dvp = re.DrawTableBorders(pPF, u, plo->_dvp, fDrawBottomLine | fLastLine*2, dul, ploAbove ? ploAbove->GetPFCells() : NULL); if(re.IsHidden()) { CCFRunPtr rp(re); re.Move(rp.FindUnhiddenForward()); } if(re.GetChar() == CELL && pli->_fIncludeCell) { Assert(pPF->_bTableLevel > 1); re.Move(1); // Bypass CELL at end of cell } // containing a table ptOld.v += pli->GetHeight() + dvp; // Advance to next line position re.SetCurPoint(ptOld); if(fLastLine) re.EraseToBottom(); return TRUE; } RECTUV rcRender = re.GetRcRender(); LONG dvpBottom = min(prcView->bottom, rcRender.bottom); LONG dvpTop = max(prcView->top, rcRender.top); LONG v0; dvpTop = max(dvpTop, 0); // Line's nested layout is a regular layout galley, i.e., not a table row for(ili1 = 0; ili1 < cLine1; ili1++, pli1++) { pt = re.GetCurPoint(); v0 = pt.v + pli1->GetHeight(); fLastLine = ili1 == cLine1 - 1 || v0 >= dvpBottom; //Support khyphChangeAfter re.SetIhyphPrev(ili1 > 0 ? (pli1 - 1)->_ihyph : 0); if(v0 < dvpTop) { pt.v = v0; // Advance to next line position re.SetCurPoint(pt); re.Move(pli1->_cch); } else if(pt.v >= dvpBottom) re.Move(pli1->_cch); // Get to end of nested display else if(!Render(re, pli1, prcView, fLastLine, ili1, cLine1)) return FALSE; } return TRUE; } /* * CLayout::GetVertAlignShift(uCell, dypText) * * @mfunc * Render visible part of the line *pli * * @rdesc * Vertical shift for cell text * * @devnote * Calculating this shift for vertically merged cells is tricky because * dypCell = sum of the cell heights of all cells in the vertically * merged set. In particular, if the table is not nested, one needs to * wait for recalc of all rows in the set. dypText is relatively easy * since it's the height of the top cell in the set. */ LONG CLayout::GetVertAlignShift( LONG uCell, //@parm uCell to use LONG dypText) //@parm Text height in cell { LONG dyp = 0; if(IsVertMergedCell(uCell)) { } else if(GetCellVertAlign(uCell)) { dyp = _dvp - _dvpBrdrTop - _dvpBrdrBot - dypText; if(dyp > 0 && IsCellVertAlignCenter(uCell)) dyp /= 2; } return dyp; }