static ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i) { ME_DisplayItem *pp, *piter = p; int j; if (!i) return NULL; j = reverse_find_non_whitespace( get_text( &p->member.run, 0 ), i); if (j>0) { pp = split_run_extents(wc, piter, j); wc->pt.x += piter->member.run.nWidth; return pp; } else { pp = piter; /* omit all spaces before split point */ while(piter != wc->pRowStart) { piter = ME_FindItemBack(piter, diRun); if (piter->member.run.nFlags & MERF_WHITESPACE) { pp = piter; continue; } if (piter->member.run.nFlags & MERF_ENDWHITE) { i = reverse_find_non_whitespace( get_text( &piter->member.run, 0 ), piter->member.run.len ); pp = split_run_extents(wc, piter, i); wc->pt = pp->member.run.pt; return pp; } /* this run is the end of spaces, so the run edge is a good point to split */ wc->pt = pp->member.run.pt; wc->bOverflown = TRUE; TRACE("Split point is: %s|%s\n", debugstr_run( &piter->member.run ), debugstr_run( &pp->member.run )); return pp; } wc->pt = piter->member.run.pt; return piter; } }
/****************************************************************************** * split_run_extents * * Splits a run into two in a given place. It also updates the screen position * and size (extent) of the newly generated runs. */ static ME_DisplayItem *split_run_extents(ME_WrapContext *wc, ME_DisplayItem *item, int nVChar) { ME_TextEditor *editor = wc->context->editor; ME_Run *run, *run2; ME_Paragraph *para = &wc->pPara->member.para; ME_Cursor cursor = {wc->pPara, item, nVChar}; assert(item->member.run.nCharOfs != -1); if(TRACE_ON(richedit)) { TRACE("Before check before split\n"); ME_CheckCharOffsets(editor); TRACE("After check before split\n"); } run = &item->member.run; TRACE("Before split: %s(%d, %d)\n", debugstr_run( run ), run->pt.x, run->pt.y); ME_SplitRunSimple(editor, &cursor); run2 = &cursor.pRun->member.run; calc_run_extent(wc->context, para, wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, run); run2->pt.x = run->pt.x+run->nWidth; run2->pt.y = run->pt.y; if(TRACE_ON(richedit)) { TRACE("Before check after split\n"); ME_CheckCharOffsets(editor); TRACE("After check after split\n"); TRACE("After split: %s(%d, %d), %s(%d, %d)\n", debugstr_run( run ), run->pt.x, run->pt.y, debugstr_run( run2 ), run2->pt.x, run2->pt.y); } return cursor.pRun; }
/****************************************************************************** * ME_CheckCharOffsets * * Checks if editor lists' validity and optionally dumps the document structure */ void ME_CheckCharOffsets(ME_TextEditor *editor) { ME_DisplayItem *p = editor->pBuffer->pFirst; int ofs = 0, ofsp = 0; TRACE_(richedit_check)("Checking begin\n"); if(TRACE_ON(richedit_lists)) { TRACE_(richedit_lists)("---\n"); ME_DumpDocument(editor->pBuffer); } do { p = ME_FindItemFwd(p, diRunOrParagraphOrEnd); switch(p->type) { case diTextEnd: TRACE_(richedit_check)("tend, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs); assert(ofsp+ofs == p->member.para.nCharOfs); TRACE_(richedit_check)("Checking finished\n"); return; case diParagraph: TRACE_(richedit_check)("para, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs); assert(ofsp+ofs == p->member.para.nCharOfs); ofsp = p->member.para.nCharOfs; ofs = 0; break; case diRun: TRACE_(richedit_check)("run, real ofs = %d (+ofsp = %d), counted = %d, len = %d, txt = %s, flags=%08x, fx&mask = %08x\n", p->member.run.nCharOfs, p->member.run.nCharOfs+ofsp, ofsp+ofs, p->member.run.len, debugstr_run( &p->member.run ), p->member.run.nFlags, p->member.run.style->fmt.dwMask & p->member.run.style->fmt.dwEffects); assert(ofs == p->member.run.nCharOfs); assert(p->member.run.len); ofs += p->member.run.len; break; case diCell: TRACE_(richedit_check)("cell\n"); break; default: assert(0); } } while(1); TRACE_(richedit_check)("Checking finished\n"); }
void ME_DumpDocument(ME_TextBuffer *buffer) { /* FIXME this is useless, */ ME_DisplayItem *pItem = buffer->pFirst; TRACE("DOCUMENT DUMP START\n"); while(pItem) { switch(pItem->type) { case diTextStart: TRACE("Start\n"); break; case diCell: TRACE("Cell(level=%d%s)\n", pItem->member.cell.nNestingLevel, !pItem->member.cell.next_cell ? ", END" : (!pItem->member.cell.prev_cell ? ", START" :"")); break; case diParagraph: TRACE("Paragraph(ofs=%d)\n", pItem->member.para.nCharOfs); if (pItem->member.para.nFlags & MEPF_ROWSTART) TRACE(" - (Table Row Start)\n"); if (pItem->member.para.nFlags & MEPF_ROWEND) TRACE(" - (Table Row End)\n"); break; case diStartRow: TRACE(" - StartRow\n"); break; case diRun: TRACE(" - Run(%s, %d, flags=%x)\n", debugstr_run( &pItem->member.run ), pItem->member.run.nCharOfs, pItem->member.run.nFlags); break; case diTextEnd: TRACE("End(ofs=%d)\n", pItem->member.para.nCharOfs); break; default: break; } pItem = pItem->next; } TRACE("DOCUMENT DUMP END\n"); }
/****************************************************************************** * ME_PropagateCharOffsets * * Shifts (increases or decreases) character offset (relative to beginning of * the document) of the part of the text starting from given place. */ void ME_PropagateCharOffset(ME_DisplayItem *p, int shift) { /* Runs in one paragraph contain character offset relative to their owning * paragraph. If we start the shifting from the run, we need to shift * all the relative offsets until the end of the paragraph */ if (p->type == diRun) /* propagate in all runs in this para */ { TRACE("PropagateCharOffset(%s, %d)\n", debugstr_run( &p->member.run ), shift); do { p->member.run.nCharOfs += shift; assert(p->member.run.nCharOfs >= 0); p = ME_FindItemFwd(p, diRunOrParagraphOrEnd); } while(p->type == diRun); } /* Runs in next paragraphs don't need their offsets updated, because they, * again, those offsets are relative to their respective paragraphs. * Instead of that, we're updating paragraphs' character offsets. */ if (p->type == diParagraph) /* propagate in all next paras */ { do { p->member.para.nCharOfs += shift; assert(p->member.para.nCharOfs >= 0); p = p->member.para.next_para; } while(p->type == diParagraph); } /* diTextEnd also has character offset in it, which makes finding text length * easier. But it needs to be up to date first. */ if (p->type == diTextEnd) { p->member.para.nCharOfs += shift; assert(p->member.para.nCharOfs >= 0); } }
static HRESULT itemize_para( ME_Context *c, ME_DisplayItem *p ) { ME_Paragraph *para = &p->member.para; ME_Run *run; ME_DisplayItem *di; SCRIPT_ITEM buf[16], *items = buf; int items_passed = sizeof( buf ) / sizeof( buf[0] ), num_items, cur_item; SCRIPT_CONTROL control = { LANG_USER_DEFAULT, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0 }; SCRIPT_STATE state = { 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0 }; HRESULT hr; assert( p->type == diParagraph ); while (1) { hr = ScriptItemize( para->text->szData, para->text->nLen, items_passed, &control, &state, items, &num_items ); if (hr != E_OUTOFMEMORY) break; /* may not be enough items if hr == E_OUTOFMEMORY */ if (items_passed > para->text->nLen + 1) break; /* something else has gone wrong */ items_passed *= 2; if (items == buf) items = heap_alloc( items_passed * sizeof( *items ) ); else items = heap_realloc( items, items_passed * sizeof( *items ) ); if (!items) break; } if (FAILED( hr )) goto end; if (TRACE_ON( richedit )) { TRACE( "got items:\n" ); for (cur_item = 0; cur_item < num_items; cur_item++) { TRACE( "\t%d - %d RTL %d bidi level %d\n", items[cur_item].iCharPos, items[cur_item+1].iCharPos - 1, items[cur_item].a.fRTL, items[cur_item].a.s.uBidiLevel ); } TRACE( "before splitting runs into ranges\n" ); for (di = p->next; di != p->member.para.next_para; di = di->next) { if (di->type != diRun) continue; TRACE( "\t%d: %s\n", di->member.run.nCharOfs, debugstr_run( &di->member.run ) ); } } /* split runs into ranges at item boundaries */ for (di = p->next, cur_item = 0; di != p->member.para.next_para; di = di->next) { if (di->type != diRun) continue; run = &di->member.run; if (run->nCharOfs == items[cur_item+1].iCharPos) cur_item++; items[cur_item].a.fLogicalOrder = TRUE; run->script_analysis = items[cur_item].a; if (run->nFlags & MERF_ENDPARA) break; /* don't split eop runs */ if (run->nCharOfs + run->len > items[cur_item+1].iCharPos) { ME_Cursor cursor = {p, di, items[cur_item+1].iCharPos - run->nCharOfs}; ME_SplitRunSimple( c->editor, &cursor ); } } if (TRACE_ON( richedit )) { TRACE( "after splitting into ranges\n" ); for (di = p->next; di != p->member.para.next_para; di = di->next) { if (di->type != diRun) continue; TRACE( "\t%d: %s\n", di->member.run.nCharOfs, debugstr_run( &di->member.run ) ); } } para->nFlags |= MEPF_COMPLEX; end: if (items != buf) heap_free( items ); return hr; }
static ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, int loc) { ME_DisplayItem *piter = p, *pp; int i, idesp, len; ME_Run *run = &p->member.run; idesp = i = find_split_point( wc->context, loc, run ); len = run->len; assert(len>0); assert(i<len); if (i) { /* don't split words */ i = reverse_find_whitespace( get_text( run, 0 ), i ); pp = ME_MaximizeSplit(wc, p, i); if (pp) return pp; } TRACE("Must backtrack to split at: %s\n", debugstr_run( &p->member.run )); if (wc->pLastSplittableRun) { if (wc->pLastSplittableRun->member.run.nFlags & (MERF_GRAPHICS|MERF_TAB)) { wc->pt = wc->pLastSplittableRun->member.run.pt; return wc->pLastSplittableRun; } else if (wc->pLastSplittableRun->member.run.nFlags & MERF_SPLITTABLE) { /* the following two lines are just to check if we forgot to call UpdateRunFlags earlier, they serve no other purpose */ ME_UpdateRunFlags(wc->context->editor, run); assert((wc->pLastSplittableRun->member.run.nFlags & MERF_SPLITTABLE)); piter = wc->pLastSplittableRun; run = &piter->member.run; len = run->len; /* don't split words */ i = reverse_find_whitespace( get_text( run, 0 ), len ); if (i == len) i = reverse_find_non_whitespace( get_text( run, 0 ), len ); if (i) { ME_DisplayItem *piter2 = split_run_extents(wc, piter, i); wc->pt = piter2->member.run.pt; return piter2; } /* splittable = must have whitespaces */ assert(0 == "Splittable, but no whitespaces"); } else { /* restart from the first run beginning with spaces */ wc->pt = wc->pLastSplittableRun->member.run.pt; return wc->pLastSplittableRun; } } TRACE("Backtracking failed, trying desperate: %s\n", debugstr_run( &p->member.run )); /* OK, no better idea, so assume we MAY split words if we can split at all*/ if (idesp) return split_run_extents(wc, piter, idesp); else if (wc->pRowStart && piter != wc->pRowStart) { /* don't need to break current run, because it's possible to split before this run */ wc->bOverflown = TRUE; return piter; } else { /* split point inside first character - no choice but split after that char */ if (len != 1) { /* the run is more than 1 char, so we may split */ return split_run_extents(wc, piter, 1); } /* the run is one char, can't split it */ return piter; } }
BOOL ME_InternalDeleteText(ME_TextEditor *editor, ME_Cursor *start, int nChars, BOOL bForce) { ME_Cursor c = *start; int nOfs = ME_GetCursorOfs(start), text_len = ME_GetTextLength( editor ); int shift = 0; int totalChars = nChars; ME_DisplayItem *start_para; BOOL delete_all = FALSE; /* Prevent deletion past last end of paragraph run. */ nChars = min(nChars, text_len - nOfs); if (nChars == text_len) delete_all = TRUE; start_para = c.pPara; if (!bForce) { ME_ProtectPartialTableDeletion(editor, &c, &nChars); if (nChars == 0) return FALSE; } while(nChars > 0) { ME_Run *run; ME_CursorFromCharOfs(editor, nOfs+nChars, &c); if (!c.nOffset && nOfs+nChars == (c.pRun->member.run.nCharOfs + c.pPara->member.para.nCharOfs)) { /* We aren't deleting anything in this run, so we will go back to the * last run we are deleting text in. */ ME_PrevRun(&c.pPara, &c.pRun); c.nOffset = c.pRun->member.run.len; } run = &c.pRun->member.run; if (run->nFlags & MERF_ENDPARA) { int eollen = c.pRun->member.run.len; BOOL keepFirstParaFormat; if (!ME_FindItemFwd(c.pRun, diParagraph)) { return TRUE; } keepFirstParaFormat = (totalChars == nChars && nChars <= eollen && run->nCharOfs); if (!editor->bEmulateVersion10) /* v4.1 */ { ME_DisplayItem *next_para = ME_FindItemFwd(c.pRun, diParagraphOrEnd); ME_DisplayItem *this_para = next_para->member.para.prev_para; /* The end of paragraph before a table row is only deleted if there * is nothing else on the line before it. */ if (this_para == start_para && next_para->member.para.nFlags & MEPF_ROWSTART) { /* If the paragraph will be empty, then it should be deleted, however * it still might have text right now which would inherit the * MEPF_STARTROW property if we joined it right now. * Instead we will delete it after the preceding text is deleted. */ if (nOfs > this_para->member.para.nCharOfs) { /* Skip this end of line. */ nChars -= (eollen < nChars) ? eollen : nChars; continue; } keepFirstParaFormat = TRUE; } } ME_JoinParagraphs(editor, c.pPara, keepFirstParaFormat); /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */ ME_CheckCharOffsets(editor); nChars -= (eollen < nChars) ? eollen : nChars; continue; } else { ME_Cursor cursor; int nCharsToDelete = min(nChars, c.nOffset); int i; c.nOffset -= nCharsToDelete; ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags |= MEPF_REWRAP; cursor = c; /* nChars is the number of characters that should be deleted from the PRECEDING runs (these BEFORE cursor.pRun) nCharsToDelete is a number of chars to delete from THIS run */ nChars -= nCharsToDelete; shift -= nCharsToDelete; TRACE("Deleting %d (remaning %d) chars at %d in %s (%d)\n", nCharsToDelete, nChars, c.nOffset, debugstr_run( run ), run->len); /* nOfs is a character offset (from the start of the document to the current (deleted) run */ add_undo_insert_run( editor, nOfs + nChars, get_text( run, c.nOffset ), nCharsToDelete, run->nFlags, run->style ); ME_StrDeleteV(run->para->text, run->nCharOfs + c.nOffset, nCharsToDelete); run->len -= nCharsToDelete; TRACE("Post deletion string: %s (%d)\n", debugstr_run( run ), run->len); TRACE("Shift value: %d\n", shift); /* update cursors (including c) */ for (i=-1; i<editor->nCursors; i++) { ME_Cursor *pThisCur = editor->pCursors + i; if (i == -1) pThisCur = &c; if (pThisCur->pRun == cursor.pRun) { if (pThisCur->nOffset > cursor.nOffset) { if (pThisCur->nOffset-cursor.nOffset < nCharsToDelete) pThisCur->nOffset = cursor.nOffset; else pThisCur->nOffset -= nCharsToDelete; assert(pThisCur->nOffset >= 0); assert(pThisCur->nOffset <= run->len); } if (pThisCur->nOffset == run->len) { pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd); assert(pThisCur->pRun->type == diRun); pThisCur->nOffset = 0; } } } /* c = updated data now */ if (c.pRun == cursor.pRun) ME_SkipAndPropagateCharOffset(c.pRun, shift); else ME_PropagateCharOffset(c.pRun, shift); if (!cursor.pRun->member.run.len) { TRACE("Removing empty run\n"); ME_Remove(cursor.pRun); ME_DestroyDisplayItem(cursor.pRun); } shift = 0; /* ME_CheckCharOffsets(editor); */ continue; } } if (delete_all) ME_SetDefaultParaFormat( editor, start_para->member.para.pFmt ); return TRUE; }
/* join tp with tp->member.para.next_para, keeping tp's style; this * is consistent with the original */ ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp, BOOL keepFirstParaFormat) { ME_DisplayItem *pNext, *pFirstRunInNext, *pRun, *pTmp, *pCell = NULL; int i, shift; int end_len; CHARFORMAT2W fmt; ME_Cursor startCur, endCur; ME_String *eol_str; assert(tp->type == diParagraph); assert(tp->member.para.next_para); assert(tp->member.para.next_para->type == diParagraph); pNext = tp->member.para.next_para; /* Need to locate end-of-paragraph run here, in order to know end_len */ pRun = ME_FindItemBack(pNext, diRunOrParagraph); assert(pRun); assert(pRun->type == diRun); assert(pRun->member.run.nFlags & MERF_ENDPARA); end_len = pRun->member.run.len; eol_str = ME_VSplitString( tp->member.para.text, pRun->member.run.nCharOfs ); ME_AppendString( tp->member.para.text, pNext->member.para.text->szData, pNext->member.para.text->nLen ); /* null char format operation to store the original char format for the ENDPARA run */ ME_InitCharFormat2W(&fmt); endCur.pPara = pNext; endCur.pRun = ME_FindItemFwd(pNext, diRun); endCur.nOffset = 0; startCur = endCur; ME_PrevRun(&startCur.pPara, &startCur.pRun); ME_SetCharFormat(editor, &startCur, &endCur, &fmt); if (!editor->bEmulateVersion10) { /* v4.1 */ /* Table cell/row properties are always moved over from the removed para. */ tp->member.para.nFlags = pNext->member.para.nFlags; tp->member.para.pCell = pNext->member.para.pCell; /* Remove cell boundary if it is between the end paragraph run and the next * paragraph display item. */ for (pTmp = pRun->next; pTmp != pNext; pTmp = pTmp->next) { if (pTmp->type == diCell) { pCell = pTmp; break; } } } add_undo_split_para( editor, &pNext->member.para, eol_str, pCell ? &pCell->member.cell : NULL ); if (pCell) { ME_Remove( pCell ); if (pCell->member.cell.prev_cell) pCell->member.cell.prev_cell->member.cell.next_cell = pCell->member.cell.next_cell; if (pCell->member.cell.next_cell) pCell->member.cell.next_cell->member.cell.prev_cell = pCell->member.cell.prev_cell; ME_DestroyDisplayItem( pCell ); } if (!keepFirstParaFormat) { add_undo_set_para_fmt( editor, &tp->member.para ); *tp->member.para.pFmt = *pNext->member.para.pFmt; tp->member.para.border = pNext->member.para.border; } shift = pNext->member.para.nCharOfs - tp->member.para.nCharOfs - end_len; pFirstRunInNext = ME_FindItemFwd(pNext, diRunOrParagraph); assert(pFirstRunInNext->type == diRun); /* Update selection cursors so they don't point to the removed end * paragraph run, and point to the correct paragraph. */ for (i=0; i < editor->nCursors; i++) { if (editor->pCursors[i].pRun == pRun) { editor->pCursors[i].pRun = pFirstRunInNext; editor->pCursors[i].nOffset = 0; } else if (editor->pCursors[i].pPara == pNext) { editor->pCursors[i].pPara = tp; } } pTmp = pNext; do { pTmp = ME_FindItemFwd(pTmp, diRunOrParagraphOrEnd); if (pTmp->type != diRun) break; TRACE("shifting %s by %d (previous %d)\n", debugstr_run( &pTmp->member.run ), shift, pTmp->member.run.nCharOfs); pTmp->member.run.nCharOfs += shift; pTmp->member.run.para = &tp->member.para; } while(1); ME_Remove(pRun); ME_DestroyDisplayItem(pRun); if (editor->pLastSelStartPara == pNext) editor->pLastSelStartPara = tp; if (editor->pLastSelEndPara == pNext) editor->pLastSelEndPara = tp; tp->member.para.next_para = pNext->member.para.next_para; pNext->member.para.next_para->member.para.prev_para = tp; ME_Remove(pNext); ME_DestroyDisplayItem(pNext); ME_PropagateCharOffset(tp->member.para.next_para, -end_len); ME_CheckCharOffsets(editor); editor->nParagraphs--; tp->member.para.nFlags |= MEPF_REWRAP; return tp; }