/*--------------------------------------------------------------------*//*:Ignore this sentence. Copyright (C) 1999, 2001 SIL International. All rights reserved. Distributable under the terms of either the Common Public License or the GNU Lesser General Public License, as specified in the LICENSING.txt file. File: GrTableManager.cpp Responsibility: Sharon Correll Last reviewed: Not yet. Description: GrTableManager, which handles the interactions between passes, including the demand-pull algorithm. -------------------------------------------------------------------------------*//*:End Ignore*/ //:>******************************************************************************************** //:> Include files //:>******************************************************************************************** #include "Main.h" #ifdef _MSC_VER #pragma hdrstop #endif #undef THIS_FILE DEFINE_THIS_FILE #ifndef _WIN32 #include #include #endif //:>******************************************************************************************** //:> Forward declarations //:>******************************************************************************************** //:>******************************************************************************************** //:> Local Constants and static variables //:>******************************************************************************************** namespace gr { //:>******************************************************************************************** //:> Methods //:>******************************************************************************************** /*---------------------------------------------------------------------------------------------- Constructors and initializers ----------------------------------------------------------------------------------------------*/ EngineState::EngineState() : m_ipassJustCalled(-1), m_fStartLineContext(false), m_fEndLineContext(false), ////m_pgg(NULL), m_cslotPreSeg(0), m_islotblkCurr(-1), m_islotNext(kSlotBlockSize), m_fInitialLB(false), m_fFinalLB(false), m_fInsertedLB(false), m_fExceededSpace(false), m_fHitHardBreak(false), m_fRemovedWhtsp(false), m_dxsShrinkPossible(GrSlotState::kNotYetSet), m_islotPosNext(-1), m_xsPosXNext(0), m_ysPosYNext(0), m_dxsTotWidthSoFar(0), m_dxsVisWidthSoFar(0), m_prgzpst(NULL), m_prgpsstrm(NULL) { m_vslotblk.clear(); m_vprgnSlotVarLenBufs.clear(); } void EngineState::Initialize(GrEngine * pgreng, GrTableManager * ptman) { m_ptman = ptman; m_cpass = ptman->NumberOfPasses(); // duplicate for convenience m_cUserDefn = pgreng->NumUserDefn(); m_cFeat = pgreng->NumFeat(); m_cCompPerLig = pgreng->NumCompPerLig(); if (m_prgzpst) delete[] m_prgzpst; m_prgzpst = new PassState[m_cpass]; ptman->StorePassStates(m_prgzpst); } /*---------------------------------------------------------------------------------------------- Destructors ----------------------------------------------------------------------------------------------*/ GrTableManager::~GrTableManager() { for (int ipass = 0; ipass < m_cpass; ++ipass) delete Pass(ipass); delete[] m_prgppass; } EngineState::~EngineState() { DestroySlotBlocks(); delete[] m_prgzpst; if (m_prgpsstrm) { for (int isstrm = 0; isstrm < m_cpass; ++isstrm) delete OutputStream(isstrm); delete[] m_prgpsstrm; } } /*---------------------------------------------------------------------------------------------- Delete all the slots. ----------------------------------------------------------------------------------------------*/ void EngineState::DestroySlotBlocks() { Assert(m_vslotblk.size() == m_vprgnSlotVarLenBufs.size()); for (size_t islotblk = 0; islotblk < m_vslotblk.size(); ++islotblk) { delete[] m_vslotblk[islotblk]; delete[] m_vprgnSlotVarLenBufs[islotblk]; } m_vslotblk.clear(); m_vprgnSlotVarLenBufs.clear(); } /*---------------------------------------------------------------------------------------------- Create the passes, and them fill them in by reading from the file stream. ----------------------------------------------------------------------------------------------*/ bool GrTableManager::CreateAndReadPasses(GrIStream & grstrm, int fxdSilfVersion, int fxdRuleVersion, int cpassFont, long lSubTableStart, int * rgnPassOffsets, int ipassSub1Font, int ipassPos1Font, int ipassJust1Font, byte ipassPostBidiFont) { Assert(ipassSub1Font <= ipassJust1Font); Assert(ipassJust1Font <= ipassPos1Font); Assert(ipassPos1Font <= cpassFont); if (ipassSub1Font > ipassJust1Font || ipassJust1Font > ipassPos1Font || ipassPos1Font > cpassFont) { return false; // bad table } // Adjusted indices based on the fact that we have a glyph generation pass that is // pass 0, and possibly a bidi pass: m_cpass = cpassFont + 1; int ipassLB1, ipassSub1, ipassBidi, ipassPos1, ipassJust1; ipassLB1 = 1; ipassSub1 = ipassSub1Font + 1; if (ipassPostBidiFont == 0xFF) { // No bidi pass. m_fBidi = false; // Add 1 below to account for the glyph-generation pass. ipassPos1 = ipassPos1Font + 1; ipassJust1 = ipassJust1Font + 1; ipassBidi = ipassJust1; } else { m_fBidi = true; m_cpass++; Assert(ipassPostBidiFont == ipassJust1Font); ipassJust1 = ipassJust1Font + 2; ipassPos1 = ipassPos1Font + 2; ipassBidi = ipassJust1 - 1; } // Always make at least one positioning pass, even if there are no rules. if (ipassPos1 == m_cpass) { m_cpass++; } m_prgppass = new GrPass*[m_cpass + 1]; m_prgppass[0] = new GrGlyphGenPass(0); int ipass = 1; int ipassFont = 0; m_cpassLB = 0; m_ipassJust1 = 1; m_ipassPos1 = 1; for ( ; ipass < m_cpass; ipass++, ipassFont++) { if (ipass < ipassSub1) { m_prgppass[ipass] = new GrLineBreakPass(ipass); m_prgppass[ipass]->ReadFromFont(grstrm, fxdSilfVersion, fxdRuleVersion, lSubTableStart + rgnPassOffsets[ipassFont]); m_cpassLB++; m_ipassJust1++; m_ipassPos1++; } else if (ipassSub1 <= ipass && ipass < ipassBidi) { m_prgppass[ipass] = new GrSubPass(ipass); m_prgppass[ipass]->ReadFromFont(grstrm, fxdSilfVersion, fxdRuleVersion, lSubTableStart + rgnPassOffsets[ipassFont]); m_ipassJust1++; m_ipassPos1++; } else if (ipassBidi == ipass && ipass < ipassJust1) { m_prgppass[ipass] = new GrBidiPass(ipass); m_prgppass[ipass]->SetTopDirLevel(TopDirectionLevel()); ipassFont--; // no corresponding pass in font m_ipassJust1++; m_ipassPos1++; } else if (ipassJust1 <= ipass && ipass < ipassPos1) { // A justification pass is, in essence, a substitution pass. m_prgppass[ipass] = new GrSubPass(ipass); m_prgppass[ipass]->ReadFromFont(grstrm, fxdSilfVersion, fxdRuleVersion, lSubTableStart + rgnPassOffsets[ipassFont]); m_ipassPos1++; } else if (ipassPos1 <= ipass) { m_prgppass[ipass] = new GrPosPass(ipass); if (ipassFont < cpassFont) { m_prgppass[ipass]->ReadFromFont(grstrm, fxdSilfVersion, fxdRuleVersion, lSubTableStart + rgnPassOffsets[ipassFont]); } else { // No positioning pass in font: create a bogus one. m_prgppass[ipass]->InitializeWithNoRules(); } } else { Assert(false); return false; // bad table } } return true; } /*---------------------------------------------------------------------------------------------- Create passes corresponding to an invalid font. ----------------------------------------------------------------------------------------------*/ void GrTableManager::CreateEmpty() { m_cpass = 2; m_prgppass = new GrPass*[2]; m_prgppass[0] = new GrGlyphGenPass(0); m_prgppass[1] = new GrPosPass(1); m_prgppass[1]->InitializeWithNoRules(); m_ipassPos1 = 1; m_cpassLB = 0; m_fBidi = false; m_engst.CreateEmpty(); } void EngineState::CreateEmpty() { m_fStartLineContext = false; m_fEndLineContext = false; } /*---------------------------------------------------------------------------------------------- Create the slot streams corresponding to the passes. ----------------------------------------------------------------------------------------------*/ void EngineState::CreateSlotStreams() { if (m_prgpsstrm) return; m_prgpsstrm = new GrSlotStream * [m_cpass]; for (int ipass = 0; ipass < m_cpass; ++ipass) { m_prgpsstrm[ipass] = new GrSlotStream(ipass); } } /*---------------------------------------------------------------------------------------------- Store information about the pass-states in the pass objects. ----------------------------------------------------------------------------------------------*/ void GrTableManager::StorePassStates(PassState * rgzpst) { for (int ipass = 0; ipass < m_cpass; ipass++) Pass(ipass)->SetPassState(rgzpst + ipass); } /*---------------------------------------------------------------------------------------------- Demand-driven loop: with goal of filling up final slot stream, loop backward and forward filling up slot streams until final stream has reached physical capacity. @param pgjus - justification agent; NULL if justification is of no interest @param jmodi - (internal) justification mode @param fStartLine, fEndLine @param ichSegLim - known end of segment; -1 when using backtracking @param dxWidthRequested - available width; when justifying, exact width desired @param dxUnjustifiedWidth - when justifying, what the width would be normally @param fNeedFinalBreak - true if the end of the segment needs to be a valid break point @param fMoreText - true if char stream doesn't go up to text-stream lim @param ichFontLim - end of the range that could be rendered by this font @param fInfiniteWidth - used for "measured" segments @param fWidthIsCharCount - kludge for test procedures: the width is a character count, not a pixel count @param lbPref - try for longest segment ending with this breakweight - when justifying, lb to assume @param lbMax - max (last resort) breakweight if no preferred break possible @param ichwCallerBtLim - caller's backtrack lim; -1 if caller is not backtracking @param twsh - how to handle trailing white-space @param fParaRtl - overall paragraph direction @param nDirDepth - direction depth of this segment ----------------------------------------------------------------------------------------------*/ void GrTableManager::Run(Segment * psegNew, Font * pfont, GrCharStream * pchstrm, IGrJustifier * pgjus, int jmodi, LayoutEnvironment & layout, int ichSegLim, float dxWidthRequested, float dxUnjustifiedWidth, bool fNeedFinalBreak, bool fMoreText, int ichFontLim, bool fInfiniteWidth, bool fWidthIsCharCount, int ichwCallerBtLim, int nDirDepth, SegEnd estJ) { bool fStartLine = layout.startOfLine(); bool fEndLine = layout.endOfLine(); LineBrk lbPref = layout.bestBreak(); LineBrk lbMax = layout.worstBreak(); TrWsHandling twsh = layout.trailingWs(); bool fParaRtl = layout.rightToLeft(); Segment * psegPrev = layout.prevSegment(); Segment * psegInit = layout.segmentForInit(); std::ostream * pstrmLog = layout.loggingStream(); m_engst.Initialize(Engine(), this); // do this after the tables have been fully read m_engst.m_jmodi = jmodi; m_engst.m_ipassJustCalled = -1; m_engst.m_twsh = twsh; m_engst.m_fParaRtl = fParaRtl; m_engst.m_dxsShrinkPossible = GrSlotState::kNotYetSet; m_fLogging = (pstrmLog != NULL); int cbPrev; byte * pbPrevSegDat = NULL; if (psegPrev) cbPrev = psegPrev->NextSegInitBuffer(&pbPrevSegDat); else if (psegInit) cbPrev = psegInit->ThisSegInitBuffer(&pbPrevSegDat); else cbPrev = 0; if (pchstrm->IsEmpty()) { m_engst.m_lbPrevEnd = klbNoBreak; InitSegmentAsEmpty(psegNew, pfont, pchstrm, fStartLine, fEndLine); psegNew->SetWidths(0, 0); psegNew->SetUpOutputArrays(pfont, this, NULL, 0, 0, 0, twsh, fParaRtl, nDirDepth, true); psegNew->SetLayout(layout); return; } int islotUnderBreak = -1; // position of inserted or final break point in underlying // data; none so far (if an actual LB has been inserted, // this is the index of that slot; otherwise it is the // index of the last valid slot) int islotSurfaceBreak = -1; // position of line break in surface stream; none so far LineBrk lbBestToTry = lbPref; LineBrk lbFound = klbNoBreak; float dxMaxWidth = dxWidthRequested; if (m_engst.m_jmodi == kjmodiJustify) { Assert(fInfiniteWidth); lbBestToTry = klbClipBreak; lbFound = lbPref; } float dxWidth; std::vector vnslotNeeded; vnslotNeeded.resize(m_cpass); m_engst.InitializeStreams(this, pchstrm, cbPrev, pbPrevSegDat, fNeedFinalBreak, &islotUnderBreak); int ipassCurr; int nNeedInput; // kNextPass: go on to the next pass // kBacktrack: need to insert a line break // > 0: need more input from previous pass int cchwPostXlbContext = m_pgreng->PostXlbContext(); if (ichSegLim > -1) { // When we know exactly where the end of the segment will be, get as much as // we need from the character stream, and then fill up the remaining streams. // (3 below is an arbitrary number; we just want to get slightly more than // we really need.) vnslotNeeded[0] = ichSegLim - pchstrm->Min() + cchwPostXlbContext + 3; for (int ipass = 1; ipass < m_cpass; ipass++) vnslotNeeded[ipass] = 10000; // Start at pass 0. ipassCurr = 0; } else { // When we're doing line-breaking to find the end of the segment, use the // demand-driven approach that tries to fill up the available space. vnslotNeeded[m_cpass - 1] = 1; // Start at the last pass. (It might be more efficient to start at the first pass and // generate a small arbitrary number of glyphs.) ipassCurr = m_cpass - 1; } while (ipassCurr < m_cpass) { if (ipassCurr == 0) { int nNeedNow = vnslotNeeded[0]; // Zeroth pass: generate glyph IDs from the underlying input. nNeedInput = Pass(0)->ExtendGlyphIDOutput(this, pchstrm, OutputStream(0), ichSegLim, cchwPostXlbContext, lbPref, nNeedNow, fNeedFinalBreak, m_engst.m_twsh, &islotUnderBreak); Assert(nNeedInput == kNextPass || nNeedInput == kBacktrack); vnslotNeeded[0] -= nNeedNow; } else if (ipassCurr < m_cpass - 1) { // Middle pass. int cslotNeedNow = vnslotNeeded[ipassCurr]; cslotNeedNow = max(1, cslotNeedNow); int cslotGot; Pass(ipassCurr)->ExtendOutput(this, InputStream(ipassCurr), OutputStream(ipassCurr), cslotNeedNow, twsh, &nNeedInput, &cslotGot, &islotUnderBreak); vnslotNeeded[ipassCurr] -= cslotGot; //if (nNeedInput != kNextPass && nNeedInput != kBacktrack) // nNeedInput = max(nNeedInput, cslotNeedNow - cslotGot); } else // (ipassCurr == m_cpass - 1) { // Final pass: position and test allotted space. float dxWidthWShrink = dxMaxWidth; if (m_engst.m_dxsShrinkPossible != GrSlotState::kNotYetSet) dxWidthWShrink += (int)m_engst.m_dxsShrinkPossible; nNeedInput = Pass(ipassCurr)->ExtendFinalOutput(this, InputStream(ipassCurr), OutputStream(ipassCurr), dxWidthWShrink, fWidthIsCharCount, fInfiniteWidth, (islotUnderBreak > -1), (fNeedFinalBreak && lbFound == klbNoBreak), lbMax, twsh, &islotSurfaceBreak, &dxWidth); } if (m_engst.m_jmodi == kjmodiJustify) { if (ipassCurr < m_ipassJust1 && !OutputStream(ipassCurr)->FullyWritten()) { // When justifying, fill up all the streams up to the point where we need // to interact with the justification routine. // I think this code is obsolete. int nStillNeed = 1000; if ((ipassCurr == 1 && nNeedInput != kNextPass) || ipassCurr == 0) nStillNeed = ichSegLim - (pchstrm->Pos() - pchstrm->Min()) + cchwPostXlbContext + 3; if (nStillNeed > 0) { if (nNeedInput == kNextPass) ipassCurr++; // repeat this pass nNeedInput = nStillNeed; } } else if (nNeedInput == kNextPass && ipassCurr + 1 == m_ipassJust1) { CallJustifier(pgjus, ipassCurr, dxUnjustifiedWidth, dxWidthRequested, fEndLine); } } DetermineShrink(pgjus, ipassCurr); if (nNeedInput == kBacktrack) { // Final output has overflowed its space--find (or adjust) the break point. bool fFoundBreak = Backtrack(&islotUnderBreak, &lbBestToTry, lbMax, twsh, fMoreText, ichwCallerBtLim, &lbFound); if (!fFoundBreak) { // Nothing will fit. Initialize the new segment just enough so that // we can delete it. InitSegmentToDelete(psegNew, pfont, pchstrm); return; } } else if (nNeedInput == kNextPass) { // This pass is sufficiently full--return to following pass. ipassCurr++; } else { // Get more input from previous pass. ipassCurr--; vnslotNeeded[ipassCurr] = nNeedInput; } } // At this point we have fully transduced all the text for this segment. // Figure out why we broke the segment and what the caller is supposed to do about it. SegEnd est; if (m_engst.m_jmodi == kjmodiJustify) { // Don't change what was passed in. est = estJ; } else if (m_engst.m_fRemovedWhtsp) // (islotUnderBreak > -1 && twsh == ktwshNoWs) { Assert(!m_engst.m_fInsertedLB); est = kestMoreWhtsp; } else if (m_engst.m_fHitHardBreak) { est = kestHardBreak; } else if (!m_engst.m_fExceededSpace && !fMoreText) // Rendered to the end of the original requested range. est = kestNoMore; // else if (!m_fExceededSpace && !fNextIsSameWs && fMoreText) // // Writing system break; may be more room on the line. But see tweak below. // *pest = kestWsBreak; else if (m_engst.m_fExceededSpace) { // Exceeded available space, found a legal break. if (twsh == ktwshNoWs && m_engst.m_fRemovedWhtsp) est = kestMoreWhtsp; else est = kestMoreLines; } else if (twsh == ktwshOnlyWs) { est = kestOkayBreak; // but see tweak below } else { // Broke because of something like a font change. Determine whether this is a legal // break or not. // TODO SharonC: handle the situation where it is legal to break BEFORE the first // character of the following segment. Assert(fMoreText); //Assert(fNextIsSameWs); if (ichwCallerBtLim > -1) { Assert(m_engst.m_fInsertedLB); } else if (ichSegLim > -1) { // We stopped where caller said to. Assert(fInfiniteWidth); } else { Assert(!m_engst.m_fInsertedLB); Assert(islotUnderBreak == -1 || m_engst.m_fFinalLB); } int islotTmp = OutputStream(m_cpass - 1)->WritePos(); GrSlotState * pslotTmp; if (m_engst.m_fFinalLB || m_engst.m_fInsertedLB) pslotTmp = OutputStream(m_cpass-1)->SlotAt(islotTmp - 2); else pslotTmp = OutputStream(m_cpass-1)->SlotAt(islotTmp - 1); if (abs(pslotTmp->BreakWeight()) <= lbPref) est = kestOkayBreak; else if (abs(pslotTmp->BreakWeight()) > lbMax) est = kestBadBreak; else if (OutputStream(m_cpassLB)->HasEarlierBetterBreak(islotUnderBreak, lbFound, this->LBGlyphID())) { est = kestBadBreak; } else est = kestOkayBreak; // We no longer have the ability to know whether or not the next segment will use // the same writing system. //if (est == kestBadBreak && m_engst.m_fExceededSpace && !fNextIsSameWs && fMoreText) // est = kestWsBreak; } // Make a segment out of it and return it. int dichSegLen; InitNewSegment(psegNew, pfont, pchstrm, pgjus, islotUnderBreak, islotSurfaceBreak, fStartLine, fEndLine, ichFontLim, lbFound, est, &dichSegLen); if (psegNew->segmentTermination() == kestNothingFit) { return; } else if (psegNew->segmentTermination() == kestHardBreak && psegNew->startCharacter() == psegNew->stopCharacter()) { // Empty segment caused by a hard-break. m_engst.m_lbPrevEnd = klbNoBreak; psegNew->SetWidths(0, 0); psegNew->SetUpOutputArrays(pfont, this, NULL, 0, 0, 0, twsh, fParaRtl, nDirDepth, true); psegNew->SetLayout(layout); return; } psegNew->RecordInitializationForThisSeg(cbPrev, pbPrevSegDat); if ((est == kestNoMore || est == kestWsBreak) && twsh == ktwshOnlyWs && dichSegLen < pchstrm->Lim() - pchstrm->Min()) { // If we are asking for white-space only, and we didn't use up everything in the font // range, we could possibly have gotten an initial white-space only segment // (as opposed to a writing system break). est = kestOkayBreak; psegNew->FixTermination(est); } // Do this before fixing up the associations below. //if (m_pgreng->LoggingTransduction()) // WriteTransductionLog(pchstrm, *ppsegRet, // cbPrev, pbPrevSegDat, pbNextSegDat, pcbNextSegDat); WriteTransductionLog(pstrmLog, pchstrm, psegNew, cbPrev, pbPrevSegDat); RecordAssocsAndOutput(pfont, psegNew, fWidthIsCharCount, twsh, fParaRtl, nDirDepth); // Do this after fixing up the associations. //if (m_pgreng->LoggingTransduction()) // WriteAssociationLog(pchstrm, *ppsegRet); WriteAssociationLog(pstrmLog, pchstrm, psegNew); psegNew->SetLayout(layout); } /*---------------------------------------------------------------------------------------------- Create a new segment to return. @param pchstrm - input stream @param pgjus - justification agent, in case this segment needs to be stretched or shrunk @param islotLBStreamBreak - position of inserted line break slot (which should be equal to the position in the glyph generation stream); -1 if no line-break was inserted (we rendered to the end of the range) @param islotSurfaceBreak - position of line break slot in final (surface) stream @param fStartLine - does this segment start a line? @param fEndLine - does this segment end a line? @param ichFontLim - end of range that could be rendered by this font @param lbEndFound - line break found; possibly adjusted here @param est - why did the segment terminate ----------------------------------------------------------------------------------------------*/ void GrTableManager::InitNewSegment(Segment * psegNew, Font * pfont, GrCharStream * pchstrm, IGrJustifier * pgjus, int islotLBStreamBreak, int islotSurfaceBreak, bool fStartLine, bool fEndLine, int ichFontLim, LineBrk lbEndFound, SegEnd est, int * pdichSegLen) { LineBrk lbStart = m_engst.m_lbPrevEnd; LineBrk lbEnd = lbEndFound; if (est == kestBadBreak) lbEnd = klbLetterBreak; int ichwMin = pchstrm->Min(); int ichwLim; // int cslotPreSegStream0 = m_cslotPreSeg; // if (m_fInitialLB && m_cpassLB > 0) // { // // Initial LB is included in m_cslotPreSeg, but is not in stream 0. // // --Not really needed, because both islotLBStreamBreak and m_cslotPreSeg take // // the initial LB into account if it is there. // cslotPreSegStream0--; // } // No longer have to subtract this, because the chunk map takes it into // consideration. //int cslotPreInitialLB = m_cslotPreSeg - (m_fInitialLB ? 1 : 0); //int ichwOldTmp; if (m_engst.m_fInsertedLB) { Assert(islotLBStreamBreak > -1); int ichwSegLim = m_engst.LbSlotToSegLim(islotLBStreamBreak, pchstrm, m_cpassLB); ichwLim = ichwSegLim + pchstrm->Min(); // - cslotPreInitialLB; // Old result, for debugging: //ichwOldTmp = islotLBStreamBreak + pchstrm->Min() - m_cslotPreSeg; } else if (m_engst.m_fFinalLB || islotLBStreamBreak == -1) ichwLim = pchstrm->Lim(); else { int ichwSegLim = m_engst.LbSlotToSegLim(islotLBStreamBreak, pchstrm, m_cpassLB); ichwLim = ichwSegLim + pchstrm->Min(); // - cslotPreInitialLB; // Old result, for debugging: // islotLBStreamBreak is the last slot in the segment, not the LB glyph; hence +1. //ichwOldTmp = islotLBStreamBreak + pchstrm->Min() - m_cslotPreSeg + 1; } *pdichSegLen = ichwLim - ichwMin; if (ichwMin >= ichwLim) { if (est == kestHardBreak) { // Empty segment cause by a hard-break. InitSegmentAsEmpty(psegNew, pfont, pchstrm, fStartLine, fEndLine); psegNew->FixTermination(est); } else { // Invalid empty segment. Assert(ichwMin == ichwLim); InitSegmentToDelete(psegNew, pfont, pchstrm); } return; } Assert(psegNew); psegNew->Initialize(pchstrm->TextSrc(), ichwMin, ichwLim, lbStart, lbEnd, est, fStartLine, fEndLine, m_pgreng->RightToLeft()); psegNew->SetEngine(m_pgreng); psegNew->SetFont(pfont); psegNew->SetJustifier(pgjus); psegNew->SetFaceName(m_pgreng->FaceName(), m_pgreng->BaseFaceName()); bool fNextSegNeedsContext = !(est == kestNoMore || est == kestWsBreak || ichwLim >= pchstrm->Lim() || ichwLim >= ichFontLim); InitializeForNextSeg(psegNew, islotLBStreamBreak, islotSurfaceBreak, lbEnd, fNextSegNeedsContext, pchstrm); psegNew->SetPreContext(0 - m_pgreng->PreXlbContext()); } /*---------------------------------------------------------------------------------------------- The newly created segment is invalid, for instance, because we were backtracking and couldn't find a valid break point. Initialize it just enough so that it can be deleted by the client. ----------------------------------------------------------------------------------------------*/ void GrTableManager::InitSegmentToDelete(Segment * psegNew, Font * pfont, GrCharStream * pchstrm) { psegNew->Initialize(pchstrm->TextSrc(), 0, 0, klbClipBreak, klbClipBreak, kestNothingFit, false, false, m_pgreng->RightToLeft()); psegNew->SetEngine(m_pgreng); psegNew->SetFont(pfont); psegNew->SetJustifier(NULL); // can't justify an empty segment psegNew->SetFaceName(m_pgreng->FaceName(), m_pgreng->BaseFaceName()); psegNew->SetPreContext(0); } /*---------------------------------------------------------------------------------------------- Map from the given slot (which is generally the final LB slot) to the end of the segment, taking into account the fact that a single slot may represent two 16-bit surrogates. There are several approaches that seem reasonable but do NOT work. (I tried them!) You can't calculate it directly from the length of the streams, because that doesn't take into account the possible presence of surrogate pairs in the character stream. You can't use the chunk map at the point of the LB, because if a rule spanned a LB the chunk won't have ended there and so there won't be any valid information. What seems to work is to figure out the slot(s) in the zeroth stream using the associations and then use the segment offset for this slot. This should be reliable because there is always a one-to-one correspondence between glyphs in the line-break passes (ie, no insertions or deletions), so the associations should be dependable and straightforward. ----------------------------------------------------------------------------------------------*/ int EngineState::LbSlotToSegLim(int islotLB, GrCharStream * pchstrm, int cpassLB) { Assert(islotLB < OutputStream(cpassLB)->WritePos()); // Figure out the corresponding characters in the zeroth stream. These should be straight- // forward to figure out because no substitutions are permitted in the LB passes. // I do the slots before and after the LB, if possible, as a sanity check. GrSlotStream * psstrmLB = OutputStream(cpassLB); GrSlotState * pslotPreLB = (m_fFinalLB || m_fInsertedLB) ? psstrmLB->SlotAt(islotLB - 1) : psstrmLB->SlotAt(islotLB); GrSlotState * pslotPostLB = (psstrmLB->WritePos() > islotLB + 1) ? psstrmLB->SlotAt(islotLB + 1) : NULL; int ichwPreLB = pslotPreLB->AfterAssoc(); int ichwSegLim; if (pslotPostLB) { ichwSegLim = pslotPostLB->BeforeAssoc(); Assert(ichwPreLB + 1 == ichwSegLim || ichwPreLB + 2 == ichwSegLim); // in case of a surrogate } else { ichwSegLim = ichwPreLB + 1; while (!pchstrm->AtUnicodeCharBoundary(ichwSegLim)) ichwSegLim++; Assert(pchstrm->AtUnicodeCharBoundary(ichwSegLim)); } // Old buggy routine that doesn't handle the case where a rule (and hence the chunk map) did // not end at the LB character: // Map backwards through the LB pass streams, if any. //int islotAdjusted = islotLB + 1; // after the LB //for (int ipass = m_cpassLB; ipass > 0; ipass--) //{ // islotAdjusted = ChunkInPrev(ipass, islotAdjusted, pchstrm); //} // //int ichwSegLim = ChunkInPrev(0, islotAdjusted, pchstrm); // Mapping from 32-bit to 16-bit should only increase the index. Except that there // may be a LB in the slot stream that is not in the character stream, so it could be // one less. Assert(ichwSegLim + m_cslotPreSeg >= islotLB - 1); return ichwSegLim; } /*---------------------------------------------------------------------------------------------- Return the chunk mapping into the previous stream, taking into account the fact that it is not recorded for the write-position. ----------------------------------------------------------------------------------------------*/ int GrTableManager::ChunkInPrev(int ipass, int islot, GrCharStream * pchstrm) { GrSlotStream * psstrmOut = OutputStream(ipass); GrSlotStream * psstrmIn = (ipass == 0) ? NULL : InputStream(ipass); int islotTmp = islot; int islotAdjusted; do { if (islot >= psstrmOut->WritePos()) { Assert(islot == psstrmOut->WritePos()); islotAdjusted = (ipass == 0) ? pchstrm->SegOffset() : psstrmIn->ReadPos(); Assert(islotAdjusted > -1); } else islotAdjusted = psstrmOut->ChunkInPrev(islotTmp); islotTmp--; } while (islotAdjusted == -1); return islotAdjusted; } /*---------------------------------------------------------------------------------------------- Create a new segment corresponding to an empty string. @param pchstrm - input stream @param fStartLine - does this segment start a line? @param fEndLine - does this segment end a line? ----------------------------------------------------------------------------------------------*/ void GrTableManager::InitSegmentAsEmpty(Segment * psegNew, Font * pfont, GrCharStream * pchstrm, bool fStartLine, bool fEndLine) { LineBrk lbStart = m_engst.m_lbPrevEnd; LineBrk lbEnd = klbNoBreak; int ichwMin = 0; int ichwLim = 0; Assert(psegNew); psegNew->Initialize(pchstrm->TextSrc(), ichwMin, ichwLim, lbStart, lbEnd, kestNoMore, fStartLine, fEndLine, m_pgreng->RightToLeft()); psegNew->SetEngine(m_pgreng); psegNew->SetFont(pfont); psegNew->SetJustifier(NULL); // can't justify an empty segment psegNew->SetFaceName(m_pgreng->FaceName(), m_pgreng->BaseFaceName()); byte pbNextSegDat[256]; int cbNextSegDat; int * pcbNextSegDat = &cbNextSegDat; // Initialization for (theoretical) next segment. byte * pb = pbNextSegDat; *pb++ = byte(lbEnd); *pb++ = kdircNeutral; *pb++ = kdircNeutral; *pb++ = 0; for (int ipass = 0; ipass < m_cpass; ipass++) *pb++ = 0; *pcbNextSegDat = 0; psegNew->RecordInitializationForNextSeg(*pcbNextSegDat, pbNextSegDat); psegNew->SetPreContext(0); } /*---------------------------------------------------------------------------------------------- Find a line-break and unwind the passes. Return true if we were able to backtrack successfully (which may have meant using a less-than-optimal line-break). Return false if no line-break could be found (because maxBreakWeight did not allow letter breaks or clipping--generally because there was already something on this line). @param pislotPrevBreak - position of previously-created break, -1 if none; this is the index of the line-break glyph if any, or the index of the last slot in the segment; adjusted to contain the position of the newly found break @param plbMin - minimum (best) line break weight to try @param lbMax - maximum (worst possible) line break weight @param twsh - how to handle trailing white-space @param fMoreText - true if char stream doesn't go up to original lim @param ichwCallerBtLim - caller's backtrack lim; -1 if caller is not backtracking @param plbFound - kind of line-break created ----------------------------------------------------------------------------------------------*/ bool GrTableManager::Backtrack(int * pislotPrevBreak, LineBrk * plbMin, LineBrk lbMax, TrWsHandling twsh, bool fMoreText, int ichwCallerBtLim, LineBrk * plbFound) { int islotStartTry; //if (*pislotPrevBreak == ichwCallerBtLim - 1) //{ // islotStartTry = *pislotPrevBreak; //} //else if (*pislotPrevBreak == -1) { // If no line break has been found so far, figure out where to start trying based // on where the final stream choked. GrSlotStream * psstrmFinal = OutputStream(m_cpass-1); if ((islotStartTry = m_engst.TraceStreamZeroPos(psstrmFinal->WritePos()-1, TopDirectionLevel())) == -1) { // Just start looking at the end of what's been generated. islotStartTry = OutputStream(m_cpassLB)->ReadPos() - 1; } } else { // Start just before previous line break. if (m_engst.m_fInsertedLB || m_engst.m_fFinalLB) islotStartTry = *pislotPrevBreak - 2; // skip inserted LB and previous slot else islotStartTry = *pislotPrevBreak - 1; // skip previous slot } if (ichwCallerBtLim > -1 && islotStartTry > ichwCallerBtLim - 1) { islotStartTry = ichwCallerBtLim - 1; } // Determine if we want to insert an actual line-break "glyph". Don't do that if // we are omitting trailing white space or we are at a writing-system or font break. bool fInsertLB; if (twsh == ktwshNoWs) // omitting trailing white-space fInsertLB = false; else if (twsh == ktwshOnlyWs) // trailing white-space segment fInsertLB = true; else if (ichwCallerBtLim > -1) // backtracking fInsertLB = true; else if (*pislotPrevBreak > -1) // no longer at the edge of the writing system or font fInsertLB = true; else if (!fMoreText) // at final edge of total range to render fInsertLB = true; else fInsertLB = false; // Try to insert a line-break in the output of the (final) line-break pass (if any, or // the output of the glyph-generation pass). First try the preferred (strictest) // break weight and gradually relax. LineBrk lb = *plbMin; Assert(*plbMin <= lbMax); GrSlotStream * psstrmLB = OutputStream(m_cpassLB); islotStartTry = min(islotStartTry, psstrmLB->WritePos() - 1); int islotNewBreak = -1; while (lb <= lbMax) { LineBrk lbNextToTry; if (fInsertLB) { islotNewBreak = psstrmLB->InsertLineBreak(this, *pislotPrevBreak, m_engst.m_fInsertedLB, islotStartTry, lb, twsh, m_engst.m_cslotPreSeg, &lbNextToTry); } else { islotNewBreak = psstrmLB->MakeSegmentBreak(this, *pislotPrevBreak, m_engst.m_fInsertedLB, islotStartTry, lb, twsh, m_engst.m_cslotPreSeg, &lbNextToTry); } if (islotNewBreak > -1) break; if (lb >= lbMax) break; lb = IncLineBreak(lb); // relax the break weight } if (islotNewBreak == -1) { return false; } // We've successfully inserted a line break. if (fInsertLB) m_engst.m_fInsertedLB = true; m_engst.m_fFinalLB = false; UnwindAndReinit(islotNewBreak); *pislotPrevBreak = islotNewBreak; *plbMin = lb; // return the best break we were able to find, so we don't keep trying // for a better one when we know now that we can't find it *plbFound = lb; return true; } /*---------------------------------------------------------------------------------------------- This method is called after backtracking or removing trailing white-space. Unwind the following slot streams as necessary depending on where the change was made. @param islotNewBreak - in output of (final) line-break pass ----------------------------------------------------------------------------------------------*/ void GrTableManager::UnwindAndReinit(int islotNewBreak) { OutputStream(m_cpassLB)->ZapCalculatedDirLevels(islotNewBreak); // Mark the passes before the line-break pass as fully written, so that if there // isn't a line-break glyph to intercept, we don't keep trying to get more input // from them. int ipass; for (ipass = 1; ipass < m_cpassLB + 1; ++ipass) { InputStream(ipass)->MarkFullyWritten(); } // Unwind the passes to the changed positions. // Note that we don't need to unwind pass 0, since all it does is generate glyph IDs. // Also we don't need to unwind the line-break passes, because conceptually they happen // before the break is inserted. int islotChgPos = islotNewBreak; bool fFirst = true; for (ipass = m_cpassLB + 1 ; ipass < m_cpass ; ++ipass) { islotChgPos = Pass(ipass)->Unwind(this, islotChgPos, InputStream(ipass), OutputStream(ipass), fFirst); fFirst = false; } // For anything that may have been skipped in the final output: OutputStream(m_cpass - 1)->SetReadPos(0); OutputStream(m_cpass - 1)->SetReadPosMax(0); Pass(m_cpass - 1)->UndoResyncSkip(); OutputStream(m_cpass - 1)->ClearSlotsSkippedToResync(); m_engst.InitPosCache(); m_engst.m_dxsShrinkPossible = GrSlotState::kNotYetSet; // recalculate it } /*---------------------------------------------------------------------------------------------- Store information in the newly created segment that will allow us to reinitalize the streams in order to process the next segment, appropriately handling any cross-line-boundary contextuals. The information required is the number of glyphs in the first stream that must be reprocessed, and a skip-offset between each stream indicating the beginning of chunk boundaries. In most cases, where there are no cross-line-boundary contextuals, all these numbers will be zero. The buffer generated is of the following format (all are 1 byte, unsigned): end breakweight previous strong directionality code previous terminator dir code restart backup skip offsets for each pass This format must match what is generated by InitializeStreams. @param pseg - newly created segment @param islotUnderBreak - position of line break inserted in underlying stream (output of glyph-generation pass--stream 0); -1 if we rendered to the end of the range @param islotSurfaceBreak - position of line break in surface stream; -1 if we rendered to the end of the range @param lbEnd - kind of line-break at the end of this segment @param fNextSegNeedsContext - true if we need to remember the context for the next seg @param pchstrm - character stream input for this segment @param cbNextMax - amount of space available in the buffer @param pbNextSegDat - buffer for initializing next segment @param pcbNextSegDat - amount of space used in buffer ----------------------------------------------------------------------------------------------*/ void GrTableManager::InitializeForNextSeg(Segment * pseg, int islotUnderBreak, int islotSurfaceBreak, LineBrk lbEnd, bool fNextSegNeedsContext, GrCharStream * pchstrm) { byte pbNextSegDat[256]; int cbNextMax = 256; int cbNextSegDat; int * pcbNextSegDat = &cbNextSegDat; std::vector vcslotSkipOffsets; vcslotSkipOffsets.resize(m_cpass); byte * pb = pbNextSegDat; if (!fNextSegNeedsContext) { // No contextualization between this segment and the next, whatever kind of renderer // it might use. *pcbNextSegDat = 0; return; } gid16 chwLB = LBGlyphID(); int islotUnderLim = islotUnderBreak; if (!m_engst.m_fInsertedLB && !m_engst.m_fFinalLB) // Then islotUnderBreak is the position of the last slot in the segment; increment // to get the lim in stream zero. islotUnderLim++; int islotSurfaceLim = islotSurfaceBreak; if (islotSurfaceLim == -1) islotSurfaceLim = OutputStream(m_cpass - 1)->FinalSegLim(); *pb++ = byte(lbEnd); // Find previous strong and terminator directionality codes. GrSlotStream * psstrm = OutputStream(m_cpass - 1); DirCode dircStrong = kdircNeutral; DirCode dircTerm = kdircNeutral; int islot; for (islot = islotSurfaceLim; islot-- > 0; ) { GrSlotState * pslot = psstrm->SlotAt(islot); DirCode dirc = pslot->Directionality(); if (dircTerm == kdircNeutral && dirc == kdircEuroTerm) dircTerm = dirc; if (StrongDir(dirc)) { dircStrong = dirc; break; } } *pb++ = byte(dircStrong); *pb++ = byte(dircTerm); // Record how much of each pass to redo in the next segment. // The basic algorithm is described in the "WR Data Transform Engine" document. // But here we are doing a variation on what is described there. We are calculating // the backup locations for BOTH the rule start locations and the necessary pre-contexts. // It is the precontext values that are recorded for use by the following segment. But we // also calculate the positions for the rule-start locations to make sure there is enough // pre-context at each pass. int islotRS = islotSurfaceLim; // rule-start positions int islotPC = islotRS; // to handle pre-context positions // If line-breaks are irrelevant to this font, we don't need to bother. bool fGiveUp = !m_pgreng->LineBreakFlag(); int ipass; for (ipass = m_cpass - 1; ipass >= 0 && !fGiveUp; --ipass) { GrSlotStream * psstrmIn = (ipass == 0) ? NULL : InputStream(ipass); GrSlotStream * psstrmOut = OutputStream(ipass); int islotBackupMin = (ipass == 0) ? 0 : psstrmOut->SegMin(); if (ipass >= m_cpassLB && m_engst.m_fInitialLB) islotBackupMin++; // how far we can back up--not before the start of this segment int islotPrevRS = 0; // corresponding slot in the previous stream for rule-start position int islotPrevPC = 0; // same for pre-context position // Initially we have to include at least this many prior slots: int cslotSkipOffset = 0; if (islotRS == psstrmOut->WritePos()) { islotPrevRS = (ipass == 0) ? pchstrm->Pos() : psstrmIn->ReadPos(); } else { // Ignore the final line-break, because it is explicitly inserted during // the (final) line-break pass. // if (ipass == m_cpassLB && psstrm->SlotAt(islotRS)->IsFinalLineBreak(chwLB)) // { // Assert(islotRS > 0); // islotRS--; // } // Ignore the chunk in the pass that generated the line break that corresponds // to the inserted line break being chunked with the previous glyph--we don't // want to treat that as a cross-line-boundary contextual! if (ipass == m_cpassLB && psstrmOut->SlotAt(islotRS)->IsFinalLineBreak(chwLB)) { // Assert(psstrm->ChunkInPrev(islotRS) == -1); if (islotRS + 1 == psstrmOut->WritePos()) islotPrevRS = (ipass == 0) ? pchstrm->SegOffset() : psstrmIn->ReadPos(); else islotPrevRS = psstrmOut->ChunkInPrev(islotRS + 1); } else { // Back up to the beginning of a chunk. while (islotRS >= islotBackupMin && (islotPrevRS = psstrmOut->ChunkInPrev(islotRS)) == -1) { --islotRS; //++cslotSkipOffset; } } } if (islotPrevRS == -1 || islotRS < islotBackupMin) // hit the start of the segment { // In order to get enough context to be sure to run the same rules again, // we'd have to back up into the previous segment. Too complicated, so give up. fGiveUp = true; break; } int cslotPreContext = Pass(ipass)->MaxRulePreContext(); LBackupPC: if (islotPC == psstrmOut->WritePos()) { islotPrevPC = (ipass == 0) ? pchstrm->Pos() : psstrmIn->ReadPos(); } else { if (ipass == m_cpassLB && psstrmOut->SlotAt(islotPC)->IsFinalLineBreak(chwLB)) { if (islotPC+1 == psstrmOut->WritePos()) islotPrevPC = (ipass == 0) ? pchstrm->SegOffset() : psstrmIn->ReadPos(); else islotPrevPC = psstrmOut->ChunkInPrev(islotPC+1); } else { // Back up to the beginning of a chunk; also make sure there are enough slots // to handle the required pre-context for this pass. while (islotPC >= islotBackupMin && ((islotPrevPC = psstrmOut->ChunkInPrev(islotPC)) == -1 || islotPrevRS - islotPrevPC < cslotPreContext)) { --islotPC; ++cslotSkipOffset; } } } if (islotPC >= islotBackupMin && islotPrevRS - islotPrevPC < cslotPreContext) { --islotPC; ++cslotSkipOffset; goto LBackupPC; } if (islotPrevPC == -1 || islotPC < islotBackupMin) // hit the start of the segment { fGiveUp = true; break; } vcslotSkipOffsets[ipass] = cslotSkipOffset; islotRS = islotPrevRS; islotPC = islotPrevPC; if (ipass == 0) { // Convert from underlying chars to stream-zero slots. islotRS += m_engst.m_cslotPreSeg; islotPC += m_engst.m_cslotPreSeg; } } // The following should be the case unless they tried to position contextually across // the line break boundary, which is a very strange thing to do! - no longer true, // because the pre-context is now included in this count, and it normal to have // a pre-context in the final positioning pass. //Assert(vcslotSkipOffsets[m_cpass-1] == 0); // Also no adjustments are made by the glyph-generation pass, so this should always // be true: Assert(vcslotSkipOffsets[0] == 0); // The number of slots in the underlying input previous to the line-break // that must be reprocessed: int cslotPreLB = islotUnderLim - islotPC; if (fGiveUp) { // We can't reasonably provide enough context, so don't try to do it at all. cslotPreLB = 0; for (ipass = 0; ipass < m_cpass; ipass++) vcslotSkipOffsets[ipass] = 0; } Assert(cslotPreLB >= 0); Assert(cslotPreLB < 0xFF); *pb++ = byte(cslotPreLB); for (ipass = 0; ipass < m_cpass; ipass++) *pb++ = byte(vcslotSkipOffsets[ipass]); *pcbNextSegDat = 4 + m_cpass; pseg->RecordInitializationForNextSeg(*pcbNextSegDat, pbNextSegDat); } /*---------------------------------------------------------------------------------------------- Calculate the associations, and record the output slots in the segment. ----------------------------------------------------------------------------------------------*/ void GrTableManager::RecordAssocsAndOutput(Font * pfont, Segment * pseg, bool fWidthIsCharCount, TrWsHandling twsh, bool fParaRtl, int nDirDepth) { int cchwUnderlying = pseg->stopCharacter() - pseg->startCharacter(); GrSlotStream * psstrmFinal = OutputStream(m_cpass-1); int csloutSurface = psstrmFinal->WritePos() - psstrmFinal->IndexOffset(); // # of output slots psstrmFinal->SetNeutralAssociations(LBGlyphID()); float xsTotalWidth, xsVisWidth; #ifdef OLD_TEST_STUFF if (fWidthIsCharCount) xsTotalWidth = xsVisWidth = 0; else #endif // OLD_TEST_STUFF // Make sure the final positions are set for every glyph. CalcPositionsUpTo(m_cpass-1, reinterpret_cast(NULL), &xsTotalWidth, &xsVisWidth); pseg->SetWidths(xsVisWidth, xsTotalWidth); pseg->SetUpOutputArrays(pfont, this, psstrmFinal, cchwUnderlying, csloutSurface, LBGlyphID(), twsh, fParaRtl, nDirDepth); // Set underlying-to-surface assocations in the segment. CalculateAssociations(pseg, csloutSurface); } /*---------------------------------------------------------------------------------------------- Calculate the underlying-to-surface associations and ligature mappings. Assumes the arrays have been properly initialized. ----------------------------------------------------------------------------------------------*/ void GrTableManager::CalculateAssociations(Segment * pseg, int csloutSurface) { GrSlotStream * psstrmFinal = OutputStream(m_cpass-1); std::vector vichwAssocs; std::vector vichwComponents; for (int islot = psstrmFinal->IndexOffset(); islot < psstrmFinal->WritePos(); islot++) { GrSlotState * pslot = psstrmFinal->SlotAt(islot); int islout = islot - psstrmFinal->IndexOffset(); if (!pslot->IsLineBreak(LBGlyphID())) { vichwAssocs.clear(); pslot->AllAssocs(vichwAssocs); size_t iichw; for (iichw = 0; iichw < vichwAssocs.size(); ++iichw) { pseg->RecordSurfaceAssoc(vichwAssocs[iichw], islout, 0); } vichwComponents.clear(); if (pslot->HasComponents()) pslot->AllComponentRefs(vichwComponents); for (iichw = 0; iichw < vichwComponents.size(); iichw++) { pseg->RecordLigature(vichwComponents[iichw], islout, iichw); } } } AdjustAssocsForOverlaps(pseg); pseg->CleanUpAssocsVectors(); // pseg->AdjustForOverlapsWithPrevSeg(); // for any characters officially in the // previous segment but rendered in this one, // or vice versa // TODO SharonC: for all characters that are right-to-left in the underlying stream, // reverse the before and after flags in the segment. } /*---------------------------------------------------------------------------------------------- For any slots associated with characters in the new segment but not rendered in the segment, adjust the underlying-to-surface mappings accordingly. Ie, set the before mapping to kNegInfinity for slots at the beginning of the streams, and set the after mapping to kPosInfinity for slots at the end of the streams. TODO SharonC: Rework this method more carefully to handle each slot exactly once. The current approach could possibly, for instance, process a slot that is inserted in one pass and deleted in the next. ----------------------------------------------------------------------------------------------*/ void GrTableManager::AdjustAssocsForOverlaps(Segment * pseg) { if (!m_engst.m_fInitialLB && !m_engst.m_fFinalLB && !m_engst.m_fInsertedLB) // no cross-line contextualization possible return; gid16 chwLB = LBGlyphID(); std::vector vichwAssocs; // Process any slots output by any substitution pass (or later) on either side of the // line-breaks. These are the relevant slots that are rendered in the previous and // following segments. for (int ipass = m_cpass; ipass-- > m_cpassLB + 1; ) { GrSlotStream * psstrm = OutputStream(ipass); int islotMin = (ipass == m_cpass - 1) ? psstrm->IndexOffset() : 0; if (m_engst.m_fInitialLB) { for (int islot = islotMin; ; islot++) { GrSlotState * pslot = psstrm->SlotAt(islot); if (pslot->IsInitialLineBreak(chwLB)) break; Assert(!pslot->IsFinalLineBreak(chwLB)); if (pslot->PassModified() != ipass) continue; vichwAssocs.clear(); pslot->AllAssocs(vichwAssocs); for (size_t iichw = 0; iichw < vichwAssocs.size(); ++iichw) pseg->MarkSlotInPrevSeg(vichwAssocs[iichw], islot); } } if (m_engst.m_fFinalLB || m_engst.m_fInsertedLB && ipass > m_cpassLB) { for (int islot = psstrm->WritePos(); islot-- > islotMin ; ) { GrSlotState * pslot = psstrm->SlotAt(islot); if (pslot->IsFinalLineBreak(chwLB)) break; Assert(!pslot->IsInitialLineBreak(chwLB)); if (pslot->PassModified() != ipass) continue; vichwAssocs.clear(); pslot->AllAssocs(vichwAssocs); for (size_t iichw = 0; iichw < vichwAssocs.size(); ++iichw) pseg->MarkSlotInNextSeg(vichwAssocs[iichw], islot); } } } } /*---------------------------------------------------------------------------------------------- Initialize all the streams. In particular, process the information about the skip-offsets to handle segments other than the first. @param cbPrev - number of bytes in the pbPrevSegDat buffer @param pbPrevSegDat - buffer in the following format (all are 1 byte, unsigned): prev seg's end breakweight prev seg's previous strong directionality code prev seg's previous terminator dir code restart backup skip offsets for each pass This format must match what is generated by InitializeForNextSeg. ----------------------------------------------------------------------------------------------*/ void EngineState::InitializeStreams(GrTableManager * ptman, GrCharStream * pchstrm, int cbPrev, byte * pbPrevSegDat, bool fNeedFinalBreak, int * pislotFinalBreak) { int cpassLB = ptman->NumberOfLbPasses(); int ipassPos1 = ptman->FirstPosPass(); CreateSlotStreams(); int cslotBackup = 0; InitForNewSegment(ptman); if (cbPrev == 0) { m_lbPrevEnd = klbNoBreak; m_dircInitialStrongDir = kdircNeutral; m_dircInitialTermDir = kdircNeutral; for (int ipass = 0; ipass < m_cpass; ++ipass) { OutputStream(ipass)->Initialize(ipassPos1, false); m_prgzpst[ipass].SetResyncSkip(0); if (ptman->LoggingTransduction()) m_prgzpst[ipass].InitializeLogInfo(); } } else { Assert(cbPrev >= 4); byte * pb = pbPrevSegDat; m_lbPrevEnd = LineBrk(*pb++); // Directionality codes from the previous segment. m_dircInitialStrongDir = DirCode((int)*pb++); m_dircInitialTermDir = DirCode((int)*pb++); cslotBackup = (int)*pb++; Assert((cbPrev == 4 || cslotBackup == 0) || cbPrev == m_cpass + 4); for (int ipass = 0; ipass < m_cpass; ++ipass) { OutputStream(ipass)->Initialize(ipassPos1, true); if (cbPrev == 4) m_prgzpst[ipass].SetResyncSkip(0); // different writing system else m_prgzpst[ipass].SetResyncSkip(*pb++); if (ptman->LoggingTransduction()) m_prgzpst[ipass].InitializeLogInfo(); } pchstrm->Backup(cslotBackup); } if (cbPrev > 0 || pchstrm->StartLine()) { // Go ahead and run the initial line-break passes (if any), and then insert // a line-break at the appropriate place where the new segment will begin. int cslotToGet = cslotBackup; LGetMore: ptman->Pass(0)->ExtendGlyphIDOutput(ptman, pchstrm, OutputStream(0), -1, 0, klbWordBreak, cslotToGet, fNeedFinalBreak, m_twsh, pislotFinalBreak); OutputStream(0)->SetSegMin(cslotBackup); for (int ipass = 1; ipass <= cpassLB; ipass++) { if (cslotToGet > 0) { int nRet = kNextPass; int cslotGot; ptman->Pass(ipass)->ExtendOutput(ptman, InputStream(ipass), OutputStream(ipass), cslotToGet, m_twsh, &nRet, &cslotGot, pislotFinalBreak); if (nRet != kNextPass) { cslotToGet = 1; goto LGetMore; } } OutputStream(ipass)->SetSegMin(cslotBackup); } Assert(OutputStream(cpassLB)->WritePos() >= cslotBackup); m_cslotPreSeg = cslotBackup; if (pchstrm->StartLine()) { OutputStream(cpassLB)->AppendLineBreak(ptman, pchstrm, m_lbPrevEnd, (ptman->RightToLeft() ? kdircRlb : kdircLlb), cslotBackup, true, -1); SetInitialLB(); m_cslotPreSeg++; if (cpassLB > 0 && *pislotFinalBreak > -1) // See corresponding kludge in GrPass::ExtendGlyphIDOutput. *pislotFinalBreak += 1; } else { OutputStream(cpassLB)->SetSegMin(cslotBackup); } OutputStream(cpassLB)->CalcIndexOffset(ptman); } else { Assert(cslotBackup == 0); OutputStream(0)->SetSegMin(0); m_cslotPreSeg = 0; } } /*---------------------------------------------------------------------------------------------- Reinitialize as we start to generate a new segment. ----------------------------------------------------------------------------------------------*/ void EngineState::InitForNewSegment(GrTableManager * ptman) { // Set up a fresh batch of slots. DestroySlotBlocks(); m_islotblkCurr = -1; m_islotNext = kSlotBlockSize; m_fInitialLB = false; m_fFinalLB = false; m_fInsertedLB = false; m_fExceededSpace = false; m_fHitHardBreak = false; m_fRemovedWhtsp = false; InitPosCache(); //////m_pgg = pgg; for (int ipass = 0; ipass < m_cpass; ipass++) m_prgzpst[ipass].InitForNewSegment(ipass, ptman->Pass(ipass)->MaxRuleContext()); } /*---------------------------------------------------------------------------------------------- Calculate the position in stream zero of the given slot in the final stream, based on the associations. (This will be the same as the position at the end of the line-break passes, which is really what we want.) @param islotFinal - index of the final slot in the final stream ----------------------------------------------------------------------------------------------*/ int EngineState::TraceStreamZeroPos(int islotFinal, int nTopDir) { GrSlotStream * psstrmFinal = OutputStream(m_cpass - 1); if (psstrmFinal->WritePos() == 0) return -1; GrSlotState * pslot = psstrmFinal->SlotAt(islotFinal); // If we're in the middle of reordered bidi stuff, just use the end of the stream. if (pslot->DirLevel() > nTopDir) return -1; int islot = pslot->BeforeAssoc(); if (islot == kPosInfinity || islot < 0) return -1; return islot + m_cslotPreSeg; } /*---------------------------------------------------------------------------------------------- Return the next higher (more relaxed, less desirable) line break weight. ----------------------------------------------------------------------------------------------*/ LineBrk GrTableManager::IncLineBreak(LineBrk lb) { Assert(lb != klbClipBreak); return (LineBrk)((int)lb + 1); } /*---------------------------------------------------------------------------------------------- Return a pointer to a slot that is near the current position in the given stream. Return NULL if no slots have been output at all. This is used for initializing features of line-break slots. @param ipassArg - pass at which to start looking @param islot - slot to look for; -1 if considering the current position ----------------------------------------------------------------------------------------------*/ GrSlotState * EngineState::AnAdjacentSlot(int ipassArg, int islot) { int ipass = ipassArg; while (ipass >= 0) { GrSlotStream * psstrm = OutputStream(ipass); if (psstrm->NumberOfSlots() > 0) { if (islot == -1) { if (psstrm->AtEnd()) return psstrm->SlotAt(psstrm->WritePos() - 1); else return psstrm->Peek(); } else { if (psstrm->WritePos() <= islot) return psstrm->SlotAt(psstrm->WritePos() - 1); else return psstrm->SlotAt(islot); } } ipass--; } return NULL; } /*---------------------------------------------------------------------------------------------- Simple access methods. ----------------------------------------------------------------------------------------------*/ GrEngine * GrTableManager::Engine() { return m_pgreng; } EngineState * GrTableManager::State() { return &m_engst; } GrEngine * EngineState::Engine() { return m_ptman->Engine(); } GrTableManager * EngineState::TableManager() { return m_ptman; } /*---------------------------------------------------------------------------------------------- Memory management for slots ----------------------------------------------------------------------------------------------*/ // standard for pass 0 slots void EngineState::NewSlot( gid16 gID, GrFeatureValues fval, int ipass, int ichwSegOffset, int nUnicode, GrSlotState ** ppslotRet) { NextSlot(ppslotRet); (*ppslotRet)->Initialize(gID, Engine(), fval, ipass, ichwSegOffset, nUnicode); } // line-break slots void EngineState::NewSlot( gid16 gID, GrSlotState * pslotFeat, int ipass, int ichwSegOffset, GrSlotState ** ppslotRet) { NextSlot(ppslotRet); (*ppslotRet)->Initialize(gID, Engine(), pslotFeat, ipass, ichwSegOffset); } // for inserting new slots after pass 0 (under-pos and unicode are irrelevant) void EngineState::NewSlot( gid16 gID, GrSlotState * pslotFeat, int ipass, GrSlotState ** ppslotRet) { NextSlot(ppslotRet); (*ppslotRet)->Initialize(gID, Engine(), pslotFeat, ipass); } // for making a new version of the slot initialized from the old void EngineState::NewSlotCopy(GrSlotState * pslotCopyFrom, int ipass, GrSlotState ** ppslotRet) { NextSlot(ppslotRet); (*ppslotRet)->InitializeFrom(pslotCopyFrom, ipass); } void EngineState::NextSlot(GrSlotState ** ppslotRet) { int cnExtraPerSlot = m_cUserDefn + (m_cCompPerLig * 2) + m_cFeat; if (m_islotNext >= kSlotBlockSize) { // Allocate a new block of slots GrSlotState * slotblkNew = new GrSlotState[kSlotBlockSize]; u_intslot * prgnSlotBuf = new u_intslot[kSlotBlockSize * cnExtraPerSlot]; m_vslotblk.push_back(slotblkNew); m_vprgnSlotVarLenBufs.push_back(prgnSlotBuf); m_islotblkCurr++; m_islotNext = 0; Assert((unsigned)m_islotblkCurr == m_vslotblk.size()-1); } *ppslotRet = m_vslotblk[m_islotblkCurr] + m_islotNext; (*ppslotRet)->BasicInitialize(m_cUserDefn, m_cCompPerLig, m_cFeat, (m_vprgnSlotVarLenBufs[m_islotblkCurr] + (m_islotNext * cnExtraPerSlot))); m_islotNext++; } /*---------------------------------------------------------------------------------------------- Convert the number of font design units into source device logical units. ----------------------------------------------------------------------------------------------*/ float GrTableManager::EmToLogUnits(int m) { return m_engst.EmToLogUnits(m); } float EngineState::EmToLogUnits(int m) { if (m == 0) return 0; float xysFontEmSquare; m_pfont->getFontMetrics(NULL, NULL, &xysFontEmSquare); // mEmUnits should be equal to the design units in the font's em square int mEmUnits = Engine()->GetFontEmUnits(); if (mEmUnits <= 0) { Warn("Failed to obtain font em-units"); return (float)m; } return GrEngine::GrIFIMulDiv(m, xysFontEmSquare, mEmUnits); } /*---------------------------------------------------------------------------------------------- Convert the source device coordinates to font design units. ----------------------------------------------------------------------------------------------*/ int GrTableManager::LogToEmUnits(float xys) { return m_engst.LogToEmUnits(xys); } int EngineState::LogToEmUnits(float xys) { if (xys == 0) return 0; float xysFontEmSquare; m_pfont->getFontMetrics(NULL, NULL, &xysFontEmSquare); // mEmUnits should be equal to the design units in the font's em square int mEmUnits = Engine()->GetFontEmUnits(); if (mEmUnits < 0) { Warn("Failed to obtain font em-units"); return (int)xys; } return GrEngine::GrFIFMulDiv(xys, mEmUnits, xysFontEmSquare); } /*---------------------------------------------------------------------------------------------- Find the coordinates for a given glyph point. Return true on success, false otherwise. ----------------------------------------------------------------------------------------------*/ bool GrTableManager::GPointToXY(gid16 chwGlyphID, int nGPoint, float * pxs, float * pys) { return m_engst.GPointToXY(chwGlyphID, nGPoint, pxs, pys); } bool EngineState::GPointToXY(gid16 chwGlyphID, int nGPoint, float * pxs, float * pys) { *pxs = INT_MIN; *pys = INT_MIN; Point pointRet; m_pfont->getGlyphPoint(chwGlyphID, nGPoint, pointRet); *pxs = pointRet.x; *pys = pointRet.y; return true; } /*---------------------------------------------------------------------------------------------- Call the justification routines. ----------------------------------------------------------------------------------------------*/ void GrTableManager::CallJustifier(IGrJustifier * pgjus, int ipassCurr, float dxUnjustified, float dxJustified, bool fEndLine) { // Indicates the stream the justification routines will read and modify: m_engst.m_ipassJustCalled = ipassCurr; int iGlyphMin = OutputStream(ipassCurr)->SegMin(); int iGlyphLim = OutputStream(ipassCurr)->SegLimIfKnown(); if (iGlyphLim == -1) iGlyphLim = OutputStream(ipassCurr)->WritePos(); GrSlotStream * psstrm = OutputStream(ipassCurr); if (m_pgreng->BasicJustification() && fEndLine) UnstretchTrailingWs(psstrm, iGlyphLim); pgjus->adjustGlyphWidths(m_pgreng, iGlyphMin, iGlyphLim, dxUnjustified, dxJustified); // After returning, reading and modifying slot attributes is not permitted: m_engst.m_ipassJustCalled = -1; } /*---------------------------------------------------------------------------------------------- For the basic justification approach, remove stretch from the trailing whitespace. ----------------------------------------------------------------------------------------------*/ void GrTableManager::UnstretchTrailingWs(GrSlotStream * psstrm, int iGlyphLim) { int islot = iGlyphLim - 1; while (islot >= 0) { GrSlotState * pslot = psstrm->SlotAt(islot); if (pslot->IsLineBreak(LBGlyphID())) { } // keep looking for the trailing space else if (pslot->IsSpace(this)) pslot->SetJStretch(0); // TODO: do this at level 0 else // something other than space--quit return; islot--; } } /*---------------------------------------------------------------------------------------------- Determine how much shrink is permissible. This method is really used only for testing the shrink mechanism, since at this time we don't allow low-end justification to do any shrinking. ----------------------------------------------------------------------------------------------*/ void GrTableManager::DetermineShrink(IGrJustifier * pgjus, int ipassCurr) { if (m_engst.m_dxsShrinkPossible != GrSlotState::kNotYetSet) return; if (m_engst.m_jmodi != kjmodiCanShrink || pgjus == NULL) { m_engst.m_dxsShrinkPossible = 0; return; } if (ipassCurr != m_ipassJust1 - 1) return; GrSlotStream * psstrm = OutputStream(ipassCurr); if (!psstrm->FullyWritten()) return; // Normally just answer 0. To test shrinking, turn on the code below. m_engst.m_dxsShrinkPossible = 0; #if TEST_SHRINK int mTotal = 0; int mShrink = 0; int islotLim = psstrm->SegLimIfKnown(); if (islotLim == -1) islotLim = psstrm->WritePos(); for ( ; islotLim >= 0; islotLim--) { // Trailing white space is not shrinkable. GrSlotState * pslot = psstrm->SlotAt(islotLim - 1); if (pslot->IsLineBreak(LBGlyphID())) continue; if (!pslot->IsSpace(this)) break; } int islot; for (islot = psstrm->SegMin(); islot < islotLim; islot++) { GrSlotState * pslot = psstrm->SlotAt(islot); if (pslot->IsLineBreak(LBGlyphID())) continue; mTotal += pslot->AdvanceX(this); mShrink += pslot->JShrink(); } // Say that we are allowed to shrink up to 10% of the width of the segment. mShrink = min(mShrink, mTotal/10); m_engst.m_dxsShrinkPossible = EmToLogUnits(mShrink); #endif // TEST_SHRINK } /*---------------------------------------------------------------------------------------------- Return the attribute of the given slot, back to the justification routine. Return logical units. ----------------------------------------------------------------------------------------------*/ GrResult EngineState::GetGlyphAttrForJustification(int iGlyph, int jgat, int nLevel, float * pValueRet) { GrResult res; int valueRetInt; switch(jgat) { case kjgatWeight: case kjgatStretchInSteps: case kjgatBreak: res = GetGlyphAttrForJustification(iGlyph, jgat, nLevel, &valueRetInt); *pValueRet = (float)valueRetInt; return res; default: break; } if (m_ipassJustCalled == -1) return kresUnexpected; if (nLevel != 1) return kresInvalidArg; GrSlotStream * psstrm = OutputStream(m_ipassJustCalled); if (iGlyph < -1 || iGlyph >= psstrm->WritePos()) return kresInvalidArg; GrSlotState * pslot = psstrm->SlotAt(iGlyph); // bool fConvertUnits = true; switch(jgat) { case kjgatStretch: *pValueRet = EmToLogUnits(pslot->JStretch()); break; case kjgatShrink: *pValueRet = EmToLogUnits(pslot->JShrink()); break; case kjgatStep: *pValueRet = EmToLogUnits(pslot->JStep()); break; default: return kresNotImpl; } return kresOk; } GrResult EngineState::GetGlyphAttrForJustification(int iGlyph, int jgat, int nLevel, int * pValueRet) { GrResult res; float valueRetFloat; switch(jgat) { case kjgatStretch: case kjgatShrink: case kjgatStep: res = GetGlyphAttrForJustification(iGlyph, jgat, nLevel, &valueRetFloat); *pValueRet = GrEngine::RoundFloat(valueRetFloat); return res; default: break; } if (m_ipassJustCalled == -1) return kresUnexpected; if (nLevel != 1) return kresInvalidArg; GrSlotStream * psstrm = OutputStream(m_ipassJustCalled); if (iGlyph < -1 || iGlyph >= psstrm->WritePos()) return kresInvalidArg; GrSlotState * pslot = psstrm->SlotAt(iGlyph); int mStretch, mStep; //float xsStretch, xsStep; switch(jgat) { case kjgatWeight: *pValueRet = pslot->JWeight(); break; case kjgatStretchInSteps: // ???? should this be calculated in terms of logical units?? // Probably doesn't matter, since the logical units are floating point, so the // level of precision is about the same. mStretch = pslot->JStretch(); mStep = pslot->JStep(); if (mStep == 0) return kresUnexpected; *pValueRet = int(mStretch / mStep); // round down break; //case kjgatStretchInSteps: // xsStretch = EmToLogUnits(pslot->JStretch()); // xsStep = EmToLogUnits(pslot->JStep()); // if (xsStep == 0) // return kresUnexpected; // *pValueRet = int(xsStretch / xsStep); // round down // break; case kjgatBreak: *pValueRet = pslot->BreakWeight(); break; default: return kresNotImpl; } return kresOk; } /*---------------------------------------------------------------------------------------------- Set the attribute of the given slot, for the justification routine. ----------------------------------------------------------------------------------------------*/ GrResult EngineState::SetGlyphAttrForJustification(int iGlyph, int jgat, int nLevel, float value) { GrResult res; int valueInt; switch(jgat) { case kjgatWeight: case kjgatStretchInSteps: case kjgatBreak: valueInt = (int)value; res = SetGlyphAttrForJustification(iGlyph, jgat, nLevel, valueInt); return res; default: break; } if (m_ipassJustCalled == -1) return kresUnexpected; if (nLevel != 1) return kresInvalidArg; GrSlotStream * psstrm = OutputStream(m_ipassJustCalled); if (iGlyph < -1 || iGlyph >= psstrm->WritePos()) return kresInvalidArg; GrSlotState * pslot = psstrm->SlotAt(iGlyph); int mValue = LogToEmUnits(value); mValue = min(mValue, 0xFFFF); // truncate to an unsigned short // Review: do we really want to allow them to set stretch/shrink/step/weight? The normal // thing for them to do is set width. switch(jgat) { case kjgatStretch: pslot->SetJStretch(mValue); break; case kjgatShrink: pslot->SetJShrink(mValue); break; case kjgatStep: pslot->SetJStep(mValue); break; case kjgatWidth: pslot->SetJWidth(mValue); break; default: return kresNotImpl; } return kresOk; } GrResult EngineState::SetGlyphAttrForJustification(int iGlyph, int jgat, int nLevel, int value) { GrResult res; float valueFloat; switch(jgat) { case kjgatStretch: case kjgatShrink: case kjgatStep: case kjgatWidth: valueFloat = (float)value; res = SetGlyphAttrForJustification(iGlyph, jgat, nLevel, valueFloat); return res; default: break; } if (m_ipassJustCalled == -1) return kresUnexpected; if (nLevel != 1) return kresInvalidArg; GrSlotStream * psstrm = OutputStream(m_ipassJustCalled); if (iGlyph < -1 || iGlyph >= psstrm->WritePos()) return kresInvalidArg; GrSlotState * pslot = psstrm->SlotAt(iGlyph); int mValue, cSteps; switch(jgat) { case kjgatWeight: pslot->SetJWeight(value); break; case kjgatWidthInSteps: cSteps = value; mValue = pslot->JStep(); if (mValue == 0) return kresUnexpected; pslot->SetJWidth(mValue * cSteps); break; default: return kresNotImpl; } return kresOk; } //:>-------------------------------------------------------------------------------------------- //:> Functions to forward to the engine. //:>------------------------------------------------------------------------------------------*/ gid16 GrTableManager::GetGlyphIDFromUnicode(int nUnicode) { return m_pgreng->GetGlyphIDFromUnicode(nUnicode); } /*--------------------------------------------------------------------------------------------*/ gid16 GrTableManager::ActualGlyphForOutput(utf16 chw) { return m_pgreng->ActualGlyphForOutput(chw); } /*--------------------------------------------------------------------------------------------*/ GrGlyphTable * GrTableManager::GlyphTable() { return m_pgreng->GlyphTable(); } /*--------------------------------------------------------------------------------------------*/ gid16 GrTableManager::LBGlyphID() { return m_pgreng->LBGlyphID(); } /*--------------------------------------------------------------------------------------------*/ gid16 GrTableManager::GetClassGlyphIDAt(int nClass, int nIndex) { return m_pgreng->GetClassGlyphIDAt(nClass, nIndex); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::GetIndexInGlyphClass(int nClass, gid16 chwGlyphID) { return m_pgreng->GetIndexInGlyphClass(nClass, chwGlyphID); } /*--------------------------------------------------------------------------------------------*/ size_t GrTableManager::NumberOfGlyphsInClass(int nClass) { return m_pgreng->NumberOfGlyphsInClass(nClass); } /*--------------------------------------------------------------------------------------------*/ void GrTableManager::SetSlotAttrsFromGlyphAttrs(GrSlotState * pslot) { m_pgreng->SetSlotAttrsFromGlyphAttrs(pslot); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::NumFeat() { return m_pgreng->NumFeat(); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::DefaultForFeatureAt(int ifeat) { return m_pgreng->DefaultForFeatureAt(ifeat); } /*--------------------------------------------------------------------------------------------*/ void GrTableManager::DefaultsForLanguage(isocode lgcode, std::vector & vnFeats, std::vector & vnValues) { return m_pgreng->DefaultsForLanguage(lgcode, vnFeats, vnValues); } /*--------------------------------------------------------------------------------------------*/ GrFeature * GrTableManager::FeatureWithID(featid nID, int * pifeat) { return m_pgreng->FeatureWithID(nID, pifeat); } /*--------------------------------------------------------------------------------------------*/ GrFeature * GrTableManager::Feature(int ifeat) { return m_pgreng->Feature(ifeat); } /*--------------------------------------------------------------------------------------------*/ bool GrTableManager::RightToLeft() { if (m_engst.WhiteSpaceOnly()) return m_engst.m_fParaRtl; else return m_pgreng->RightToLeft(); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::TopDirectionLevel() { return m_pgreng->TopDirectionLevel(); } /*--------------------------------------------------------------------------------------------*/ float GrTableManager::VerticalOffset() { return m_pgreng->VerticalOffset(); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::NumUserDefn() { return m_pgreng->NumUserDefn(); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::NumCompPerLig() { return m_pgreng->NumCompPerLig(); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::ComponentIndexForGlyph(gid16 chwGlyphID, int nCompID) { return m_pgreng->ComponentIndexForGlyph(chwGlyphID, nCompID); } /*--------------------------------------------------------------------------------------------*/ int GrTableManager::GlyphAttrValue(gid16 chwGlyphID, int nAttrID) { return m_pgreng->GlyphAttrValue(chwGlyphID, nAttrID); } /*--------------------------------------------------------------------------------------------*/ bool GrTableManager::LoggingTransduction() { return m_fLogging; //return m_pgreng->LoggingTransduction(); } /*---------------------------------------------------------------------------------------------- Calculate the positions of the glyphs up to the given slot, within the output of the given pass. If the slot is NULL, go all the way to the end of what has been generated. @param ipass - index of pass needing positioning; normally this will be the final pass, but it could be another if positions are requested by the rules themselves @param pslotLast - last slot that needs to be positioned, or NULL @param pxsWidth - return the total width used so far @param psxVisibleWidth - return the visible width so far MOVE to EngineState ----------------------------------------------------------------------------------------------*/ void GrTableManager::CalcPositionsUpTo(int ipass, GrSlotState * pslotLast, float * pxsWidth, float * pxsVisibleWidth) { Assert(ipass >= m_ipassPos1 - 1); int isstrm = ipass; GrSlotStream * psstrm = OutputStream(isstrm); Assert(psstrm->GotIndexOffset()); if (psstrm->WritePos() <= psstrm->IndexOffset()) { Assert(psstrm->WritePos() == 0); *pxsWidth = 0; *pxsVisibleWidth = 0; return; } if (!pslotLast) { pslotLast = psstrm->SlotAt(psstrm->WritePos() - 1); } Assert(pslotLast); // First set positions of all base slots. int islot = psstrm->IndexOffset(); GrSlotState * pslot; float xs = 0; float ys = VerticalOffset(); *pxsWidth = 0; *pxsVisibleWidth = 0; bool fLast = false; bool fLastBase = false; bool fFakeItalic = m_pgreng->FakeItalic(); float fakeItalicRatio = 0; if (fFakeItalic) fakeItalicRatio = State()->GetFont()->fakeItalicRatio(); bool fBasicJust = m_pgreng->BasicJustification(); // Figure out how to know when to stop. // We need to calculate up to the base of the last leaf slot, if it happens // to be later in the stream than the last actual slot passed in. if (!psstrm->HasSlotAtPosPassIndex(pslotLast->AttachRootPosPassIndex())) return; GrSlotState * pslotLastBase = pslotLast->Base(psstrm); if (ipass == m_cpass - 1 && m_engst.m_islotPosNext > -1) { // For final pass, initialize from cache of previous calculations. islot = m_engst.m_islotPosNext; xs = m_engst.m_xsPosXNext; ys = m_engst.m_ysPosYNext; *pxsWidth = m_engst.m_dxsTotWidthSoFar; *pxsVisibleWidth = m_engst.m_dxsVisWidthSoFar; if (psstrm->SlotsPresent() <= islot) return; if (pslotLastBase->PosPassIndex() == GrSlotAbstract::kNotYetSet) { Assert(false); // I think this case is handled above return; // can't position this slot yet; its base has not been processed } if (pslotLastBase->PosPassIndex() + psstrm->IndexOffset() < islot) { fLastBase = true; if (pslotLast == pslotLastBase) fLast = true; } } std::vector vpslotAttached; bool fRtl = RightToLeft(); while (!fLast || !fLastBase) { Assert(islot < psstrm->SlotsPresent()); pslot = (isstrm == ipass) ? psstrm->SlotAt(islot) : psstrm->OutputSlotAt(islot); if (!pslot->IsBase()) { // This slot is attached to another; it will be positioned strictly // relative to that one. This happens in the loop below. vpslotAttached.push_back(pslot); } else { // Base character. bool fLB = pslot->IsLineBreak(LBGlyphID()); if (InternalJustificationMode() == kjmodiJustify && fBasicJust && ipass == m_cpass - 1 && !fLB) { m_engst.AddJWidthToAdvance(psstrm, &pslot, islot, &pslotLast, &pslotLastBase); } // Make sure the metrics are the complete ones. pslot->CalcCompositeMetrics(this, psstrm, kPosInfinity, true); float xsInc = pslot->GlyphXOffset(psstrm, fakeItalicRatio); float ysInc = pslot->GlyphYOffset(psstrm); float xsAdvX = pslot->ClusterAdvWidth(psstrm); float ysAdvY = (fLB) ? 0 : EmToLogUnits(pslot->AdvanceY(this)); if (fRtl) { xs -= xsAdvX; ys -= ysAdvY; pslot->SetXPos(xs + xsInc); pslot->SetYPos(ys + ysInc); } else { pslot->SetXPos(xs + xsInc); pslot->SetYPos(ys + ysInc); xs += xsAdvX; ys += ysAdvY; } *pxsWidth = max(*pxsWidth, fabsf(xs)); if (!IsWhiteSpace(pslot)) *pxsVisibleWidth = *pxsWidth; if (isstrm == m_cpass - 1) { // For the final output pass, cache the results of the calculation so far. // Only do this for slots that have actually been processed by the final // pass, because others may have intermediate values that may be changed // later. m_engst.m_islotPosNext = pslot->PosPassIndex() + psstrm->IndexOffset() + 1; m_engst.m_xsPosXNext = xs; m_engst.m_ysPosYNext = ys; m_engst.m_dxsTotWidthSoFar = *pxsWidth; m_engst.m_dxsVisWidthSoFar = *pxsVisibleWidth; } } // Need to have calculated both the last leaf and the last base (if they happen to // be different); if so, we're done. if (pslot == pslotLast) fLast = true; if (pslot == pslotLastBase) fLastBase = true; islot++; } Assert(fLast); Assert(fLastBase); // Now set positions of non-base slots, relative to their bases. for (size_t ipslot = 0; ipslot < vpslotAttached.size(); ipslot++) { GrSlotState * pslot = vpslotAttached[ipslot]; GrSlotState * pslotBase = pslot->Base(psstrm); if (pslotBase->XPosition() == kNegInfinity || pslotBase->YPosition() == kNegInfinity) { Assert(false); continue; } float xsCluster = pslotBase->XPosition() - pslotBase->GlyphXOffset(psstrm, fakeItalicRatio); float ysCluster = pslotBase->YPosition() - pslotBase->GlyphYOffset(psstrm); float xsInc = pslot->GlyphXOffset(psstrm, fakeItalicRatio); float ysInc = pslot->GlyphYOffset(psstrm); pslot->SetXPos(xsCluster + xsInc); pslot->SetYPos(ysCluster + ysInc); // My theory is that we don't need to adjust *pxsWidth here, because the width of // any non-base slots should be factored into the advance width of their cluster // base, which was handled above. } } /*---------------------------------------------------------------------------------------------- Return true if the given slot is white space. ----------------------------------------------------------------------------------------------*/ bool GrTableManager::IsWhiteSpace(GrSlotState * pslot) { if (pslot->IsLineBreak(LBGlyphID())) return true; return pslot->IsSpace(this); } /*---------------------------------------------------------------------------------------------- When doing basic justification, apply the value of justify.width to the advance width. This happens during the final positioning routine. ----------------------------------------------------------------------------------------------*/ void EngineState::AddJWidthToAdvance(GrSlotStream * psstrm, GrSlotState ** ppslot, int islot, GrSlotState ** ppslotLast, GrSlotState ** ppslotLastBase) { Assert(psstrm->OutputOfPass() == m_cpass - 1); // only used for final stream if ((*ppslot)->JWidth() > 0) { if ((*ppslot)->PassModified() != m_cpass - 1) { // Replace the slot with a new slot, so it gets marked properly with the // pass in which it was modified. (Otherwise the debug log gets screwed up.) GrSlotState * pslotNew; NewSlotCopy((*ppslot), m_cpass - 1, &pslotNew); psstrm->PutSlotAt(pslotNew, islot); if (*ppslot == *ppslotLast) *ppslotLast = pslotNew; if (*ppslot == *ppslotLastBase) *ppslotLastBase = pslotNew; *ppslot = pslotNew; } (*ppslot)->AddJWidthToAdvance(TableManager()); } } //:>******************************************************************************************** //:> Debuggers and utilities //:>******************************************************************************************** //:Ignore #if 0 void GrTableManager::TempDebug() { for (int ipass = 0; ipass < m_cpass; ++ipass) { int wpos = OutputStream(ipass)->WritePos(); int rpos = OutputStream(ipass)->ReadPos(); GrSlotState * wslot = OutputStream(ipass)->LastSlotWritten(); GrSlotState * rslot = OutputStream(ipass)->LastSlotRead(); } } #endif /*---------------------------------------------------------------------------------------------- Given a character index in the original string, locate the corresponding line-break slot in the final surface stream. The character index must be one before which there is a line-break (seg lim), or the beginning or the end of the string. TODO SharonC: fix the bugs ----------------------------------------------------------------------------------------------*/ int GrTableManager::SurfaceLineBreakSlot(int ichw, GrCharStream * pchstrm, bool fInitial) { if (ichw == 0) { Assert(fInitial); return -1; // no initial line break } if (ichw == pchstrm->Lim()) { Assert(!fInitial); return -1; // no terminating line break } int islotIn = pchstrm->SegOffset(ichw); Assert(islotIn >= 0); islotIn += (fInitial? m_engst.m_cslotPreSeg - 1: m_engst.m_cslotPreSeg); gid16 chwLBGlyphID = LBGlyphID(); // Now islotIn is the position in stream 0; for (int ipass = 1; ipass < m_cpass; ++ipass) { int islotChunkMin; int islotChunkLim; GrSlotStream * psstrmOut = OutputStream(ipass); GrSlotStream * psstrmIn = InputStream(ipass); if (fInitial) islotIn = max(islotIn, psstrmIn->PreChunk()); int islotT = psstrmIn->ChunkInNextMin(islotIn); islotChunkMin = psstrmIn->ChunkInNext(islotT); islotChunkMin = (islotChunkMin == -1)? 0: islotChunkMin; islotT = psstrmIn->ChunkInNextLim(islotIn); Assert(islotT <= psstrmIn->ReadPos()); if (islotT == psstrmIn->ReadPos()) islotChunkLim = psstrmOut->WritePos(); else islotChunkLim = psstrmIn->ChunkInNext(islotT); int islotOut; for (islotOut = islotChunkMin; islotOut < islotChunkLim; ++islotOut) { gid16 chw = psstrmOut->GlyphIDAt(islotOut); if (chw == chwLBGlyphID) break; } Assert(psstrmOut->GlyphIDAt(islotOut) == chwLBGlyphID); islotIn = islotOut; } return islotIn; } /*---------------------------------------------------------------------------------------------- Generate a debugger string showing the chunk boundaries for the given stream. ----------------------------------------------------------------------------------------------*/ std::wstring GrTableManager::ChunkDebugString(int ipass) { GrSlotStream * psstrm = OutputStream(ipass); std::wstring stuRet; //utf16 chwN = '\\'; //utf16 chwR = '^'; gid16 chwLBGlyphID = LBGlyphID(); for (int islot = 0; islot < psstrm->WritePos(); islot++) { if (psstrm->ChunkInPrev(islot) != -1) { if (psstrm->ChunkInNext(islot) != -1) stuRet.append(L">"); else stuRet.append(L"\\"); } else if (psstrm->ChunkInNext(islot) != -1) stuRet.append(L"/"); else stuRet.append(L" "); // if (islot == psstrm->ReadPos()) // stuRet.Append(&chwR, 1); wchar_t chw = psstrm->SlotAt(islot)->GlyphID(); if (chw == chwLBGlyphID) stuRet.append(L"#"); else stuRet.append(&chw, 1); } return stuRet; } } // namespace gr //:End Ignore