#include "tkText.h"
#include "tkTextTagSet.h"
#include "tkRangeList.h"
#include "tkInt.h"
#ifdef _WIN32
#include "tkWinInt.h"
#elif defined(__CYGWIN__)
#include "tkUnixInt.h"
#endif
#ifdef MAC_OSX_TK
#include "tkMacOSXInt.h"
#endif
#include <stdlib.h>
#include <assert.h>
#ifndef MIN
# define MIN(a,b) (a < b ? a : b)
#endif
#ifndef MAX
# define MAX(a,b) (a < b ? b : a)
#endif
#if NDEBUG
# define DEBUG(expr)
#else
# define DEBUG(expr) expr
#endif
#define FP_EQUAL_SCALE(double1, double2, scaleFactor) \
(fabs((double1) - (double2))*((scaleFactor) + 1.0) < 0.3)
#define LOG(toVar,what) \
Tcl_SetVar2(textPtr->interp, toVar, NULL, what, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT)
#define SPEEDUP_MONOSPACED_LINE_HEIGHTS 0
typedef struct BreakInfo {
uint32_t refCount;
char *brks;
struct BreakInfo *nextPtr;
} BreakInfo;
typedef struct DLine {
TkTextIndex index;
struct DLine *nextPtr;
struct DLine *prevPtr;
TkTextDispChunk *chunkPtr;
TkTextDispChunk *firstCharChunkPtr;
TkTextDispChunk *lastChunkPtr;
TkTextDispChunk *cursorChunkPtr;
BreakInfo *breakInfo;
uint32_t displayLineNo;
uint32_t hyphenRule;
uint32_t byteCount;
int32_t y;
int32_t oldY;
int32_t height;
int32_t baseline;
int32_t spaceAbove;
int32_t spaceBelow;
uint32_t length;
uint32_t flags;
} DLine;
#define HAS_3D_BORDER (1 << 0)
#define NEW_LAYOUT (1 << 1)
#define TOP_LINE (1 << 2)
#define BOTTOM_LINE (1 << 3)
#define OLD_Y_INVALID (1 << 4)
#define PARAGRAPH_START (1 << 5)
#define DELETED (1 << 6)
#define LINKED (1 << 7)
#define CACHED (1 << 8)
typedef struct StyleValues {
Tk_3DBorder border;
Pixmap bgStipple;
XColor *fgColor;
XColor *eolColor;
XColor *hyphenColor;
Tk_Font tkfont;
Pixmap fgStipple;
TkTextTabArray *tabArrayPtr;
Tk_3DBorder lMarginColor;
Tk_3DBorder rMarginColor;
XColor *overstrikeColor;
XColor *underlineColor;
char const *lang;
int hyphenRules;
int32_t borderWidth;
int32_t lMargin1;
int32_t lMargin2;
int32_t offset;
int32_t rMargin;
int32_t spacing1;
int32_t spacing2;
int32_t spacing3;
uint32_t wrapMode:3;
uint32_t tabStyle:3;
uint32_t justify:3;
uint32_t relief:3;
uint32_t indentBg:1;
uint32_t overstrike:1;
uint32_t underline:1;
uint32_t elide:1;
} StyleValues;
typedef struct TextStyle {
StyleValues *sValuePtr;
Tcl_HashEntry *hPtr;
GC bgGC;
GC fgGC;
GC ulGC;
GC ovGC;
GC eolGC;
GC hyphenGC;
uint32_t refCount;
} TextStyle;
typedef struct CharInfo {
union {
const char *chars;
struct CharInfo *next;
} u;
int32_t numBytes;
int32_t baseOffset;
TkTextSegment *segPtr;
} CharInfo;
typedef struct PixelPos {
int32_t xFirst, xLast;
int32_t yFirst, yLast;
} PixelPos;
typedef struct TextDInfo {
Tcl_HashTable styleTable;
DLine *dLinePtr;
DLine *lastDLinePtr;
TextStyle *defaultStyle;
GC copyGC;
GC scrollGC;
GC insertFgGC;
double xScrollFirst, xScrollLast;
double yScrollFirst, yScrollLast;
uint32_t firstLineNo;
uint32_t lastLineNo;
int32_t topPixelOffset;
int32_t newTopPixelOffset;
int32_t x;
int32_t y;
int32_t maxX;
int32_t maxY;
int32_t topOfEof;
int32_t curYPixelOffset;
TkTextSegment *endOfLineSegPtr;
DLine *cachedDLinePtr;
DLine *lastCachedDLinePtr;
unsigned numCachedLines;
DLine *lastMetricDLinePtr;
DLine *savedDLinePtr;
DLine *lastSavedDLinePtr;
int32_t savedDisplayLinesHeight;
char *strBuffer;
unsigned strBufferSize;
int32_t newXPixelOffset;
int32_t curXPixelOffset;
int32_t maxLength;
PixelPos curPixelPos;
PixelPos prevPixelPos;
int32_t scanMarkXPixel;
int32_t scanMarkX;
int32_t scanTotalYScroll;
int32_t scanMarkY;
TkTextIndex currChunkIndex;
TkTextDispChunk *currChunkPtr;
DLine *currDLinePtr;
int32_t topLineNo;
int32_t topByteIndex;
DLine *dLinePoolPtr;
TkTextDispChunk *chunkPoolPtr;
struct TkTextDispChunkSection *sectionPoolPtr;
CharInfo *charInfoPoolPtr;
bool dLinesInvalidated;
bool pendingUpdateLineMetricsFinished;
int32_t flags;
uint32_t countImages;
uint32_t countWindows;
bool insideLineMetricUpdate;
int lineHeight;
uint32_t lineMetricUpdateEpoch;
uint32_t lineMetricUpdateCounter;
TkRangeList *lineMetricUpdateRanges;
TkTextIndex metricIndex;
Tcl_TimerToken lineUpdateTimer;
Tcl_TimerToken scrollbarTimer;
Tcl_TimerToken repickTimer;
} TextDInfo;
typedef struct TkTextDispChunkSection {
struct TkTextDispChunkSection *nextPtr;
TkTextDispChunk *chunkPtr;
uint32_t numBytes;
} TkTextDispChunkSection;
#define DINFO_OUT_OF_DATE (1 << 0)
#define REDRAW_PENDING (1 << 1)
#define REDRAW_BORDERS (1 << 2)
#define ASYNC_UPDATE (1 << 3)
#define ASYNC_PENDING (1 << 4)
#define REPICK_NEEDED (1 << 5)
typedef struct LayoutData {
TkText *textPtr;
TkTextDispChunk *chunkPtr;
TkTextDispChunk *tabChunkPtr;
TkTextDispChunk *firstChunkPtr;
TkTextDispChunk *lastChunkPtr;
TkTextDispChunk *firstCharChunkPtr;
TkTextDispChunk *lastCharChunkPtr;
TkTextDispChunk *breakChunkPtr;
TkTextDispChunk *cursorChunkPtr;
TkTextLine *logicalLinePtr;
BreakInfo *breakInfo;
const char *brks;
TkTextIndex index;
unsigned countChunks;
unsigned numBytesSoFar;
unsigned byteOffset;
unsigned dispLineOffset;
int increaseNumBytes;
int decreaseNumBytes;
int displayLineNo;
int rMargin;
int hyphenRule;
TkTextTabArray *tabArrayPtr;
int tabStyle;
int tabSize;
int tabIndex;
unsigned tabWidth;
unsigned numSpaces;
TkTextJustify justify;
TkWrapMode wrapMode;
int maxX;
int width;
int x;
bool paragraphStart;
bool skipSpaces;
bool trimSpaces;
#if TK_LAYOUT_WITH_BASE_CHUNKS
TkTextDispChunk *baseChunkPtr;
#endif
} LayoutData;
typedef struct DisplayInfo {
int byteOffset;
int nextByteOffset;
int displayLineNo;
unsigned numDispLines;
int pixels;
bool isComplete;
const TkTextDispLineEntry *entry;
DLine *dLinePtr;
DLine *lastDLinePtr;
unsigned numCachedLines;
unsigned heightOfCachedLines;
TkTextIndex index;
TkTextLine *linePtr;
const TkTextPixelInfo *pixelInfo;
BreakInfo *lineBreakInfo;
TkTextDispLineEntry entryBuffer[2];
} DisplayInfo;
typedef enum {
DLINE_UNLINK, DLINE_UNLINK_KEEP_BRKS, DLINE_FREE_TEMP, DLINE_CACHE, DLINE_METRIC, DLINE_SAVE
} FreeDLineAction;
#define MAX_CACHED_DISPLAY_LINES 8
#define EPOCH_MASK 0x7fffffff
#define PARTIAL_COMPUTED_BIT 0x80000000
typedef enum {
SCROLL_MOVETO,
SCROLL_PAGES,
SCROLL_UNITS,
SCROLL_ERROR,
SCROLL_PIXELS
} ScrollMethod;
typedef enum {
THRESHOLD_BYTE_OFFSET,
THRESHOLD_LINE_OFFSET,
THRESHOLD_PIXEL_DISTANCE
} Threshold;
#define MIN_CHUNKS_PER_SECTION 10
#define MAX_SECTIONS_PER_LINE 20
static void AdjustForTab(LayoutData *data);
static void ComputeSizeOfTab(LayoutData *data);
static void ElideBboxProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int index, int y,
int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr,
int *heightPtr);
static int ElideMeasureProc(TkTextDispChunk *chunkPtr, int x);
static void DisplayDLine(TkText *textPtr, DLine *dlPtr, DLine *prevPtr, Pixmap pixmap);
static void DisplayLineBackground(TkText *textPtr, DLine *dlPtr, DLine *prevPtr,
Pixmap pixmap);
static void DisplayText(ClientData clientData);
static DLine * FindCachedDLine(TkText *textPtr, const TkTextIndex *indexPtr);
static DLine * FindDLine(TkText *textPtr, DLine *dlPtr, const TkTextIndex *indexPtr);
static DLine * FreeDLines(TkText *textPtr, DLine *firstPtr, DLine *lastPtr,
FreeDLineAction action);
static void FreeStyle(TkText *textPtr, TextStyle *stylePtr);
static TextStyle * GetStyle(TkText *textPtr, TkTextSegment *segPtr);
static void UpdateDefaultStyle(TkText *textPtr);
static void GetXView(Tcl_Interp *interp, TkText *textPtr, bool report);
static void GetYView(Tcl_Interp *interp, TkText *textPtr, bool report);
static unsigned GetYPixelCount(TkText *textPtr, DLine *dlPtr);
static DLine * LayoutDLine(const TkTextIndex *indexPtr, int displayLineNo);
static bool MeasureUp(TkText *textPtr, const TkTextIndex *srcPtr, int distance,
TkTextIndex *dstPtr, int32_t *overlap);
static bool MeasureDown(TkText *textPtr, TkTextIndex *srcPtr, int distance,
int32_t *overlap, bool saveDisplayLines);
static int NextTabStop(unsigned tabWidth, int x, int tabOrigin);
static void UpdateDisplayInfo(TkText *textPtr);
static void YScrollByLines(TkText *textPtr, int offset);
static void YScrollByPixels(TkText *textPtr, int offset);
static void TextInvalidateRegion(TkText *textPtr, TkRegion region);
static void TextInvalidateLineMetrics(TkText *textPtr, TkTextLine *linePtr,
unsigned lineCount, TkTextInvalidateAction action);
static int CalculateDisplayLineHeight(TkText *textPtr, const TkTextIndex *indexPtr,
unsigned *byteCountPtr);
static TkTextDispChunk * DLineChunkOfX(TkText *textPtr, DLine *dlPtr, int x, TkTextIndex *indexPtr,
bool *nearby);
static void DLineIndexOfX(TkText *textPtr, TkTextDispChunk *chunkPtr, int x,
TkTextIndex *indexPtr);
static int DLineXOfIndex(TkText *textPtr, DLine *dlPtr, int byteIndex);
static ScrollMethod TextGetScrollInfoObj(Tcl_Interp *interp, TkText *textPtr, int objc,
Tcl_Obj *const objv[], double *dblPtr, int *intPtr);
static void InvokeAsyncUpdateLineMetrics(TkText *textPtr);
static void InvokeAsyncUpdateYScrollbar(TkText *textPtr);
static void AsyncUpdateYScrollbar(ClientData clientData);
static void AsyncUpdateLineMetrics(ClientData clientData);
static void UpdateLineMetrics(TkText *textPtr, unsigned doThisMuch);
static bool TestIfLinesUpToDate(const TkTextIndex *indexPtr);
static void SaveDisplayLines(TkText *textPtr, DisplayInfo *info, bool append);
static TkTextLine * ComputeDisplayLineInfo(TkText *textPtr, const TkTextIndex *indexPtr,
DisplayInfo *info);
static void ComputeMissingMetric(TkText *textPtr, DisplayInfo *info,
Threshold threshold, int offset);
static unsigned GetPixelsTo(TkText *textPtr, const TkTextIndex *indexPtr,
bool inclusiveLastLine, DisplayInfo *info);
static unsigned FindDisplayLineOffset(TkText *textPtr, TkTextLine *linePtr, int32_t *distance);
static void FindDisplayLineStartEnd(TkText *textPtr, TkTextIndex *indexPtr, bool end,
int cacheType);
static void CheckIfLineMetricIsUpToDate(TkText *textPtr);
static void RunUpdateLineMetricsFinished(ClientData clientData);
static void CheckLineMetricConsistency(const TkText *textPtr);
static int ComputeBreakIndex(TkText *textPtr, const TkTextDispChunk *chunkPtr,
TkTextSegment *segPtr, int byteOffset, TkWrapMode wrapMode,
TkTextSpaceMode spaceMode);
static int CharChunkMeasureChars(TkTextDispChunk *chunkPtr, const char *chars, int charsLen,
int start, int end, int startX, int maxX, int flags, int *nextXPtr);
static void CharDisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y,
int height, int baseline, Display *display, Drawable dst, int screenY);
static void CharUndisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr);
static void HyphenUndisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr);
static void DisplayChars(TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y,
int baseline, Display *display, Drawable dst);
static int CharMeasureProc(TkTextDispChunk *chunkPtr, int x);
static void CharBboxProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int index, int y,
int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr,
int *heightPtr);
static int MeasureChars(Tk_Font tkfont, const char *source, int maxBytes, int rangeStart,
int rangeLength, int startX, int maxX, int flags, int *nextXPtr);
static CharInfo * AllocCharInfo(TkText *textPtr);
static void FreeCharInfo(TkText *textPtr, CharInfo *ciPtr);
#if TK_LAYOUT_WITH_BASE_CHUNKS
static bool IsSameFGStyle(TextStyle *style1, TextStyle *style2);
#endif
static const TkTextDispChunkProcs layoutCharProcs = {
TEXT_DISP_CHAR,
CharDisplayProc,
CharUndisplayProc,
CharMeasureProc,
CharBboxProc,
};
#define CHAR_CHUNK_GET_SEGMENT(chunkPtr) (((const CharInfo *) chunkPtr->clientData)->segPtr)
static const TkTextDispChunkProcs layoutHyphenProcs = {
TEXT_DISP_HYPHEN,
CharDisplayProc,
HyphenUndisplayProc,
CharMeasureProc,
CharBboxProc,
};
typedef union {
void *ptr;
uintptr_t flag;
} __ptr_to_int;
static void * MarkPointer(void *ptr) { __ptr_to_int p; p.ptr = ptr; p.flag |= 1; return p.ptr; }
static const TkTextDispChunkProcs layoutElideProcs = {
TEXT_DISP_ELIDED,
NULL,
NULL,
ElideMeasureProc,
ElideBboxProc,
};
#if !NDEBUG
typedef struct Statistic {
unsigned numRedisplays;
unsigned linesRedrawn;
unsigned numLayouted;
unsigned numCopies;
unsigned lineHeightsRecalculated;
unsigned breakInfo;
unsigned numCached;
unsigned numHits;
unsigned numReused;
bool perfFuncIsHooked;
} Statistic;
static Statistic stats;
static void
PerfStatistic()
{
if (!tkBTreeDebug) {
return;
}
printf("PERFORMANCE -------------------\n");
printf("Calls to DisplayText: %6u\n", stats.numRedisplays);
printf("Calls to DisplayDLine: %6u\n", stats.linesRedrawn);
printf("Calls to LayoutDLine: %6u\n", stats.numLayouted);
printf("Calls to XCopyArea: %6u\n", stats.numCopies);
printf("Re-used display lines: %6u\n", stats.numReused);
printf("Cached display lines: %6u\n", stats.numCached);
printf("Found in cache: %6u\n", stats.numHits);
printf("Line metric calculation: %6u\n", stats.lineHeightsRecalculated);
printf("Break info computation: %6u\n", stats.breakInfo);
}
#endif
#if TK_CHECK_ALLOCS
static unsigned tkTextCountNewStyle = 0;
static unsigned tkTextCountDestroyStyle = 0;
static unsigned tkTextCountNewChunk = 0;
static unsigned tkTextCountDestroyChunk = 0;
static unsigned tkTextCountNewSection = 0;
static unsigned tkTextCountDestroySection = 0;
static unsigned tkTextCountNewCharInfo = 0;
static unsigned tkTextCountDestroyCharInfo = 0;
static unsigned tkTextCountNewBreakInfo = 0;
static unsigned tkTextCountDestroyBreakInfo = 0;
static unsigned tkTextCountNewDLine = 0;
static unsigned tkTextCountDestroyDLine = 0;
static unsigned tkTextCountNewDispInfo = 0;
unsigned tkTextCountDestroyDispInfo = 0;
#if TK_LAYOUT_WITH_BASE_CHUNKS
unsigned tkTextCountNewBaseChars = 0;
unsigned tkTextCountDestroyBaseChars = 0;
#endif
extern unsigned tkTextCountDestroySegment;
extern unsigned tkRangeListCountNew;
extern unsigned tkRangeListCountDestroy;
static bool hookStatFunc = true;
static void
AllocStatistic()
{
if (!tkBTreeDebug) {
return;
}
printf("--------------------------------\n");
printf("ALLOCATION: new destroy\n");
printf("--------------------------------\n");
printf("DLine: %8u - %8u\n", tkTextCountNewDLine, tkTextCountDestroyDLine);
printf("Chunk: %8u - %8u\n", tkTextCountNewChunk, tkTextCountDestroyChunk);
printf("Section: %8u - %8u\n", tkTextCountNewSection, tkTextCountDestroySection);
printf("CharInfo: %8u - %8u\n", tkTextCountNewCharInfo, tkTextCountDestroyCharInfo);
printf("DispInfo: %8u - %8u\n", tkTextCountNewDispInfo, tkTextCountDestroyDispInfo);
printf("BreakInfo: %8u - %8u\n", tkTextCountNewBreakInfo, tkTextCountDestroyBreakInfo);
#if TK_LAYOUT_WITH_BASE_CHUNKS
printf("BaseChars: %8u - %8u\n", tkTextCountNewBaseChars, tkTextCountDestroyBaseChars);
#endif
printf("Style: %8u - %8u\n", tkTextCountNewStyle, tkTextCountDestroyStyle);
printf("RangeList: %8u - %8u\n", tkRangeListCountNew, tkRangeListCountDestroy);
if (tkTextCountNewDLine != tkTextCountDestroyDLine
|| tkTextCountNewChunk != tkTextCountDestroyChunk
|| tkTextCountNewSection != tkTextCountDestroySection
|| tkTextCountNewCharInfo != tkTextCountDestroyCharInfo
|| tkTextCountNewDispInfo != tkTextCountDestroyDispInfo
#if TK_LAYOUT_WITH_BASE_CHUNKS
|| tkTextCountNewBaseChars != tkTextCountDestroyBaseChars
#endif
|| tkTextCountNewStyle != tkTextCountDestroyStyle
|| tkRangeListCountNew != tkRangeListCountDestroy) {
printf("*** memory leak detected ***\n");
}
}
#endif
static const char doNotBreakAtAll[8] = {
LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK,
LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK };
static bool IsPowerOf2(unsigned n) { return !(n & (n - 1)); }
static unsigned
NextPowerOf2(uint32_t n)
{
--n;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return ++n;
}
static bool
IsExpandableSpace(
const char *s)
{
return UCHAR(s[0]) == 0x20 || (UCHAR(s[0]) == 0xc2 && UCHAR(s[1]) == 0x0a);
}
static void
LogTextHeightCalc(
TkText *textPtr,
const TkTextIndex *indexPtr)
{
char string[TK_POS_CHARS];
assert(tkTextDebug);
TkTextPrintIndex(textPtr, indexPtr, string);
LOG("tk_textHeightCalc", string);
}
static void
LogTextRelayout(
TkText *textPtr,
const TkTextIndex *indexPtr)
{
char string[TK_POS_CHARS];
assert(tkTextDebug);
TkTextPrintIndex(textPtr, indexPtr, string);
LOG("tk_textRelayout", string);
}
static void
LogTextInvalidateLine(
TkText *textPtr,
unsigned count)
{
char buffer[4*TCL_INTEGER_SPACE + 3];
const TkRangeList *ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
unsigned totalCount = TkRangeListCount(ranges) - count;
unsigned totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
int lineNum = TkRangeListIsEmpty(ranges) ? -1 : TkRangeListLow(ranges);
assert(tkTextDebug);
snprintf(buffer, sizeof(buffer), "%d %u - %u %u", lineNum, totalLines, count, totalCount);
LOG("tk_textInvalidateLine", buffer);
}
static void
DisplayTextWhenIdle(
TkText *textPtr)
{
if (textPtr->sharedTextPtr->allowUpdateLineMetrics && !(textPtr->dInfoPtr->flags & REDRAW_PENDING)) {
textPtr->dInfoPtr->flags |= REDRAW_PENDING;
Tcl_DoWhenIdle(DisplayText, textPtr);
}
}
static int
GetLeftLineMargin(
const DLine *dlPtr,
const StyleValues *sValuePtr)
{
assert(dlPtr);
assert(sValuePtr);
return (dlPtr->flags & PARAGRAPH_START) ? sValuePtr->lMargin1 : sValuePtr->lMargin2;
}
#if SPEEDUP_MONOSPACED_LINE_HEIGHTS
static bool
TestMonospacedLineHeights(
const TkText *textPtr)
{
return textPtr->wrapMode == TEXT_WRAPMODE_NONE
&& textPtr->dInfoPtr->countImages == 0
&& textPtr->dInfoPtr->countWindows == 0
&& TkTextTagSetDisjunctiveBits(TkBTreeRootTagInfo(textPtr->sharedTextPtr->tree),
textPtr->sharedTextPtr->affectLineHeightTags);
return false;
}
#endif
static bool
UseMonospacedLineHeights(
const TkText *textPtr)
{
#if SPEEDUP_MONOSPACED_LINE_HEIGHTS
return TestMonospacedLineHeights(textPtr)
&& TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges);
#else
return false;
#endif
}
static const unsigned char isVowel[256] = {
#define _ 0
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, 1, _, _, _, 1, _, _, _, 1, _, _, _, _, _, 1,
_, _, _, _, _, 1, _, _, _, _, _, _, _, _, _, _,
_, 1, _, _, _, 1, _, _, _, 1, _, _, _, _, _, 1,
_, _, _, _, _, 1, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
#undef _
};
static const unsigned char isConsonant[256] = {
#define _ 0
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, _, _, _, _, _,
_, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
#undef _
};
static const unsigned char isUmlaut[256] = {
#define _ 0
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, 1, _, _, _, _, _, _, 1, _, _, _, _,
_, _, _, _, _, _, 1, _, _, _, _, _, 1, _, _, _,
_, _, _, _, 1, _, _, _, _, _, _, 1, _, _, _, _,
_, _, _, _, _, _, 1, _, _, _, _, _, 1, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
#undef _
};
static const unsigned char umlautToVowel[256] = {
#define ___ 0
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, 'A', ___, ___, ___, ___, ___, ___, 'E', ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, 'O', ___, ___, ___, ___, ___, 'U', ___, ___, ___,
___, ___, ___, ___, 'a', ___, ___, ___, ___, ___, ___, 'e', ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, 'o', ___, ___, ___, ___, ___, 'u', ___, ___, ___,
#undef ___
};
static bool IsVowel(unsigned char c) { return isVowel[c]; }
static bool IsUmlaut(unsigned char c) { return umlautToVowel[c] != 0; }
static bool IsConsonant(unsigned char c) { return isConsonant[c]; }
static unsigned char UmlautToVowel(unsigned char c) { return umlautToVowel[c]; }
static unsigned char ConvertC3Next(unsigned char c) { return 0xc0 | (c - 0x80); }
static bool
IsUmlautOrVowel(const char *s)
{
return UCHAR(s[0]) == 0xc3 ? isUmlaut[UCHAR(s[1])] : UCHAR(s[0]) < 0x80 && isVowel[UCHAR(s[0])];
}
static void
SetupHyphenChars(
TkTextSegment *segPtr,
unsigned offset)
{
assert(offset <= 2);
assert(segPtr->typePtr->group == SEG_GROUP_HYPHEN);
assert(sizeof(doNotBreakAtAll) >= 6);
memcpy(segPtr->body.chars + offset, "\xe2\x80\x90", 4);
segPtr->body.hyphen.textSize = 3 + offset;
}
static bool
IsDoubleDigraph(
char c1,
char c2)
{
switch (c1) {
case 'c':
case 'z': return c2 == 's';
case 'g':
case 'l':
case 'n':
case 't': return c2 == 'y';
case 's': return c2 == 'z';
}
return false;
}
static bool
IsHyphenChunk(
const TkTextDispChunk *chunkPtr)
{
assert(chunkPtr);
return chunkPtr->layoutProcs && chunkPtr->layoutProcs->type == TEXT_DISP_HYPHEN;
}
static bool
IsCharChunk(
const TkTextDispChunk *chunkPtr)
{
assert(chunkPtr);
return chunkPtr->layoutProcs && chunkPtr->layoutProcs->type == TEXT_DISP_CHAR;
}
static char
GetLastCharInChunk(
const TkTextDispChunk *chunkPtr)
{
const CharInfo *ciPtr;
if (!chunkPtr) {
return '\0';
}
assert(chunkPtr->layoutProcs);
assert(chunkPtr->clientData);
if (!IsCharChunk(chunkPtr)) {
return '\0';
}
ciPtr = chunkPtr->clientData;
assert(ciPtr->numBytes > 0);
return ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 1];
}
static char
GetSecondLastCharInChunk(
const TkTextDispChunk *chunkPtr)
{
const CharInfo *ciPtr;
if (!chunkPtr || !IsCharChunk(chunkPtr)) {
return '\0';
}
ciPtr = chunkPtr->clientData;
assert(chunkPtr->clientData);
assert(ciPtr->numBytes > 0);
if (ciPtr->numBytes > 1) {
return ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 2];
}
if ((chunkPtr = chunkPtr->prevCharChunkPtr) && IsCharChunk(chunkPtr)) {
ciPtr = chunkPtr->clientData;
assert(ciPtr->numBytes > 0);
return ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 1];
}
return '\0';
}
static int
FilterHyphenRules(
int hyphenRules,
const char *lang)
{
if (lang && hyphenRules) {
enum {
CA_RULES = (1 << TK_TEXT_HYPHEN_GEMINATION),
DE_RULES = (1 << TK_TEXT_HYPHEN_CK)|(1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT),
HU_RULES = (1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH),
NL_RULES = (1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL)|(1 << TK_TEXT_HYPHEN_TREMA),
NO_RULES = (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT),
PL_RULES = (1 << TK_TEXT_HYPHEN_REPEAT),
SV_RULES = (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT)
};
switch (lang[0]) {
case 'c': if (lang[1] == 'a') { hyphenRules &= CA_RULES; }; break;
case 'd': if (lang[1] == 'e') { hyphenRules &= DE_RULES; }; break;
case 'h': if (lang[1] == 'u') { hyphenRules &= HU_RULES; }; break;
case 'p': if (lang[1] == 'l') { hyphenRules &= PL_RULES; }; break;
case 's': if (lang[1] == 'v') { hyphenRules &= SV_RULES; }; break;
case 'n':
switch (lang[1]) {
case 'b':
case 'n':
case 'o': hyphenRules &= NO_RULES; break;
case 'l': hyphenRules &= NL_RULES; break;
}
break;
}
}
return hyphenRules;
}
bool
TkTextPendingSync(
const TkText *textPtr)
{
return !!(textPtr->dInfoPtr->flags & (ASYNC_UPDATE|ASYNC_PENDING));
}
static bool
TestIfLinesUpToDate(
const TkTextIndex *indexPtr)
{
const TkRangeList *ranges;
assert(indexPtr->textPtr);
ranges = indexPtr->textPtr->dInfoPtr->lineMetricUpdateRanges;
if (TkRangeListIsEmpty(ranges)) {
return true;
}
return TkTextIndexGetLineNumber(indexPtr, indexPtr->textPtr) < TkRangeListLow(ranges);
}
static void
InvokeAsyncUpdateYScrollbar(
TkText *textPtr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
assert(!dInfoPtr->scrollbarTimer);
if (textPtr->syncTime == 0) {
AsyncUpdateYScrollbar(textPtr);
} else {
textPtr->refCount += 1;
dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(textPtr->syncTime,
AsyncUpdateYScrollbar, textPtr);
}
}
static void
InvokeAsyncUpdateLineMetrics(
TkText *textPtr)
{
assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
if (textPtr->syncTime > 0) {
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (!dInfoPtr->lineUpdateTimer) {
textPtr->refCount += 1;
dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr);
}
}
}
static void
SetupEolSegment(
TkText *textPtr,
TextDInfo *dInfoPtr)
{
char eolChar[10];
Tcl_UniChar uc;
const char *p = textPtr->eolCharPtr ? Tcl_GetString(textPtr->eolCharPtr) : NULL;
int len;
if (!p || !*p) { p = "\xc2\xb6"; }
len = Tcl_UtfToUniChar(p, &uc);
strcpy(eolChar, p);
strcpy(eolChar + len, "\n");
if (dInfoPtr->endOfLineSegPtr) {
TkBTreeFreeSegment(dInfoPtr->endOfLineSegPtr);
}
dInfoPtr->endOfLineSegPtr = TkBTreeMakeCharSegment(
eolChar, len + 1, textPtr->sharedTextPtr->emptyTagInfoPtr);
}
void
TkTextCreateDInfo(
TkText *textPtr)
{
TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
TkTextBTree tree = sharedTextPtr->tree;
TextDInfo *dInfoPtr;
XGCValues gcValues;
bool isMonospaced;
dInfoPtr = memset(malloc(sizeof(TextDInfo)), 0, sizeof(TextDInfo));
Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
dInfoPtr->copyGC = None;
gcValues.graphics_exposures = True;
dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);
dInfoPtr->insertFgGC = None;
dInfoPtr->xScrollFirst = -1;
dInfoPtr->xScrollLast = -1;
dInfoPtr->yScrollFirst = -1;
dInfoPtr->yScrollLast = -1;
dInfoPtr->topLineNo = -1;
dInfoPtr->topByteIndex = -1;
dInfoPtr->flags = DINFO_OUT_OF_DATE;
dInfoPtr->lineMetricUpdateRanges = TkRangeListCreate(64);
dInfoPtr->firstLineNo = TkBTreeLinesTo(tree, NULL, TkBTreeGetStartLine(textPtr), NULL);
dInfoPtr->lastLineNo = TkBTreeLinesTo(tree, NULL, TkBTreeGetLastLine(textPtr), NULL);
dInfoPtr->lineMetricUpdateEpoch = 1;
dInfoPtr->strBufferSize = 512;
dInfoPtr->strBuffer = malloc(dInfoPtr->strBufferSize);
TkTextIndexClear(&dInfoPtr->metricIndex, textPtr);
TkTextIndexClear(&dInfoPtr->currChunkIndex, textPtr);
SetupEolSegment(textPtr, dInfoPtr);
if (textPtr->state == TK_TEXT_STATE_NORMAL
&& textPtr->blockCursorType
&& textPtr->showInsertFgColor) {
XGCValues gcValues;
gcValues.foreground = textPtr->insertFgColorPtr->pixel;
dInfoPtr->insertFgGC = Tk_GetGC(textPtr->tkwin, GCForeground, &gcValues);
}
textPtr->dInfoPtr = dInfoPtr;
isMonospaced = UseMonospacedLineHeights(textPtr);
if (isMonospaced) {
TkBTreeUpdatePixelHeights(textPtr, TkBTreeGetStartLine(textPtr), 1,
dInfoPtr->lineMetricUpdateEpoch);
} else {
dInfoPtr->lineMetricUpdateRanges = TkRangeListAdd(dInfoPtr->lineMetricUpdateRanges, 0, 0);
}
if (!sharedTextPtr->breakInfoTableIsInitialized) {
Tcl_InitHashTable(&sharedTextPtr->breakInfoTable, TCL_ONE_WORD_KEYS);
sharedTextPtr->breakInfoTableIsInitialized = true;
}
if (sharedTextPtr->allowUpdateLineMetrics) {
if (!isMonospaced) {
InvokeAsyncUpdateLineMetrics(textPtr);
}
InvokeAsyncUpdateYScrollbar(textPtr);
}
#if TK_CHECK_ALLOCS
if (hookStatFunc) {
atexit(AllocStatistic);
hookStatFunc = false;
}
#endif
#if !NDEBUG
if (!stats.perfFuncIsHooked) {
atexit(PerfStatistic);
stats.perfFuncIsHooked = true;
}
#endif
}
void
TkTextDeleteBreakInfoTableEntries(
Tcl_HashTable *breakInfoTable)
{
Tcl_HashSearch search;
Tcl_HashEntry *hPtr;
assert(breakInfoTable);
for (hPtr = Tcl_FirstHashEntry(breakInfoTable, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) {
BreakInfo *breakInfo = Tcl_GetHashValue(hPtr);
assert(breakInfo->brks);
free(breakInfo->brks);
free(breakInfo);
DEBUG_ALLOC(tkTextCountDestroyBreakInfo++);
}
}
void
TkTextFreeDInfo(
TkText *textPtr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextDispChunk *chunkPtr;
TkTextDispChunkSection *sectionPtr;
DLine *dlPtr;
CharInfo *ciPtr;
if (dInfoPtr->pendingUpdateLineMetricsFinished) {
Tcl_CancelIdleCall(RunUpdateLineMetricsFinished, (ClientData) textPtr);
}
if (dInfoPtr->flags & REDRAW_PENDING) {
Tcl_CancelIdleCall(DisplayText, textPtr);
}
FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
FreeDLines(textPtr, dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
FreeDLines(textPtr, NULL, NULL, DLINE_CACHE);
FreeDLines(textPtr, NULL, NULL, DLINE_METRIC);
if (dInfoPtr->copyGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
}
Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);
if (dInfoPtr->insertFgGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->insertFgGC);
}
if (dInfoPtr->lineUpdateTimer) {
Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer);
textPtr->refCount -= 1;
dInfoPtr->lineUpdateTimer = NULL;
}
if (dInfoPtr->scrollbarTimer) {
Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer);
textPtr->refCount -= 1;
dInfoPtr->scrollbarTimer = NULL;
}
if (dInfoPtr->repickTimer) {
Tcl_DeleteTimerHandler(dInfoPtr->repickTimer);
textPtr->refCount -= 1;
dInfoPtr->repickTimer = NULL;
}
ciPtr = dInfoPtr->charInfoPoolPtr;
while (ciPtr) {
CharInfo *nextPtr = ciPtr->u.next;
free(ciPtr);
DEBUG_ALLOC(tkTextCountDestroyCharInfo++);
ciPtr = nextPtr;
}
sectionPtr = dInfoPtr->sectionPoolPtr;
while (sectionPtr) {
TkTextDispChunkSection *nextPtr = sectionPtr->nextPtr;
free(sectionPtr);
DEBUG_ALLOC(tkTextCountDestroySection++);
sectionPtr = nextPtr;
}
chunkPtr = dInfoPtr->chunkPoolPtr;
while (chunkPtr) {
TkTextDispChunk *nextPtr = chunkPtr->nextPtr;
free(chunkPtr);
DEBUG_ALLOC(tkTextCountDestroyChunk++);
chunkPtr = nextPtr;
}
dlPtr = dInfoPtr->dLinePoolPtr;
while (dlPtr) {
DLine *nextPtr = dlPtr->nextPtr;
free(dlPtr);
DEBUG_ALLOC(tkTextCountDestroyDLine++);
dlPtr = nextPtr;
}
if (dInfoPtr->defaultStyle) {
#if 0
DEBUG_ALLOC(assert(dInfoPtr->defaultStyle->refCount == 1));
#endif
FreeStyle(textPtr, dInfoPtr->defaultStyle);
}
Tcl_DeleteHashTable(&dInfoPtr->styleTable);
TkRangeListDestroy(&dInfoPtr->lineMetricUpdateRanges);
TkBTreeFreeSegment(dInfoPtr->endOfLineSegPtr);
free(dInfoPtr->strBuffer);
free(dInfoPtr);
}
void
TkTextResetDInfo(
TkText *textPtr)
{
TextDInfo *dInfoPtr;
TkSharedText *sharedTextPtr;
TkTextIndex index1, index2;
unsigned lineNo1, lineNo2;
if (UseMonospacedLineHeights(textPtr)) {
return;
}
dInfoPtr = textPtr->dInfoPtr;
sharedTextPtr = textPtr->sharedTextPtr;
TkTextIndexSetupToStartOfText(&index1, textPtr, sharedTextPtr->tree);
TkTextIndexSetupToEndOfText(&index2, textPtr, sharedTextPtr->tree);
TkTextChanged(sharedTextPtr, NULL, &index1, &index2);
lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, textPtr, TkTextIndexGetLine(&index1), NULL);
lineNo2 = TkBTreeLinesTo(sharedTextPtr->tree, textPtr, TkTextIndexGetLine(&index2), NULL);
assert(lineNo1 < lineNo2);
TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
dInfoPtr->lineMetricUpdateRanges =
TkRangeListAdd(dInfoPtr->lineMetricUpdateRanges, lineNo1, lineNo2 - 1);
dInfoPtr->lineMetricUpdateEpoch = 1;
dInfoPtr->topLineNo = -1;
dInfoPtr->topByteIndex = -1;
if (textPtr->sharedTextPtr->allowUpdateLineMetrics) {
TkTextUpdateLineMetrics(textPtr, lineNo1, lineNo2);
}
FreeDLines(textPtr, NULL, NULL, DLINE_CACHE);
FreeDLines(textPtr, NULL, NULL, DLINE_METRIC);
}
static TextStyle *
MakeStyle(
TkText *textPtr,
TkTextTag *tagPtr)
{
StyleValues styleValues;
TextStyle *stylePtr;
Tcl_HashEntry *hPtr;
int isNew;
bool isSelected;
XGCValues gcValues;
unsigned long mask;
int borderPrio = -1, borderWidthPrio = -1, reliefPrio = -1;
int bgStipplePrio = -1, indentBgPrio = -1;
int fgPrio = -1, fontPrio = -1, fgStipplePrio = -1;
int underlinePrio = -1, elidePrio = -1, justifyPrio = -1, offsetPrio = -1;
int lMargin1Prio = -1, lMargin2Prio = -1, rMarginPrio = -1;
int lMarginColorPrio = -1, rMarginColorPrio = -1;
int spacing1Prio = -1, spacing2Prio = -1, spacing3Prio = -1;
int overstrikePrio = -1, tabPrio = -1, tabStylePrio = -1;
int wrapPrio = -1, langPrio = -1, hyphenRulesPrio = -1;
int eolColorPrio = -1, hyphenColorPrio = -1;
memset(&styleValues, 0, sizeof(StyleValues));
styleValues.relief = TK_RELIEF_FLAT;
styleValues.fgColor = textPtr->fgColor;
styleValues.eolColor = textPtr->eolColor;
styleValues.hyphenColor = textPtr->hyphenColor;
styleValues.underlineColor = textPtr->fgColor;
styleValues.overstrikeColor = textPtr->fgColor;
styleValues.tkfont = textPtr->tkfont;
styleValues.justify = textPtr->justify;
styleValues.spacing1 = textPtr->spacing1;
styleValues.spacing2 = textPtr->spacing2;
styleValues.spacing3 = textPtr->spacing3;
styleValues.tabArrayPtr = textPtr->tabArrayPtr;
styleValues.tabStyle = textPtr->tabStyle;
styleValues.wrapMode = textPtr->wrapMode;
styleValues.lang = textPtr->lang;
styleValues.hyphenRules = textPtr->hyphenRulesPtr ? textPtr->hyphenRules : TK_TEXT_HYPHEN_MASK;
isSelected = false;
for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
Tk_3DBorder border;
XColor *fgColor;
border = tagPtr->border;
fgColor = tagPtr->fgColor;
if (tagPtr == textPtr->selTagPtr && !(textPtr->flags & HAVE_FOCUS)) {
if (!textPtr->inactiveSelBorder) {
continue;
}
#ifdef MAC_OSX_TK
if (textPtr->state == TK_TEXT_STATE_DISABLED) {
continue;
}
#endif
border = textPtr->inactiveSelBorder;
}
if (tagPtr->selBorder && isSelected) {
border = tagPtr->selBorder;
}
if (tagPtr->selFgColor != None && isSelected) {
fgColor = tagPtr->selFgColor;
}
if (border && tagPtr->priority > borderPrio) {
styleValues.border = border;
borderPrio = tagPtr->priority;
}
if (tagPtr->borderWidthPtr
&& Tcl_GetString(tagPtr->borderWidthPtr)[0] != '\0'
&& tagPtr->priority > borderWidthPrio) {
styleValues.borderWidth = tagPtr->borderWidth;
borderWidthPrio = tagPtr->priority;
}
if (tagPtr->reliefString && tagPtr->priority > reliefPrio) {
if (!styleValues.border) {
styleValues.border = textPtr->border;
}
assert(tagPtr->relief < 8);
styleValues.relief = tagPtr->relief;
reliefPrio = tagPtr->priority;
}
if (tagPtr->bgStipple != None && tagPtr->priority > bgStipplePrio) {
styleValues.bgStipple = tagPtr->bgStipple;
bgStipplePrio = tagPtr->priority;
}
if (tagPtr->indentBgString != None && tagPtr->priority > indentBgPrio) {
assert(tagPtr->indentBg <= 1);
styleValues.indentBg = tagPtr->indentBg;
indentBgPrio = tagPtr->priority;
}
if (fgColor != None && tagPtr->priority > fgPrio) {
styleValues.fgColor = fgColor;
fgPrio = tagPtr->priority;
}
if (tagPtr->tkfont != None && tagPtr->priority > fontPrio) {
styleValues.tkfont = tagPtr->tkfont;
fontPrio = tagPtr->priority;
}
if (tagPtr->fgStipple != None && tagPtr->priority > fgStipplePrio) {
styleValues.fgStipple = tagPtr->fgStipple;
fgStipplePrio = tagPtr->priority;
}
if (tagPtr->justifyString && tagPtr->priority > justifyPrio) {
styleValues.justify = tagPtr->justify;
justifyPrio = tagPtr->priority;
}
if (tagPtr->lMargin1String && tagPtr->priority > lMargin1Prio) {
styleValues.lMargin1 = tagPtr->lMargin1;
lMargin1Prio = tagPtr->priority;
}
if (tagPtr->lMargin2String && tagPtr->priority > lMargin2Prio) {
styleValues.lMargin2 = tagPtr->lMargin2;
lMargin2Prio = tagPtr->priority;
}
if (tagPtr->lMarginColor && tagPtr->priority > lMarginColorPrio) {
styleValues.lMarginColor = tagPtr->lMarginColor;
lMarginColorPrio = tagPtr->priority;
}
if (tagPtr->offsetString && tagPtr->priority > offsetPrio) {
styleValues.offset = tagPtr->offset;
offsetPrio = tagPtr->priority;
}
if (tagPtr->overstrikeString && tagPtr->priority > overstrikePrio) {
assert(tagPtr->overstrike <= 1);
styleValues.overstrike = tagPtr->overstrike;
overstrikePrio = tagPtr->priority;
if (tagPtr->overstrikeColor != None) {
styleValues.overstrikeColor = tagPtr->overstrikeColor;
} else if (fgColor != None) {
styleValues.overstrikeColor = fgColor;
}
}
if (tagPtr->rMarginString && tagPtr->priority > rMarginPrio) {
styleValues.rMargin = tagPtr->rMargin;
rMarginPrio = tagPtr->priority;
}
if (tagPtr->rMarginColor && tagPtr->priority > rMarginColorPrio) {
styleValues.rMarginColor = tagPtr->rMarginColor;
rMarginColorPrio = tagPtr->priority;
}
if (tagPtr->spacing1String && tagPtr->priority > spacing1Prio) {
styleValues.spacing1 = tagPtr->spacing1;
spacing1Prio = tagPtr->priority;
}
if (tagPtr->spacing2String && tagPtr->priority > spacing2Prio) {
styleValues.spacing2 = tagPtr->spacing2;
spacing2Prio = tagPtr->priority;
}
if (tagPtr->spacing3String && tagPtr->priority > spacing3Prio) {
styleValues.spacing3 = tagPtr->spacing3;
spacing3Prio = tagPtr->priority;
}
if (tagPtr->tabStringPtr && tagPtr->priority > tabPrio) {
styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
tabPrio = tagPtr->priority;
}
if (tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE && tagPtr->priority > tabStylePrio) {
assert(tagPtr->tabStyle < 8);
styleValues.tabStyle = tagPtr->tabStyle;
tabStylePrio = tagPtr->priority;
}
if (tagPtr->eolColor && tagPtr->priority > eolColorPrio) {
styleValues.eolColor = tagPtr->eolColor;
eolColorPrio = tagPtr->priority;
}
if (tagPtr->hyphenColor && tagPtr->priority > hyphenColorPrio) {
styleValues.hyphenColor = tagPtr->hyphenColor;
hyphenColorPrio = tagPtr->priority;
}
if (tagPtr->underlineString && tagPtr->priority > underlinePrio) {
assert(tagPtr->underline <= 1);
styleValues.underline = tagPtr->underline;
underlinePrio = tagPtr->priority;
if (tagPtr->underlineColor != None) {
styleValues.underlineColor = tagPtr->underlineColor;
} else if (fgColor != None) {
styleValues.underlineColor = fgColor;
}
}
if (tagPtr->elideString && tagPtr->priority > elidePrio) {
assert(tagPtr->elide <= 1);
styleValues.elide = tagPtr->elide;
elidePrio = tagPtr->priority;
}
if (tagPtr->langPtr && tagPtr->priority > langPrio) {
styleValues.lang = tagPtr->lang;
langPrio = tagPtr->priority;
}
if (tagPtr->hyphenRulesPtr && tagPtr->priority > hyphenRulesPrio) {
styleValues.hyphenRules = tagPtr->hyphenRules;
hyphenRulesPrio = tagPtr->priority;
}
if (tagPtr->wrapMode != TEXT_WRAPMODE_NULL && tagPtr->priority > wrapPrio) {
styleValues.wrapMode = tagPtr->wrapMode;
wrapPrio = tagPtr->priority;
}
}
hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, (char *) &styleValues, &isNew);
if (!isNew) {
return Tcl_GetHashValue(hPtr);
}
stylePtr = malloc(sizeof(TextStyle));
stylePtr->refCount = 0;
if (styleValues.border) {
gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;
mask = GCForeground;
if (styleValues.bgStipple != None) {
gcValues.stipple = styleValues.bgStipple;
gcValues.fill_style = FillStippled;
mask |= GCStipple|GCFillStyle;
}
stylePtr->bgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
} else {
stylePtr->bgGC = None;
}
mask = GCFont;
gcValues.font = Tk_FontId(styleValues.tkfont);
mask |= GCForeground;
if (styleValues.eolColor) {
gcValues.foreground = styleValues.eolColor->pixel;
stylePtr->eolGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
} else {
stylePtr->eolGC = None;
}
if (styleValues.hyphenColor) {
gcValues.foreground = styleValues.hyphenColor->pixel;
stylePtr->hyphenGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
} else {
stylePtr->hyphenGC = None;
}
gcValues.foreground = styleValues.fgColor->pixel;
if (styleValues.fgStipple != None) {
gcValues.stipple = styleValues.fgStipple;
gcValues.fill_style = FillStippled;
mask |= GCStipple|GCFillStyle;
}
stylePtr->fgGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
mask = GCForeground;
gcValues.foreground = styleValues.underlineColor->pixel;
stylePtr->ulGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
gcValues.foreground = styleValues.overstrikeColor->pixel;
stylePtr->ovGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
stylePtr->sValuePtr = (StyleValues *) Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
stylePtr->hPtr = hPtr;
Tcl_SetHashValue(hPtr, stylePtr);
DEBUG_ALLOC(tkTextCountNewStyle++);
return stylePtr;
}
static TextStyle *
GetStyle(
TkText *textPtr,
TkTextSegment *segPtr)
{
TextStyle *stylePtr;
TkTextTag *tagPtr;
if (segPtr && (tagPtr = TkBTreeGetSegmentTags(textPtr->sharedTextPtr, segPtr, textPtr))) {
stylePtr = MakeStyle(textPtr, tagPtr);
} else {
if (!textPtr->dInfoPtr->defaultStyle) {
UpdateDefaultStyle(textPtr);
}
stylePtr = textPtr->dInfoPtr->defaultStyle;
}
stylePtr->refCount += 1;
return stylePtr;
}
static void
UpdateDefaultStyle(
TkText *textPtr)
{
TextStyle *stylePtr = MakeStyle(textPtr, NULL);
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (stylePtr != dInfoPtr->defaultStyle) {
if (dInfoPtr->defaultStyle) {
FreeStyle(textPtr, dInfoPtr->defaultStyle);
}
dInfoPtr->defaultStyle = stylePtr;
stylePtr->refCount += 1;
}
}
static void
FreeStyle(
TkText *textPtr,
TextStyle *stylePtr)
{
assert(stylePtr);
assert(stylePtr->refCount > 0);
if (--stylePtr->refCount == 0) {
if (stylePtr->bgGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->bgGC);
}
if (stylePtr->fgGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->fgGC);
}
if (stylePtr->ulGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->ulGC);
}
if (stylePtr->ovGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->ovGC);
}
if (stylePtr->eolGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->eolGC);
}
if (stylePtr->hyphenGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->hyphenGC);
}
Tcl_DeleteHashEntry(stylePtr->hPtr);
free(stylePtr);
DEBUG_ALLOC(tkTextCountDestroyStyle++);
}
}
static bool
IsStartOfNotMergedLine(
const TkTextIndex *indexPtr)
{
return TkTextIndexGetLine(indexPtr)->logicalLine
? TkTextIndexIsStartOfLine(indexPtr)
: TkTextIndexIsStartOfText(indexPtr);
}
#if TK_LAYOUT_WITH_BASE_CHUNKS
static bool
IsSameFGStyle(
TextStyle *style1,
TextStyle *style2)
{
StyleValues *sv1;
StyleValues *sv2;
if (style1 == style2) {
return true;
}
sv1 = style1->sValuePtr;
sv2 = style2->sValuePtr;
return sv1->tkfont == sv2->tkfont && sv1->offset == sv2->offset;
}
#endif
static TkTextSegment *
LayoutGetNextSegment(
TkTextSegment *segPtr)
{
while ((segPtr = segPtr->nextPtr)) {
if (segPtr->typePtr == &tkTextCharType) {
return segPtr;
}
if (segPtr->typePtr == &tkTextBranchType) {
segPtr = segPtr->body.branch.nextPtr;
}
}
return NULL;
}
static TkTextDispChunk *
LayoutGetNextCharChunk(
TkTextDispChunk *chunkPtr)
{
while ((chunkPtr = chunkPtr->nextPtr)) {
switch (chunkPtr->layoutProcs->type) {
case TEXT_DISP_CHAR: return chunkPtr;
case TEXT_DISP_WINDOW:
case TEXT_DISP_IMAGE: return NULL;
case TEXT_DISP_HYPHEN:
case TEXT_DISP_ELIDED:
case TEXT_DISP_CURSOR: break;
}
}
return NULL;
}
static void
LayoutSetupDispLineInfo(
TkTextPixelInfo *pixelInfo)
{
TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
unsigned oldNumDispLines = TkBTreeGetNumberOfDisplayLines(pixelInfo);
if (!dispLineInfo) {
dispLineInfo = malloc(TEXT_DISPLINEINFO_SIZE(2));
DEBUG(memset(dispLineInfo, 0xff, TEXT_DISPLINEINFO_SIZE(2)));
DEBUG_ALLOC(tkTextCountNewDispInfo++);
pixelInfo->dispLineInfo = dispLineInfo;
}
dispLineInfo->numDispLines = 1;
dispLineInfo->entry[1].pixels = oldNumDispLines;
}
static void
LayoutUpdateLineHeightInformation(
const LayoutData *data,
DLine *dlPtr,
TkTextLine *linePtr,
bool finished,
int hyphenRule)
{
TkText *textPtr = data->textPtr;
unsigned epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
unsigned oldNumDispLines = TkBTreeGetNumberOfDisplayLines(pixelInfo);
TkTextDispLineInfo *dispLineInfo;
TkTextLine *nextLogicalLinePtr;
assert(dlPtr->byteCount > 0);
assert(dlPtr->displayLineNo >= 0);
assert(linePtr->logicalLine);
assert(linePtr == TkBTreeGetLogicalLine(
textPtr->sharedTextPtr, textPtr, TkTextIndexGetLine(&dlPtr->index)));
if (pixelInfo->epoch == epoch) {
int lineNo = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
if (TkRangeListContains(textPtr->dInfoPtr->lineMetricUpdateRanges, lineNo)) {
int mergedLines = 1;
nextLogicalLinePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
if (linePtr->nextPtr != nextLogicalLinePtr) {
mergedLines = TkBTreeCountLines(textPtr->sharedTextPtr->tree, linePtr,
nextLogicalLinePtr) - 1;
}
TkRangeListRemove(textPtr->dInfoPtr->lineMetricUpdateRanges, lineNo, lineNo + mergedLines);
}
return;
}
TK_TEXT_DEBUG(LogTextHeightCalc(textPtr, &dlPtr->index));
dispLineInfo = pixelInfo->dispLineInfo;
dlPtr->hyphenRule = hyphenRule;
if (dlPtr->displayLineNo > 0) {
TkTextDispLineEntry *dispLineEntry;
assert(dispLineInfo);
assert(data->byteOffset == dispLineInfo->entry[dlPtr->displayLineNo].byteOffset);
if (dlPtr->displayLineNo >= dispLineInfo->numDispLines
&& !IsPowerOf2(dlPtr->displayLineNo + 2)) {
unsigned size = NextPowerOf2(dlPtr->displayLineNo + 2);
dispLineInfo = realloc(dispLineInfo, TEXT_DISPLINEINFO_SIZE(size));
DEBUG(memset(dispLineInfo->entry + dlPtr->displayLineNo + 1, 0xff,
(size - dlPtr->displayLineNo - 1)*sizeof(dispLineInfo->entry[0])));
pixelInfo->dispLineInfo = dispLineInfo;
}
dispLineInfo->numDispLines = dlPtr->displayLineNo + 1;
dispLineEntry = dispLineInfo->entry + dlPtr->displayLineNo;
(dispLineEntry + 1)->byteOffset = data->byteOffset + dlPtr->byteCount;
(dispLineEntry + 1)->pixels = oldNumDispLines;
dispLineEntry->height = dlPtr->height;
dispLineEntry->pixels = (dispLineEntry - 1)->pixels + dlPtr->height;
dispLineEntry->byteOffset = data->byteOffset;
dispLineEntry->hyphenRule = hyphenRule;
} else if (!finished) {
LayoutSetupDispLineInfo(pixelInfo);
dispLineInfo = pixelInfo->dispLineInfo;
dispLineInfo->entry[0].height = dlPtr->height;
dispLineInfo->entry[0].pixels = dlPtr->height;
dispLineInfo->entry[0].byteOffset = data->byteOffset;
dispLineInfo->entry[0].hyphenRule = hyphenRule;
dispLineInfo->entry[1].byteOffset = data->byteOffset + dlPtr->byteCount;
}
assert(finished || dispLineInfo);
if (finished) {
TkTextLine *nextLogicalLinePtr;
unsigned lineHeight, mergedLines, lineNo, numDispLines, i;
if (dlPtr->displayLineNo > 0) {
lineHeight = dispLineInfo->entry[dispLineInfo->numDispLines - 1].pixels;
numDispLines = dispLineInfo->numDispLines;
} else {
lineHeight = dlPtr->height;
numDispLines = lineHeight > 0;
}
nextLogicalLinePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
mergedLines = TkBTreeCountLines(textPtr->sharedTextPtr->tree, linePtr, nextLogicalLinePtr);
if (mergedLines > 0) {
mergedLines -= 1;
}
if (pixelInfo->height != lineHeight || mergedLines > 0 || numDispLines != oldNumDispLines) {
TkBTreeAdjustPixelHeight(textPtr, linePtr, lineHeight, mergedLines, numDispLines);
}
if (dispLineInfo && dlPtr->displayLineNo == 0) {
free(dispLineInfo);
DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
pixelInfo->dispLineInfo = NULL;
}
textPtr->dInfoPtr->lineMetricUpdateCounter += 1;
pixelInfo->epoch = epoch;
lineNo = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
for (i = 0; i < mergedLines; ++i) {
pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr = linePtr->nextPtr);
pixelInfo->epoch = epoch;
if (pixelInfo->dispLineInfo) {
free(pixelInfo->dispLineInfo);
DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
pixelInfo->dispLineInfo = NULL;
}
}
TkRangeListRemove(textPtr->dInfoPtr->lineMetricUpdateRanges, lineNo, lineNo + mergedLines);
} else {
pixelInfo->epoch = epoch | PARTIAL_COMPUTED_BIT;
}
}
static unsigned
LayoutComputeBreakLocations(
LayoutData *data)
{
unsigned totalSize = 0;
TkText *textPtr = data->textPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextSegment *segPtr = data->logicalLinePtr->segPtr;
bool useUniBreak = data->textPtr->useUniBreak;
char const *lang = useUniBreak ? textPtr->lang : NULL;
char const *nextLang = NULL;
unsigned capacity = dInfoPtr->strBufferSize;
char *str = dInfoPtr->strBuffer;
char *brks = textPtr->brksBuffer;
while (segPtr) {
unsigned size = 0;
unsigned newTotalSize;
for ( ; segPtr; segPtr = segPtr->nextPtr) {
switch ((int) segPtr->typePtr->group) {
case SEG_GROUP_CHAR: {
unsigned newSize;
if (useUniBreak) {
const char *myLang = TkBTreeGetLang(textPtr, segPtr);
if (myLang[0] != lang[0] || myLang[1] != lang[1]) {
nextLang = myLang;
break;
}
}
if ((newSize = size + segPtr->size) >= capacity) {
capacity = MAX(2*capacity, newSize + 1);
str = realloc(str, newSize);
}
memcpy(str + size, segPtr->body.chars, segPtr->size);
size = newSize;
break;
}
case SEG_GROUP_HYPHEN:
if (useUniBreak) {
const char *myLang = TkBTreeGetLang(textPtr, segPtr);
if (myLang[0] != lang[0] || myLang[1] != lang[1]) {
nextLang = myLang;
break;
}
}
if (size + 1 >= capacity) {
assert(2*capacity > size + 1);
str = realloc(str, capacity *= 2);
}
str[size++] = '\t';
break;
case SEG_GROUP_IMAGE:
case SEG_GROUP_WINDOW:
if (size + 1 >= capacity) {
assert(2*capacity > size + 1);
str = realloc(str, capacity *= 2);
}
str[size++] = '\t';
break;
case SEG_GROUP_BRANCH:
segPtr = segPtr->body.branch.nextPtr;
break;
}
}
if (size > 0) {
newTotalSize = totalSize + size;
if (newTotalSize > textPtr->brksBufferSize) {
textPtr->brksBufferSize = MAX(newTotalSize, textPtr->brksBufferSize + 512);
textPtr->brksBuffer = realloc(textPtr->brksBuffer, textPtr->brksBufferSize + 1);
brks = textPtr->brksBuffer;
}
str[size] = '\0';
TkTextComputeBreakLocations(data->textPtr->interp, str, size,
lang ? (*lang ? lang : "en") : NULL, brks + totalSize);
totalSize = newTotalSize;
}
lang = nextLang;
}
dInfoPtr->strBuffer = str;
dInfoPtr->strBufferSize = capacity;
return totalSize;
}
static void
LayoutLookAheadChars(
TkTextDispChunk *chunkPtr,
const char *str,
unsigned numChars,
char *buf)
{
TkTextSegment *segPtr = ((CharInfo *) chunkPtr->clientData)->segPtr;
for ( ; numChars > 0; --numChars) {
if (!*str) {
segPtr = LayoutGetNextSegment(segPtr);
if (!segPtr) {
memset(buf, '\0', numChars);
return;
}
str = segPtr->body.chars;
}
*buf++ = *str++;
}
}
static void
LayoutApplyHyphenRules(
LayoutData *data,
TkTextDispChunk *prevCharChunkPtr,
TkTextDispChunk *hyphenChunkPtr,
TkTextDispChunk *nextCharChunkPtr)
{
TkTextSegment *hyphenPtr = hyphenChunkPtr->clientData;
const StyleValues *sValPtr = hyphenChunkPtr->stylePtr->sValuePtr;
int hyphenRules = sValPtr->hyphenRules & hyphenChunkPtr->hyphenRules;
data->increaseNumBytes = 0;
data->decreaseNumBytes = 0;
SetupHyphenChars(hyphenPtr, 0);
hyphenRules = FilterHyphenRules(hyphenRules, sValPtr->lang);
if (hyphenRules) {
const CharInfo *prevCiPtr;
const CharInfo *nextCiPtr;
const char *prevCharPtr;
const char *nextCharPtr;
unsigned char prevChar;
unsigned char nextChar;
char lookAhead[3];
if (hyphenRules & (1 << TK_TEXT_HYPHEN_REPEAT)) {
data->increaseNumBytes = -1;
data->hyphenRule = TK_TEXT_HYPHEN_REPEAT;
return;
}
if (!IsCharChunk(prevCharChunkPtr)) {
return;
}
while ((prevCiPtr = prevCharChunkPtr->clientData)->numBytes == 0) {
if (!(prevCharChunkPtr = prevCharChunkPtr->prevCharChunkPtr)
|| !IsCharChunk(prevCharChunkPtr)) {
return;
}
}
prevCharPtr = prevCiPtr->u.chars + prevCiPtr->baseOffset + prevCiPtr->numBytes - 1;
if (UCHAR(prevCharPtr[0]) < 0x80) {
prevChar = UCHAR(prevCharPtr[0]);
} else if (prevCiPtr->numBytes > 1 && UCHAR(prevCharPtr[-1]) == 0xc3) {
prevChar = ConvertC3Next(prevCharPtr[1]);
} else {
return;
}
if (hyphenRules & (1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL)) {
if (IsVowel(prevChar)) {
char secondPrevChar = '\0';
if (prevCiPtr->numBytes > 1) {
secondPrevChar = prevCharPtr[-1];
} else {
const TkTextDispChunk *chunkPtr = prevCharChunkPtr->prevCharChunkPtr;
if (chunkPtr && IsCharChunk(chunkPtr)) {
const TkTextSegment *segPtr = CHAR_CHUNK_GET_SEGMENT(chunkPtr);
secondPrevChar = segPtr->body.chars[segPtr->size - 1];
}
}
if (prevChar == secondPrevChar) {
if (prevChar == 'e') {
char *s = hyphenPtr->body.chars;
data->decreaseNumBytes = 2;
s[0] = 0xc3; s[1] = 0xa9;
SetupHyphenChars(hyphenPtr, 2);
} else {
data->decreaseNumBytes = 1;
}
data->hyphenRule = TK_TEXT_HYPHEN_DOUBLE_VOWEL;
return;
}
}
}
if (!IsCharChunk(nextCharChunkPtr)) {
return;
}
if ((nextCiPtr = nextCharChunkPtr->clientData)->numBytes == 0) {
TkTextSegment *segPtr = LayoutGetNextSegment(nextCharChunkPtr->clientData);
if (!segPtr) {
return;
}
nextCharPtr = segPtr->body.chars;
} else {
nextCharPtr = nextCiPtr->u.chars + nextCiPtr->baseOffset;
}
if (UCHAR(nextCharPtr[0]) < 0x80) {
nextChar = UCHAR(nextCharPtr[0]);
} else if (UCHAR(nextCharPtr[0]) == 0xc3) {
nextChar = ConvertC3Next(nextCharPtr[1]);
} else {
return;
}
if (hyphenRules & (1 << TK_TEXT_HYPHEN_CK)) {
if (prevChar == UCHAR('c') && nextChar == UCHAR('k')) {
data->decreaseNumBytes = 1;
hyphenPtr->body.chars[0] = 'k';
SetupHyphenChars(hyphenPtr, 1);
data->hyphenRule = TK_TEXT_HYPHEN_CK;
return;
}
}
if (hyphenRules & (1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH)) {
if (prevChar == nextChar) {
LayoutLookAheadChars(nextCharChunkPtr, nextCharPtr + 1, 1, lookAhead);
if (lookAhead[0] && IsDoubleDigraph(prevChar, lookAhead[0])) {
hyphenPtr->body.chars[0] = lookAhead[0];
SetupHyphenChars(hyphenPtr, 1);
data->hyphenRule = TK_TEXT_HYPHEN_DOUBLE_DIGRAPH;
return;
}
}
}
if (hyphenRules & (1 << TK_TEXT_HYPHEN_TREMA)) {
if (IsVowel(prevChar) && IsUmlaut(nextChar)) {
data->hyphenRule = TK_TEXT_HYPHEN_TREMA;
return;
}
}
if (hyphenRules & (1 << TK_TEXT_HYPHEN_GEMINATION)) {
if (tolower(nextChar) == 'l') {
LayoutLookAheadChars(nextCharChunkPtr, nextCharPtr + 1, 3, lookAhead);
if (UCHAR(lookAhead[0]) == 0xc2
&& UCHAR(lookAhead[1]) == 0xb7
&& lookAhead[2] == nextChar) {
data->increaseNumBytes = 3;
hyphenPtr->body.chars[0] = nextChar;
SetupHyphenChars(hyphenPtr, 1);
data->hyphenRule = TK_TEXT_HYPHEN_GEMINATION;
return;
}
}
}
}
}
static unsigned
LayoutMakeCharInfo(
LayoutData *data,
TkTextSegment *segPtr,
int byteOffset,
int maxBytes)
{
char const *p = segPtr->body.chars + byteOffset;
CharInfo *ciPtr = AllocCharInfo(data->textPtr);
assert(data->chunkPtr);
assert(!data->chunkPtr->clientData);
if (data->trimSpaces && maxBytes > 0 && p[maxBytes - 1] == ' ') {
while (maxBytes > 1 && p[maxBytes - 2] == ' ') {
maxBytes -= 1;
}
}
#if TK_LAYOUT_WITH_BASE_CHUNKS
if (data->baseChunkPtr
&& (!IsSameFGStyle(data->baseChunkPtr->stylePtr, data->chunkPtr->stylePtr)
|| (data->lastCharChunkPtr && data->lastCharChunkPtr->numSpaces > 0))) {
data->baseChunkPtr = NULL;
}
if (!data->baseChunkPtr) {
data->baseChunkPtr = data->chunkPtr;
Tcl_DStringInit(&data->chunkPtr->baseChars);
DEBUG_ALLOC(tkTextCountNewBaseChars++);
}
data->chunkPtr->baseChunkPtr = data->baseChunkPtr;
ciPtr->baseOffset = Tcl_DStringLength(&data->baseChunkPtr->baseChars);
ciPtr->u.chars = Tcl_DStringAppend(&data->baseChunkPtr->baseChars, p, maxBytes);
#else
ciPtr->baseOffset = 0;
ciPtr->u.chars = p;
#endif
segPtr->refCount += 1;
ciPtr->segPtr = segPtr;
ciPtr->numBytes = maxBytes;
data->chunkPtr->clientData = ciPtr;
return maxBytes;
}
static void
LayoutFinalizeCharInfo(
LayoutData *data,
bool gotTab)
{
CharInfo *ciPtr = data->chunkPtr->clientData;
assert(data->trimSpaces ?
data->chunkPtr->numBytes >= ciPtr->numBytes :
data->chunkPtr->numBytes == ciPtr->numBytes);
if (ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 1] == '\n') {
ciPtr->numBytes -= 1;
}
#if TK_LAYOUT_WITH_BASE_CHUNKS
assert(data->chunkPtr->baseChunkPtr);
Tcl_DStringSetLength(&data->baseChunkPtr->baseChars, ciPtr->baseOffset + ciPtr->numBytes);
data->baseChunkPtr->baseWidth =
data->chunkPtr->width + (data->chunkPtr->x - data->baseChunkPtr->x);
if (gotTab) {
data->baseChunkPtr = NULL;
}
#endif
}
static void
LayoutUndisplay(
LayoutData *data,
TkTextDispChunk *chunkPtr)
{
assert(chunkPtr->layoutProcs);
if (chunkPtr->layoutProcs->undisplayProc) {
chunkPtr->layoutProcs->undisplayProc(data->textPtr, chunkPtr);
}
#if TK_LAYOUT_WITH_BASE_CHUNKS
if (chunkPtr == data->baseChunkPtr) {
data->baseChunkPtr = NULL;
}
#endif
}
static void
LayoutReleaseChunk(
TkText *textPtr,
TkTextDispChunk *chunkPtr)
{
if (chunkPtr->layoutProcs) {
if (chunkPtr->layoutProcs->type == TEXT_DISP_IMAGE) {
textPtr->dInfoPtr->countImages -= 1;
} else if (chunkPtr->layoutProcs->type == TEXT_DISP_WINDOW) {
textPtr->dInfoPtr->countWindows -= 1;
}
}
FreeStyle(textPtr, chunkPtr->stylePtr);
}
static void
LayoutFreeChunk(
LayoutData *data)
{
TextDInfo *dInfoPtr = data->textPtr->dInfoPtr;
TkTextDispChunk *chunkPtr = data->chunkPtr;
assert(chunkPtr);
assert(data->lastChunkPtr != chunkPtr);
assert(data->lastCharChunkPtr != chunkPtr);
assert(!chunkPtr->sectionPtr);
if (chunkPtr->layoutProcs) {
LayoutUndisplay(data, chunkPtr);
}
LayoutReleaseChunk(data->textPtr, chunkPtr);
DEBUG(chunkPtr->stylePtr = NULL);
assert(!chunkPtr->clientData);
data->numBytesSoFar -= chunkPtr->numBytes;
chunkPtr->nextPtr = dInfoPtr->chunkPoolPtr;
dInfoPtr->chunkPoolPtr = chunkPtr;
dInfoPtr->chunkPoolPtr->prevPtr = NULL;
data->chunkPtr = NULL;
assert(data->countChunks > 0);
data->countChunks -= 1;
}
static void
LayoutDoWidthAdjustmentForContextDrawing(
LayoutData *data)
{
#if TK_LAYOUT_WITH_BASE_CHUNKS && TK_DRAW_IN_CONTEXT
TkTextDispChunk *chunkPtr = data->chunkPtr;
if (chunkPtr->prevPtr) {
chunkPtr->x += chunkPtr->prevPtr->xAdjustment;
}
if (IsCharChunk(chunkPtr)) {
int newWidth;
CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, 0, -1, 0, &newWidth);
chunkPtr->xAdjustment = newWidth - chunkPtr->width;
chunkPtr->width = newWidth;
}
#endif
}
static void
LayoutFinalizeChunk(
LayoutData *data)
{
const TkTextDispChunkProcs *layoutProcs;
if (!data->chunkPtr) {
return;
}
layoutProcs = data->chunkPtr->layoutProcs;
if (!layoutProcs) {
assert(data->chunkPtr->numBytes == 0);
assert(!data->chunkPtr->clientData);
LayoutFreeChunk(data);
return;
}
if (layoutProcs->type & TEXT_DISP_CONTENT) {
data->lastCharChunkPtr = data->chunkPtr;
if (!data->firstCharChunkPtr) {
data->firstCharChunkPtr = data->chunkPtr;
}
if (layoutProcs->type & TEXT_DISP_TEXT) {
LayoutDoWidthAdjustmentForContextDrawing(data);
}
}
if (data->chunkPtr->breakIndex > 0) {
data->breakChunkPtr = data->chunkPtr;
}
if (!data->firstChunkPtr) {
assert(!data->lastChunkPtr);
data->firstChunkPtr = data->chunkPtr;
} else {
assert(data->lastChunkPtr);
data->lastChunkPtr->nextPtr = data->chunkPtr;
}
data->lastChunkPtr = data->chunkPtr;
data->dispLineOffset += data->chunkPtr->numBytes;
data->chunkPtr = NULL;
}
static TkTextDispChunkSection *
LayoutNewSection(
TextDInfo *dInfoPtr)
{
TkTextDispChunkSection *sectionPtr = dInfoPtr->sectionPoolPtr;
if (sectionPtr) {
dInfoPtr->sectionPoolPtr = dInfoPtr->sectionPoolPtr->nextPtr;
} else {
DEBUG_ALLOC(tkTextCountNewSection++);
sectionPtr = malloc(sizeof(TkTextDispChunkSection));
}
memset(sectionPtr, 0, sizeof(TkTextDispChunkSection));
return sectionPtr;
}
static void
LayoutMakeNewChunk(
LayoutData *data)
{
TkTextDispChunk *newChunkPtr;
TextDInfo *dInfoPtr = data->textPtr->dInfoPtr;
LayoutFinalizeChunk(data);
if ((newChunkPtr = dInfoPtr->chunkPoolPtr)) {
dInfoPtr->chunkPoolPtr = newChunkPtr->nextPtr;
} else {
newChunkPtr = malloc(sizeof(TkTextDispChunk));
DEBUG_ALLOC(tkTextCountNewChunk++);
}
memset(newChunkPtr, 0, sizeof(TkTextDispChunk));
newChunkPtr->prevPtr = data->lastChunkPtr;
newChunkPtr->prevCharChunkPtr = data->lastCharChunkPtr;
newChunkPtr->stylePtr = GetStyle(data->textPtr, NULL);
newChunkPtr->x = data->x;
newChunkPtr->byteOffset = data->dispLineOffset;
data->chunkPtr = newChunkPtr;
data->countChunks += 1;
}
static void
LayoutSkipBytes(
LayoutData *data,
DLine *dlPtr,
const TkTextIndex *indexPtr1,
const TkTextIndex *indexPtr2)
{
LayoutMakeNewChunk(data);
data->chunkPtr->layoutProcs = &layoutElideProcs;
data->chunkPtr->numBytes = TkTextIndexCountBytes(indexPtr1, indexPtr2);
}
static void
LayoutSetupChunk(
LayoutData *data,
TkTextSegment *segPtr)
{
TkTextDispChunk *chunkPtr = data->chunkPtr;
TkText *textPtr = data->textPtr;
TextStyle *stylePtr;
assert(segPtr->tagInfoPtr);
assert(chunkPtr->stylePtr == textPtr->dInfoPtr->defaultStyle);
assert(chunkPtr->stylePtr->refCount > 1);
chunkPtr->stylePtr->refCount -= 1;
chunkPtr->stylePtr = stylePtr = GetStyle(textPtr, segPtr);
if (data->wrapMode == TEXT_WRAPMODE_CODEPOINT) {
const TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, data->logicalLinePtr);
if (!data->brks) {
Tcl_HashEntry *hPtr;
BreakInfo *breakInfo;
int new;
hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->breakInfoTable,
(void *) data->logicalLinePtr, &new);
if (new) {
breakInfo = malloc(sizeof(BreakInfo));
breakInfo->refCount = 1;
breakInfo->brks = NULL;
data->logicalLinePtr->changed = false;
Tcl_SetHashValue(hPtr, breakInfo);
DEBUG_ALLOC(tkTextCountNewBreakInfo++);
} else {
breakInfo = Tcl_GetHashValue(hPtr);
breakInfo->refCount += 1;
if (data->logicalLinePtr->changed) {
new = true;
data->logicalLinePtr->changed = false;
}
}
if (new) {
unsigned brksSize;
brksSize = LayoutComputeBreakLocations(data);
breakInfo->brks = realloc(breakInfo->brks, brksSize);
memcpy(breakInfo->brks, textPtr->brksBuffer, brksSize);
DEBUG(stats.breakInfo += 1);
}
data->breakInfo = breakInfo;
data->brks = breakInfo->brks;
}
if (segPtr->sectionPtr) {
chunkPtr->brks = data->brks;
if (data->displayLineNo > 0) {
assert(pixelInfo->dispLineInfo);
chunkPtr->brks += pixelInfo->dispLineInfo->entry[data->displayLineNo].byteOffset;
} else {
chunkPtr->brks += data->byteOffset;
}
chunkPtr->brks += data->dispLineOffset;
} else {
assert(chunkPtr->numBytes <= sizeof(doNotBreakAtAll));
chunkPtr->brks = doNotBreakAtAll;
}
}
if (data->numBytesSoFar == 0) {
const TextDInfo *dInfoPtr = textPtr->dInfoPtr;
const StyleValues *sValuePtr = stylePtr->sValuePtr;
data->tabArrayPtr = sValuePtr->tabArrayPtr;
data->tabStyle = sValuePtr->tabStyle;
data->justify = sValuePtr->justify;
data->rMargin = sValuePtr->rMargin;
data->wrapMode = sValuePtr->wrapMode;
data->x = data->paragraphStart ? sValuePtr->lMargin1 : sValuePtr->lMargin2;
data->width = dInfoPtr->maxX - dInfoPtr->x - data->rMargin;
data->maxX = data->wrapMode == TEXT_WRAPMODE_NONE ? -1 : MAX(data->width, data->x);
chunkPtr->x = data->x;
if (data->cursorChunkPtr) {
data->cursorChunkPtr->x = data->x;
}
}
}
static bool
LayoutChars(
LayoutData *data,
TkTextSegment *segPtr,
int size,
int byteOffset)
{
const char *base = segPtr->body.chars + byteOffset;
TkTextDispChunk *chunkPtr;
bool gotTab = false;
bool finished = true;
unsigned maxBytes;
unsigned numBytes;
assert(size - byteOffset > 0);
assert(byteOffset < size);
assert(segPtr->typePtr->layoutProc);
LayoutMakeNewChunk(data);
LayoutSetupChunk(data, segPtr);
chunkPtr = data->chunkPtr;
maxBytes = size - byteOffset;
if (data->textPtr->showEndOfLine
&& base[maxBytes - 1] == '\n'
&& segPtr->sectionPtr->linePtr->nextPtr != TkBTreeGetLastLine(data->textPtr)) {
maxBytes -= 1;
}
if (maxBytes == 0) {
segPtr = data->textPtr->dInfoPtr->endOfLineSegPtr;
base = segPtr->body.chars;
maxBytes = segPtr->size;
byteOffset = 0;
} else if (segPtr->typePtr != &tkTextHyphenType
&& segPtr->sectionPtr) {
if (data->wrapMode == TEXT_WRAPMODE_CODEPOINT) {
const char *brks = chunkPtr->brks;
unsigned i;
assert(brks);
for (i = 1; i < maxBytes; ++i) {
if (brks[i] == LINEBREAK_MUSTBREAK) {
if (i < maxBytes - 2 && base[i] != '\n') {
maxBytes = i + 1;
}
break;
}
}
}
if (data->textPtr->hyphenate && finished) {
const char *p = base;
if (IsConsonant(*p)
&& data->lastCharChunkPtr
&& data->lastCharChunkPtr->prevCharChunkPtr
&& data->lastChunkPtr
&& data->lastChunkPtr->layoutProcs
&& data->lastChunkPtr->layoutProcs->type == TEXT_DISP_HYPHEN
&& *p == GetLastCharInChunk(data->lastCharChunkPtr->prevCharChunkPtr)
&& *p == GetSecondLastCharInChunk(data->lastCharChunkPtr->prevCharChunkPtr)) {
const char *nextCharPtr;
if (maxBytes > 1) {
nextCharPtr = p + 1;
} else {
const TkTextSegment *nextCharSegPtr = LayoutGetNextSegment(segPtr);
nextCharPtr = nextCharSegPtr ? nextCharSegPtr->body.chars : NULL;
}
if (nextCharPtr && (nextCharPtr[0] == 'j' || IsUmlautOrVowel(nextCharPtr))) {
const StyleValues *sValPtr = data->lastChunkPtr->stylePtr->sValuePtr;
int hyphenRules = FilterHyphenRules(sValPtr->hyphenRules, sValPtr->lang);
if (hyphenRules & (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT)) {
byteOffset += 1;
base += 1;
maxBytes -= 1;
chunkPtr->skipFirstChar = true;
}
}
}
}
if (data->trimSpaces) {
int i;
for (i = 0; i < maxBytes; ++i) {
if (base[i] == ' ' && base[i + 1] == ' ') {
while (base[i] == ' ') {
++i;
}
maxBytes = i;
data->skipSpaces = true;
break;
}
}
}
if (data->justify == TK_TEXT_JUSTIFY_LEFT) {
const char *p = base;
int i;
for (i = 0; i < maxBytes; ++i, ++p) {
if (*p == '\t') {
maxBytes = i + 1;
gotTab = true;
break;
}
}
} else if (data->justify == TK_TEXT_JUSTIFY_FULL) {
const char *p = base;
const char *e = p + maxBytes;
for ( ; p < e && !IsExpandableSpace(p); ++p) {
if (*p == '\t') {
chunkPtr->numSpaces = 0;
maxBytes = p - base + 1;
gotTab = true;
break;
}
}
if (!gotTab && p < e) {
assert(IsExpandableSpace(p));
do {
chunkPtr->numSpaces += 1;
if (*p == '\t'
&& (!data->tabArrayPtr || data->tabIndex < data->tabArrayPtr->numTabs)) {
chunkPtr->numSpaces = 0;
gotTab = true;
p += 1;
break;
}
p = Tcl_UtfNext(p);
} while (IsExpandableSpace(p));
maxBytes = p - base;
}
}
}
if (maxBytes == 0) {
assert(size == 1);
assert(chunkPtr->skipFirstChar);
data->chunkPtr->layoutProcs = &layoutElideProcs;
data->chunkPtr->numBytes = 1;
return true;
}
numBytes = LayoutMakeCharInfo(data, segPtr, byteOffset, maxBytes);
if (segPtr->typePtr->layoutProc(&data->index, segPtr, byteOffset,
data->maxX - data->tabSize, numBytes, data->numBytesSoFar == 0,
data->wrapMode, data->textPtr->spaceMode, chunkPtr) == 0) {
chunkPtr->numSpaces = 0;
return false;
}
if (numBytes == chunkPtr->numBytes) {
chunkPtr->numBytes = maxBytes;
assert(maxBytes > 0);
if (data->trimSpaces && base[maxBytes - 1] == ' ') {
data->skipSpaces = true;
}
}
assert(chunkPtr->numBytes + chunkPtr->skipFirstChar > 0);
LayoutFinalizeCharInfo(data, gotTab);
data->x += chunkPtr->width;
if (segPtr == data->textPtr->dInfoPtr->endOfLineSegPtr) {
chunkPtr->numBytes = (chunkPtr->numBytes == maxBytes) ? 1 : 0;
chunkPtr->breakIndex = chunkPtr->numBytes;
maxBytes = 1;
} else {
chunkPtr->numBytes += chunkPtr->skipFirstChar;
}
data->numBytesSoFar += chunkPtr->numBytes;
data->numSpaces += chunkPtr->numSpaces;
if (chunkPtr->numBytes != maxBytes + chunkPtr->skipFirstChar) {
return false;
}
if (gotTab) {
if (data->tabIndex >= 0) {
data->lastChunkPtr->nextPtr = data->chunkPtr;
AdjustForTab(data);
data->lastChunkPtr->nextPtr = NULL;
data->x = chunkPtr->x + chunkPtr->width;
}
data->tabChunkPtr = chunkPtr;
ComputeSizeOfTab(data);
if (data->maxX >= 0 && data->tabSize >= data->maxX - data->x) {
return false;
}
}
return finished;
}
static bool
LayoutHyphen(
LayoutData *data,
TkTextSegment *segPtr)
{
bool rc;
assert(segPtr->sectionPtr);
if (data->textPtr->hyphenate) {
LayoutMakeNewChunk(data);
LayoutSetupChunk(data, segPtr);
data->numBytesSoFar += segPtr->size;
segPtr->body.hyphen.textSize = 0;
data->chunkPtr->layoutProcs = &layoutHyphenProcs;
data->chunkPtr->clientData = segPtr;
data->chunkPtr->breakIndex = -1;
data->chunkPtr->numBytes = segPtr->size;
data->chunkPtr->hyphenRules = segPtr->body.hyphen.rules;
segPtr->refCount += 1;
rc = true;
} else {
SetupHyphenChars(segPtr, 0);
rc = LayoutChars(data, segPtr, segPtr->body.hyphen.textSize, 0);
data->chunkPtr->numBytes = MIN(1, data->chunkPtr->numBytes);
}
data->chunkPtr->breakIndex = data->chunkPtr->numBytes;
return rc;
}
static bool
LayoutEmbedded(
LayoutData *data,
TkTextSegment *segPtr)
{
assert(segPtr->typePtr->layoutProc);
LayoutMakeNewChunk(data);
if (segPtr->typePtr->layoutProc(&data->index, segPtr, 0, data->maxX - data->tabSize, 0,
data->numBytesSoFar == 0, data->wrapMode, data->textPtr->spaceMode, data->chunkPtr) != 1) {
return false;
}
#if TK_LAYOUT_WITH_BASE_CHUNKS
data->baseChunkPtr = NULL;
#endif
LayoutSetupChunk(data, segPtr);
data->numBytesSoFar += data->chunkPtr->numBytes;
data->x += data->chunkPtr->width;
if (segPtr->typePtr->group == SEG_GROUP_IMAGE) {
data->textPtr->dInfoPtr->countImages += 1;
} else {
data->textPtr->dInfoPtr->countWindows += 1;
}
return true;
}
static bool
LayoutMark(
LayoutData *data,
TkTextSegment *segPtr)
{
assert(segPtr->typePtr->layoutProc);
if (segPtr != data->textPtr->insertMarkPtr) {
return false;
}
LayoutMakeNewChunk(data);
segPtr->typePtr->layoutProc(&data->index, segPtr, 0, data->maxX - data->tabSize, 0,
data->numBytesSoFar == 0, data->wrapMode, data->textPtr->spaceMode, data->chunkPtr);
return true;
}
static bool
LayoutLogicalLine(
LayoutData *data,
DLine *dlPtr)
{
TkTextSegment *segPtr, *endPtr;
int byteIndex, byteOffset;
assert(!TkTextIsElided(&data->index));
byteIndex = TkTextIndexGetByteIndex(&data->index);
if (data->textPtr->hyphenate && data->displayLineNo > 0) {
const TkTextDispLineInfo *dispLineInfo;
int byteOffset;
int hyphenRule;
segPtr = TkTextIndexGetContentSegment(&data->index, &byteOffset);
dispLineInfo = TkBTreeLinePixelInfo(data->textPtr, data->logicalLinePtr)->dispLineInfo;
assert(dispLineInfo);
hyphenRule = dispLineInfo->entry[data->displayLineNo - 1].hyphenRule;
switch (hyphenRule) {
case TK_TEXT_HYPHEN_REPEAT:
case TK_TEXT_HYPHEN_TREMA:
case TK_TEXT_HYPHEN_DOUBLE_DIGRAPH: {
int numBytes = 0;
TkTextSegment *nextCharSegPtr;
char buf[1];
bool cont;
switch (hyphenRule) {
case TK_TEXT_HYPHEN_REPEAT:
buf[0] = '-';
numBytes = 1;
break;
case TK_TEXT_HYPHEN_TREMA:
assert(UCHAR(segPtr->body.chars[byteOffset]) == 0xc3);
buf[0] = UmlautToVowel(ConvertC3Next(segPtr->body.chars[byteOffset + 1]));
numBytes = 2;
break;
case TK_TEXT_HYPHEN_DOUBLE_DIGRAPH:
buf[0] = segPtr->body.chars[0];
numBytes = 1;
break;
}
nextCharSegPtr = TkBTreeMakeCharSegment(buf, 1, segPtr->tagInfoPtr);
cont = LayoutChars(data, nextCharSegPtr, 1, 0);
TkBTreeFreeSegment(nextCharSegPtr);
data->chunkPtr->numBytes = numBytes;
if (!cont) {
LayoutFinalizeChunk(data);
return false;
}
TkTextIndexForwBytes(data->textPtr, &data->index, data->chunkPtr->numBytes, &data->index);
byteIndex += data->chunkPtr->numBytes;
break;
}
}
}
segPtr = TkTextIndexGetFirstSegment(&data->index, &byteOffset);
endPtr = data->textPtr->endMarker;
if (segPtr->typePtr == &tkTextLinkType) {
segPtr = segPtr->nextPtr;
}
while (true) {
if (segPtr->typePtr == &tkTextCharType) {
if (data->skipSpaces) {
if (segPtr->body.chars[byteOffset] == ' ') {
TkTextIndex index = data->index;
int offset = byteOffset;
while (segPtr->body.chars[byteOffset] == ' ') {
byteOffset += 1;
}
TkTextIndexForwBytes(data->textPtr, &index, byteOffset - offset, &data->index);
LayoutSkipBytes(data, dlPtr, &index, &data->index);
byteIndex = TkTextIndexGetByteIndex(&data->index);
}
data->skipSpaces = false;
}
if (segPtr->size > byteOffset) {
if (!LayoutChars(data, segPtr, segPtr->size, byteOffset)) {
LayoutFinalizeChunk(data);
return false;
}
assert(data->chunkPtr);
byteIndex += data->chunkPtr->numBytes;
if ((byteOffset += data->chunkPtr->numBytes) == segPtr->size) {
segPtr = segPtr->nextPtr;
byteOffset = 0;
}
} else {
assert(segPtr->size == byteOffset);
segPtr = segPtr->nextPtr;
byteOffset = 0;
}
} else {
switch (segPtr->typePtr->group) {
case SEG_GROUP_HYPHEN:
if (!LayoutHyphen(data, segPtr)) {
LayoutFinalizeChunk(data);
return false;
}
byteIndex += segPtr->size;
data->skipSpaces = false;
break;
case SEG_GROUP_IMAGE:
case SEG_GROUP_WINDOW:
if (!LayoutEmbedded(data, segPtr)) {
LayoutFinalizeChunk(data);
return false;
}
byteIndex += segPtr->size;
data->skipSpaces = false;
break;
case SEG_GROUP_MARK:
if (segPtr == endPtr) {
segPtr = segPtr->sectionPtr->linePtr->lastPtr;
LayoutChars(data, segPtr, segPtr->size, segPtr->size - 1);
} else {
if (LayoutMark(data, segPtr)) {
data->cursorChunkPtr = data->chunkPtr;
}
assert(segPtr->size == 0);
}
break;
case SEG_GROUP_BRANCH: {
TkTextIndex index = data->index;
assert(segPtr->typePtr == &tkTextBranchType);
assert(segPtr->size == 0);
TkTextIndexSetSegment(&data->index, segPtr = segPtr->body.branch.nextPtr);
LayoutSkipBytes(data, dlPtr, &index, &data->index);
byteIndex = TkTextIndexGetByteIndex(&data->index);
break;
}
case SEG_GROUP_PROTECT:
case SEG_GROUP_TAG:
case SEG_GROUP_CHAR:
assert(!"unexpected segment type");
break;
}
segPtr = segPtr->nextPtr;
byteOffset = 0;
}
if (!segPtr) {
LayoutFinalizeChunk(data);
return true;
}
TkTextIndexSetPosition(&data->index, byteIndex, segPtr);
}
return false;
}
static void
LayoutDestroyChunks(
LayoutData *data)
{
TkTextDispChunk *chunkPtr = data->lastChunkPtr;
TextDInfo *dInfoPtr;
if (chunkPtr == data->breakChunkPtr) {
return;
}
dInfoPtr = data->textPtr->dInfoPtr;
for ( ; chunkPtr != data->breakChunkPtr; chunkPtr = chunkPtr->prevPtr) {
assert(chunkPtr != data->firstCharChunkPtr);
assert(chunkPtr->layoutProcs);
assert(!chunkPtr->sectionPtr);
data->numSpaces -= chunkPtr->numSpaces;
data->dispLineOffset -= chunkPtr->numBytes;
data->numBytesSoFar -= chunkPtr->numBytes;
data->countChunks -= 1;
if (chunkPtr == data->cursorChunkPtr) {
data->cursorChunkPtr = NULL;
} else if (chunkPtr == data->lastCharChunkPtr) {
data->lastCharChunkPtr = chunkPtr->prevCharChunkPtr;
}
if (chunkPtr->layoutProcs->type == TEXT_DISP_IMAGE) {
dInfoPtr->countImages -= 1;
} else if (chunkPtr->layoutProcs->type == TEXT_DISP_WINDOW) {
dInfoPtr->countWindows -= 1;
}
LayoutUndisplay(data, chunkPtr);
LayoutReleaseChunk(data->textPtr, chunkPtr);
DEBUG(chunkPtr->stylePtr = NULL);
}
data->lastChunkPtr->nextPtr = dInfoPtr->chunkPoolPtr;
dInfoPtr->chunkPoolPtr = data->breakChunkPtr->nextPtr;
dInfoPtr->chunkPoolPtr->prevPtr = NULL;
data->breakChunkPtr->nextPtr = NULL;
data->lastChunkPtr = data->breakChunkPtr;
data->chunkPtr = NULL;
data->x = data->lastChunkPtr->x + data->lastChunkPtr->width;
#if TK_LAYOUT_WITH_BASE_CHUNKS
data->baseChunkPtr = data->breakChunkPtr->baseChunkPtr;
#endif
}
static void
LayoutBreakLine(
LayoutData *data,
const TkTextIndex *indexPtr)
{
if (!data->breakChunkPtr) {
data->breakChunkPtr = data->lastCharChunkPtr;
}
while (IsHyphenChunk(data->breakChunkPtr)) {
TkTextDispChunk *hyphenChunkPtr;
TkTextDispChunk *prevChunkPtr;
TkTextDispChunk *nextChunkPtr;
hyphenChunkPtr = data->breakChunkPtr;
prevChunkPtr = hyphenChunkPtr->prevCharChunkPtr;
nextChunkPtr = LayoutGetNextCharChunk(hyphenChunkPtr);
if (prevChunkPtr && nextChunkPtr) {
TkTextSegment *hyphenSegPtr = hyphenChunkPtr->clientData;
LayoutApplyHyphenRules(data, prevChunkPtr, hyphenChunkPtr, nextChunkPtr);
data->breakChunkPtr = prevChunkPtr;
LayoutDestroyChunks(data);
if (data->decreaseNumBytes > 0) {
TkTextIndex index = *indexPtr;
TkTextSegment *segPtr;
unsigned newNumBytes = 0;
unsigned numBytes;
while (data->decreaseNumBytes >= prevChunkPtr->numBytes
&& prevChunkPtr != data->firstCharChunkPtr) {
data->decreaseNumBytes -= prevChunkPtr->numBytes;
newNumBytes += prevChunkPtr->numBytes;
prevChunkPtr = prevChunkPtr->prevPtr;
}
data->breakChunkPtr = prevChunkPtr;
LayoutDestroyChunks(data);
newNumBytes += prevChunkPtr->numBytes;
if (data->decreaseNumBytes > 0) {
segPtr = CHAR_CHUNK_GET_SEGMENT(prevChunkPtr);
prevChunkPtr->numBytes -= data->decreaseNumBytes;
numBytes = prevChunkPtr->numBytes;
assert(prevChunkPtr->layoutProcs);
LayoutUndisplay(data, prevChunkPtr);
data->chunkPtr = prevChunkPtr;
LayoutMakeCharInfo(data, segPtr, prevChunkPtr->segByteOffset, numBytes);
TkTextIndexForwBytes(data->textPtr, &index, prevChunkPtr->byteOffset, &index);
segPtr->typePtr->layoutProc(&index, segPtr, prevChunkPtr->segByteOffset,
data->maxX, numBytes, 0, data->wrapMode, data->textPtr->spaceMode,
prevChunkPtr);
LayoutFinalizeCharInfo(data, false);
if (prevChunkPtr->numBytes != numBytes && prevChunkPtr != data->firstCharChunkPtr) {
hyphenSegPtr = NULL;
}
}
prevChunkPtr->numBytes = newNumBytes;
data->chunkPtr = NULL;
}
if (hyphenSegPtr) {
int maxX = data->maxX;
bool fits;
data->x = prevChunkPtr->x + prevChunkPtr->width;
if (prevChunkPtr == data->firstCharChunkPtr && prevChunkPtr->breakIndex <= 0) {
data->maxX = INT_MAX;
}
fits = LayoutChars(data, hyphenSegPtr, hyphenSegPtr->body.hyphen.textSize, 0);
assert(!fits || data->chunkPtr->numBytes == hyphenSegPtr->body.hyphen.textSize);
hyphenChunkPtr = data->chunkPtr;
data->maxX = maxX;
if (fits) {
LayoutFinalizeChunk(data);
hyphenChunkPtr->numBytes = 1 + data->increaseNumBytes;
return;
}
LayoutFreeChunk(data);
data->hyphenRule = 0;
}
}
if (IsHyphenChunk(data->breakChunkPtr)) {
if (!(data->breakChunkPtr = data->breakChunkPtr->prevPtr)) {
return;
}
}
if (data->breakChunkPtr->breakIndex <= 0) {
do {
if (!(data->breakChunkPtr = data->breakChunkPtr->prevPtr)) {
return;
}
} while (data->breakChunkPtr->breakIndex <= 0 && !IsHyphenChunk(data->breakChunkPtr));
}
data->chunkPtr = NULL;
}
if (data->breakChunkPtr
&& (data->lastChunkPtr != data->breakChunkPtr
|| (data->lastChunkPtr->breakIndex > 0
&& data->lastChunkPtr->breakIndex != data->lastChunkPtr->numBytes))) {
unsigned addNumBytes = 0;
LayoutDestroyChunks(data);
if (data->breakChunkPtr->breakIndex > 0 && data->breakChunkPtr->numSpaces > 0) {
const TkTextDispChunk *breakChunkPtr = data->breakChunkPtr;
const CharInfo *ciPtr = breakChunkPtr->clientData;
const char *p = ciPtr->u.chars + ciPtr->baseOffset + breakChunkPtr->breakIndex;
const char *q = Tcl_UtfPrev(p, ciPtr->u.chars + ciPtr->baseOffset);
if (IsExpandableSpace(q)
&& !(breakChunkPtr->wrappedAtSpace
&& breakChunkPtr->breakIndex == breakChunkPtr->numBytes)) {
addNumBytes = p - q;
data->breakChunkPtr->breakIndex -= addNumBytes;
data->breakChunkPtr->numSpaces -= 1;
data->numSpaces -= 1;
}
}
if (data->breakChunkPtr->breakIndex != data->breakChunkPtr->numBytes) {
TkTextSegment *segPtr;
TkTextDispChunk *chunkPtr = data->breakChunkPtr;
TkTextIndex index = *indexPtr;
LayoutUndisplay(data, chunkPtr);
data->chunkPtr = chunkPtr;
TkTextIndexForwBytes(data->textPtr, &index, chunkPtr->byteOffset, &index);
segPtr = TkTextIndexGetContentSegment(&index, NULL);
LayoutMakeCharInfo(data, segPtr, chunkPtr->segByteOffset, data->breakChunkPtr->breakIndex);
segPtr->typePtr->layoutProc(&index, segPtr, chunkPtr->segByteOffset, data->maxX,
data->breakChunkPtr->breakIndex, 0, data->wrapMode, data->textPtr->spaceMode,
chunkPtr);
LayoutFinalizeCharInfo(data, false);
LayoutDoWidthAdjustmentForContextDrawing(data);
chunkPtr->numBytes += addNumBytes;
if (chunkPtr->skipFirstChar) {
chunkPtr->numBytes += 1;
}
}
}
if (data->lastChunkPtr->numBytes == 0) {
data->breakChunkPtr = data->breakChunkPtr->prevPtr;
assert(data->breakChunkPtr);
while (data->breakChunkPtr->numBytes == 0) {
data->breakChunkPtr = data->breakChunkPtr->prevPtr;
assert(data->breakChunkPtr);
}
LayoutDestroyChunks(data);
}
}
static void
LayoutFullJustification(
LayoutData *data,
DLine *dlPtr)
{
TkTextDispChunk *chunkPtr;
TkTextDispChunk *nextChunkPtr;
unsigned numSpaces;
int remainingPixels;
int shiftX;
numSpaces = data->numSpaces;
remainingPixels = data->maxX - dlPtr->length;
if (numSpaces == 0 || remainingPixels <= 0) {
return;
}
shiftX = 0;
chunkPtr = dlPtr->chunkPtr;
while ((nextChunkPtr = chunkPtr->nextPtr)) {
if (chunkPtr->numSpaces > 0) {
unsigned expand = 0;
unsigned i;
assert(IsCharChunk(chunkPtr));
for (i = 0; i < chunkPtr->numSpaces; ++i) {
unsigned space;
assert(numSpaces > 0);
space = (remainingPixels + numSpaces - 1)/numSpaces;
expand += space;
remainingPixels -= space;
numSpaces -= 1;
}
shiftX += expand;
chunkPtr->width += expand;
chunkPtr->additionalWidth = expand;
}
nextChunkPtr->x += shiftX;
chunkPtr = nextChunkPtr;
}
}
static bool
LayoutPrevDispLineEndsWithSpace(
const TkText *textPtr,
const TkTextSegment *segPtr,
int offset)
{
assert(segPtr);
assert(offset < segPtr->size);
if (TkTextSegmentIsElided(textPtr, segPtr)) {
if (!(segPtr = TkBTreeFindStartOfElidedRange(textPtr->sharedTextPtr, textPtr, segPtr))) {
return false;
}
offset = -1;
}
if (offset == -1) {
while (true) {
if (!(segPtr = segPtr->prevPtr)) {
return false;
}
switch ((int) segPtr->typePtr->group) {
case SEG_GROUP_CHAR:
return segPtr->body.chars[segPtr->size - 1] == ' ';
case SEG_GROUP_BRANCH:
if (segPtr->typePtr == &tkTextLinkType) {
segPtr = segPtr->body.link.prevPtr;
}
break;
case SEG_GROUP_MARK:
break;
case SEG_GROUP_HYPHEN:
case SEG_GROUP_IMAGE:
case SEG_GROUP_WINDOW:
return false;
}
}
}
return segPtr->typePtr == &tkTextCharType && segPtr->body.chars[offset] == ' ';
}
static DLine *
LayoutDLine(
const TkTextIndex *indexPtr,
int displayLineNo)
{
TextDInfo *dInfoPtr;
DLine *dlPtr;
TkText *textPtr;
StyleValues *sValPtr;
TkTextDispChunk *chunkPtr;
TkTextDispChunkSection *sectionPtr;
TkTextDispChunkSection *prevSectionPtr;
TkTextSegment *segPtr;
LayoutData data;
bool endOfLogicalLine;
bool isStartOfLine;
int ascent, descent, leading, jIndent;
unsigned countChunks;
unsigned chunksPerSection;
int length, offset;
assert(displayLineNo >= 0);
assert((displayLineNo == 0) ==
(IsStartOfNotMergedLine(indexPtr) || TkTextIndexIsStartOfText(indexPtr)));
DEBUG(stats.numLayouted += 1);
textPtr = indexPtr->textPtr;
assert(textPtr);
dInfoPtr = textPtr->dInfoPtr;
if (dInfoPtr->dLinePoolPtr) {
dlPtr = dInfoPtr->dLinePoolPtr;
dInfoPtr->dLinePoolPtr = dlPtr->nextPtr;
} else {
dlPtr = malloc(sizeof(DLine));
DEBUG_ALLOC(tkTextCountNewDLine++);
}
dlPtr = memset(dlPtr, 0, sizeof(DLine));
dlPtr->flags = NEW_LAYOUT|OLD_Y_INVALID;
dlPtr->index = *indexPtr;
dlPtr->displayLineNo = displayLineNo;
TkTextIndexToByteIndex(&dlPtr->index);
isStartOfLine = TkTextIndexIsStartOfLine(&dlPtr->index);
memset(&data, 0, sizeof(data));
data.index = dlPtr->index;
data.justify = textPtr->justify;
data.tabIndex = -1;
data.tabStyle = TK_TEXT_TABSTYLE_TABULAR;
data.wrapMode = textPtr->wrapMode;
data.paragraphStart = displayLineNo == 0;
data.trimSpaces = textPtr->spaceMode == TEXT_SPACEMODE_TRIM;
data.displayLineNo = displayLineNo;
data.textPtr = textPtr;
if (data.paragraphStart) {
dlPtr->flags |= PARAGRAPH_START;
data.logicalLinePtr = TkTextIndexGetLine(indexPtr);
data.byteOffset = TkTextIndexGetByteIndex(indexPtr);
} else {
TkTextLine *linePtr = TkTextIndexGetLine(indexPtr);
TkTextIndex index2 = *indexPtr;
data.logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
DEBUG(TkTextIndexSetPeer(&index2, NULL));
TkTextIndexSetByteIndex2(&index2, data.logicalLinePtr, 0);
data.byteOffset = TkTextIndexCountBytes(&index2, indexPtr);
}
segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
data.skipSpaces = data.trimSpaces && LayoutPrevDispLineEndsWithSpace(textPtr, segPtr, offset - 1);
if (TkTextSegmentIsElided(textPtr, segPtr)) {
segPtr = TkBTreeFindEndOfElidedRange(textPtr->sharedTextPtr, textPtr, segPtr);
TkTextIndexSetSegment(&data.index, segPtr);
LayoutSkipBytes(&data, dlPtr, indexPtr, &data.index);
if (TkTextIndexIsEndOfText(&data.index)) {
assert(data.chunkPtr);
assert(!data.chunkPtr->nextPtr);
dlPtr->byteCount = data.chunkPtr->numBytes;
LayoutFreeChunk(&data);
LayoutUpdateLineHeightInformation(&data, dlPtr, data.logicalLinePtr, true, 0);
return dlPtr;
}
}
endOfLogicalLine = LayoutLogicalLine(&data, dlPtr);
assert(data.numBytesSoFar > 0);
if (!endOfLogicalLine) {
LayoutBreakLine(&data, &dlPtr->index);
}
if (data.textPtr->hyphenate) {
TkTextDispChunk *chunkPtr = data.firstChunkPtr->nextPtr;
while (chunkPtr) {
TkTextDispChunk *nextChunkPtr = chunkPtr->nextPtr;
if (nextChunkPtr && chunkPtr->width == 0 && chunkPtr != data.cursorChunkPtr) {
chunkPtr->prevPtr->numBytes += chunkPtr->numBytes;
if ((chunkPtr->prevPtr->nextPtr = nextChunkPtr)) {
nextChunkPtr->prevPtr = chunkPtr->prevPtr;
data.chunkPtr = chunkPtr;
LayoutFreeChunk(&data);
}
}
chunkPtr = nextChunkPtr;
}
}
dlPtr->chunkPtr = data.firstChunkPtr;
dlPtr->lastChunkPtr = data.lastChunkPtr;
dlPtr->cursorChunkPtr = data.cursorChunkPtr;
dlPtr->firstCharChunkPtr = data.firstCharChunkPtr;
dlPtr->breakInfo = data.breakInfo;
if (data.tabIndex >= 0) {
assert(data.tabChunkPtr);
AdjustForTab(&data);
}
if (data.wrapMode == TEXT_WRAPMODE_NONE) {
data.maxX = dInfoPtr->maxX - dInfoPtr->x - data.rMargin;
}
length = dlPtr->length = data.lastChunkPtr->x + data.lastChunkPtr->width;
if (data.wrapMode != TEXT_WRAPMODE_NONE) {
length = MIN(length, data.maxX);
}
jIndent = 0;
switch (data.justify) {
case TK_TEXT_JUSTIFY_LEFT:
break;
case TK_TEXT_JUSTIFY_RIGHT:
jIndent = data.maxX - length;
break;
case TK_TEXT_JUSTIFY_FULL:
if (!endOfLogicalLine) {
LayoutFullJustification(&data, dlPtr);
}
break;
case TK_TEXT_JUSTIFY_CENTER:
jIndent = (data.maxX - length)/2;
break;
}
ascent = descent = 0;
sectionPtr = prevSectionPtr = NULL;
chunksPerSection = (data.countChunks + MAX_SECTIONS_PER_LINE - 1)/MAX_SECTIONS_PER_LINE;
chunksPerSection = MAX(chunksPerSection, MIN_CHUNKS_PER_SECTION);
countChunks = chunksPerSection - 1;
for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
if (++countChunks == chunksPerSection) {
sectionPtr = LayoutNewSection(dInfoPtr);
if (prevSectionPtr) {
prevSectionPtr->nextPtr = sectionPtr;
}
sectionPtr->chunkPtr = chunkPtr;
prevSectionPtr = sectionPtr;
countChunks = 0;
}
chunkPtr->sectionPtr = sectionPtr;
sectionPtr->numBytes += chunkPtr->numBytes;
dlPtr->byteCount += chunkPtr->numBytes;
chunkPtr->x += jIndent;
ascent = MAX(ascent, chunkPtr->minAscent);
descent = MAX(descent, chunkPtr->minDescent);
dlPtr->height = MAX(dlPtr->height, chunkPtr->minHeight);
sValPtr = chunkPtr->stylePtr->sValuePtr;
if (sValPtr->borderWidth > 0 && sValPtr->relief != TK_RELIEF_FLAT) {
dlPtr->flags |= HAS_3D_BORDER;
}
}
leading = ascent + descent;
if (dlPtr->height < leading) {
dlPtr->height = leading;
dlPtr->baseline = ascent;
} else {
dlPtr->baseline = ascent + (dlPtr->height - leading)/2;
}
sValPtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
dlPtr->spaceAbove = isStartOfLine ? sValPtr->spacing1 : (sValPtr->spacing2 + 1)/2;
dlPtr->spaceBelow = endOfLogicalLine ? sValPtr->spacing3 : sValPtr->spacing2/2;
dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;
dlPtr->baseline += dlPtr->spaceAbove;
dlPtr->length = data.lastChunkPtr->x + jIndent + data.lastChunkPtr->width;
LayoutUpdateLineHeightInformation(&data, dlPtr, data.logicalLinePtr,
endOfLogicalLine, data.hyphenRule);
return dlPtr;
}
static bool
TriggerWatchCursor(
TkText *textPtr)
{
if (textPtr->watchCmd) {
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
char buf[2][2*TK_POS_CHARS + 2];
if (memcmp(&dInfoPtr->curPixelPos, &dInfoPtr->prevPixelPos, sizeof(PixelPos)) != 0) {
textPtr->sharedTextPtr->triggerWatchCmd = false;
snprintf(buf[0], sizeof(buf[0]), "@%d,%d",
dInfoPtr->curPixelPos.xFirst, dInfoPtr->curPixelPos.yFirst);
snprintf(buf[1], sizeof(buf[1]), "@%d,%d",
dInfoPtr->curPixelPos.xLast, dInfoPtr->curPixelPos.yLast);
TkTextTriggerWatchCmd(textPtr, "view", buf[0], buf[1], NULL, false);
memcpy(&textPtr->dInfoPtr->prevPixelPos, &textPtr->dInfoPtr->curPixelPos, sizeof(PixelPos));
textPtr->sharedTextPtr->triggerWatchCmd = true;
}
}
return !(textPtr->flags & DESTROYED);
}
static void
UpdateLineMetricsFinished(
TkText *textPtr,
bool sendImmediately)
{
assert(TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges));
textPtr->dInfoPtr->flags &= ~(ASYNC_UPDATE|ASYNC_PENDING);
textPtr->dInfoPtr->pendingUpdateLineMetricsFinished = false;
TkTextRunAfterSyncCmd(textPtr);
TkTextGenerateWidgetViewSyncEvent(textPtr, sendImmediately);
}
static void
RunUpdateLineMetricsFinished(
ClientData clientData)
{
TkText *textPtr = (TkText *) clientData;
if (!(textPtr->flags & DESTROYED)) {
textPtr->dInfoPtr->pendingUpdateLineMetricsFinished = false;
if (TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges)) {
UpdateLineMetricsFinished(textPtr, true);
}
}
}
static void
CheckIfLineMetricIsUpToDate(
TkText *textPtr)
{
if (textPtr->sharedTextPtr->allowUpdateLineMetrics
&& TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges)) {
if (textPtr->dInfoPtr->lineUpdateTimer) {
Tcl_DeleteTimerHandler(textPtr->dInfoPtr->lineUpdateTimer);
textPtr->refCount -= 1;
textPtr->dInfoPtr->lineUpdateTimer = NULL;
}
GetYView(textPtr->interp, textPtr, true);
if (!(TriggerWatchCursor(textPtr))) {
return;
}
if (!textPtr->dInfoPtr->pendingUpdateLineMetricsFinished) {
textPtr->dInfoPtr->pendingUpdateLineMetricsFinished = true;
Tcl_DoWhenIdle(RunUpdateLineMetricsFinished, (ClientData) textPtr);
}
if (tkBTreeDebug) {
CheckLineMetricConsistency(textPtr);
}
}
}
static void
SaveDisplayLines(
TkText *textPtr,
DisplayInfo *info,
bool append)
{
TextDInfo *dInfoPtr;
DLine *firstPtr, *lastPtr;
int height, viewHeight;
if (!(firstPtr = info->dLinePtr)) {
return;
}
assert(info->lastDLinePtr);
lastPtr = info->lastDLinePtr;
dInfoPtr = textPtr->dInfoPtr;
height = dInfoPtr->savedDisplayLinesHeight + info->heightOfCachedLines;
viewHeight = Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth;
viewHeight += info->dLinePtr->height;
if (append) {
if (dInfoPtr->lastSavedDLinePtr) {
dInfoPtr->lastSavedDLinePtr->nextPtr = firstPtr;
firstPtr->prevPtr = dInfoPtr->lastSavedDLinePtr;
} else {
dInfoPtr->savedDLinePtr = firstPtr;
}
dInfoPtr->lastSavedDLinePtr = lastPtr;
firstPtr = lastPtr = dInfoPtr->savedDLinePtr;
while (lastPtr->nextPtr && height >= viewHeight - lastPtr->height) {
height -= lastPtr->height;
lastPtr = lastPtr->nextPtr;
}
if (firstPtr != lastPtr) {
FreeDLines(textPtr, firstPtr, lastPtr, DLINE_FREE_TEMP);
assert(dInfoPtr->savedDLinePtr == lastPtr);
}
} else {
if (dInfoPtr->savedDLinePtr) {
lastPtr->nextPtr = dInfoPtr->savedDLinePtr;
dInfoPtr->savedDLinePtr->prevPtr = lastPtr;
} else {
dInfoPtr->lastSavedDLinePtr = lastPtr;
}
dInfoPtr->savedDLinePtr = firstPtr;
firstPtr = lastPtr = dInfoPtr->lastSavedDLinePtr;
while (firstPtr->prevPtr && height >= viewHeight - firstPtr->height) {
height -= firstPtr->height;
firstPtr = firstPtr->prevPtr;
}
if (firstPtr != lastPtr) {
FreeDLines(textPtr, firstPtr->nextPtr, NULL, DLINE_FREE_TEMP);
assert(!firstPtr->nextPtr);
dInfoPtr->lastSavedDLinePtr = firstPtr;
}
}
dInfoPtr->savedDisplayLinesHeight = height;
info->dLinePtr = info->lastDLinePtr = NULL;
info->numCachedLines = 0;
info->heightOfCachedLines = 0;
assert(!dInfoPtr->savedDLinePtr == !dInfoPtr->lastSavedDLinePtr);
}
static TkTextDispLineEntry *
SearchDispLineEntry(
TkTextDispLineEntry *first,
const TkTextDispLineEntry *last,
unsigned byteOffset)
{
if (byteOffset >= last->byteOffset) {
return (TkTextDispLineEntry *) last;
}
while (first != last) {
TkTextDispLineEntry *mid = first + (last - first)/2;
if (byteOffset >= (mid + 1)->byteOffset) {
first = mid + 1;
} else {
last = mid;
}
}
return first;
}
static void
InsertDLine(
TkText *textPtr,
DisplayInfo *info,
DLine *dlPtr,
unsigned viewHeight)
{
DLine *firstPtr = info->dLinePtr;
assert(!dlPtr->nextPtr);
assert(!dlPtr->prevPtr);
info->heightOfCachedLines += dlPtr->height;
if (firstPtr && info->heightOfCachedLines >= viewHeight + firstPtr->height) {
info->heightOfCachedLines -= firstPtr->height;
if ((info->dLinePtr = firstPtr->nextPtr)) {
info->dLinePtr->prevPtr = NULL;
} else {
info->lastDLinePtr = NULL;
}
firstPtr->nextPtr = NULL;
FreeDLines(textPtr, firstPtr, NULL, DLINE_FREE_TEMP);
} else {
info->numCachedLines += 1;
}
if (info->lastDLinePtr) {
assert(info->dLinePtr);
info->lastDLinePtr->nextPtr = dlPtr;
dlPtr->prevPtr = info->lastDLinePtr;
} else {
assert(!info->dLinePtr);
info->dLinePtr = dlPtr;
}
info->lastDLinePtr = dlPtr;
}
static TkTextLine *
ComputeDisplayLineInfo(
TkText *textPtr,
const TkTextIndex *indexPtr,
DisplayInfo *info)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextPixelInfo *pixelInfo;
TkTextDispLineInfo *dispLineInfo;
TkTextDispLineEntry *entry;
TkTextLine *logicalLinePtr;
TkTextLine *linePtr;
unsigned byteOffset;
unsigned startByteOffset;
unsigned viewHeight;
assert(info);
linePtr = TkTextIndexGetLine(indexPtr);
logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
pixelInfo = TkBTreeLinePixelInfo(textPtr, logicalLinePtr);
dispLineInfo = pixelInfo->dispLineInfo;
info->index = *indexPtr;
TkTextIndexSetToStartOfLine2(&info->index, logicalLinePtr);
startByteOffset = TkTextIndexGetByteIndex(&info->index);
byteOffset = TkTextIndexCountBytes(&info->index, indexPtr);
byteOffset += TkTextIndexGetByteIndex(&info->index);
info->pixelInfo = pixelInfo;
info->displayLineNo = 0;
info->numDispLines = 1;
info->entry = info->entryBuffer;
info->dLinePtr = info->lastDLinePtr = NULL;
info->nextByteOffset = -1;
info->numCachedLines = 0;
info->heightOfCachedLines = 0;
info->linePtr = linePtr;
if (dInfoPtr->lineMetricUpdateEpoch == (pixelInfo->epoch & EPOCH_MASK)) {
if (!dispLineInfo) {
TkTextLine *nextLogicalLinePtr =
TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, logicalLinePtr);
entry = info->entryBuffer;
if (logicalLinePtr->nextPtr == nextLogicalLinePtr
&& TkTextIndexIsStartOfLine(&info->index)) {
info->nextByteOffset = logicalLinePtr->size - byteOffset;
entry->byteOffset = 0;
(entry + 1)->byteOffset = logicalLinePtr->size;
} else {
TkTextIndex index2 = info->index;
TkTextIndexSetToStartOfLine2(&index2, nextLogicalLinePtr);
info->nextByteOffset = TkTextIndexCountBytes(&info->index, &index2);
entry->byteOffset = TkTextIndexGetByteIndex(&info->index);
(entry + 1)->byteOffset = entry->byteOffset + info->nextByteOffset;
}
info->byteOffset = byteOffset;
info->isComplete = true;
info->pixels = pixelInfo->height;
entry->height = pixelInfo->height;
entry->pixels = pixelInfo->height;
byteOffset = (entry + 1)->byteOffset - startByteOffset;
TkTextIndexForwBytes(textPtr, &info->index, byteOffset, &info->index);
return logicalLinePtr;
}
if (dispLineInfo->numDispLines > 0) {
const TkTextDispLineEntry *last;
unsigned nextByteOffset;
last = dispLineInfo->entry + dispLineInfo->numDispLines;
entry = SearchDispLineEntry(dispLineInfo->entry, last, byteOffset);
if (entry != last) {
info->entry = entry;
info->byteOffset = byteOffset - entry->byteOffset;
info->nextByteOffset = (entry + 1)->byteOffset - byteOffset;
info->displayLineNo = entry - dispLineInfo->entry;
info->numDispLines = dispLineInfo->numDispLines;
info->pixels = (last - 1)->pixels;
info->isComplete = (dInfoPtr->lineMetricUpdateEpoch == pixelInfo->epoch);
byteOffset = last->byteOffset - startByteOffset;
TkTextIndexForwBytes(textPtr, &info->index, byteOffset, &info->index);
return logicalLinePtr;
}
info->displayLineNo = dispLineInfo->numDispLines;
nextByteOffset = last->byteOffset - dispLineInfo->entry[0].byteOffset;
TkBTreeMoveForward(&info->index, nextByteOffset);
byteOffset -= nextByteOffset;
}
}
viewHeight = Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth;
viewHeight += dInfoPtr->dLinePtr ? dInfoPtr->dLinePtr->height : 20;
while (true) {
DLine *dlPtr;
if (dInfoPtr->lastMetricDLinePtr
&& TkTextIndexIsEqual(&info->index, &dInfoPtr->lastMetricDLinePtr->index)) {
dlPtr = dInfoPtr->lastMetricDLinePtr;
dInfoPtr->lastMetricDLinePtr = NULL;
assert(dlPtr->displayLineNo == info->displayLineNo);
} else {
dlPtr = LayoutDLine(&info->index, info->displayLineNo);
}
InsertDLine(textPtr, info, dlPtr, viewHeight);
TkTextIndexForwBytes(textPtr, &info->index, dlPtr->byteCount, &info->index);
if (dInfoPtr->lineMetricUpdateEpoch == pixelInfo->epoch || byteOffset < dlPtr->byteCount) {
info->byteOffset = byteOffset;
info->nextByteOffset = dlPtr->byteCount - byteOffset;
info->isComplete = (dInfoPtr->lineMetricUpdateEpoch == pixelInfo->epoch);
break;
}
byteOffset -= dlPtr->byteCount;
info->displayLineNo += 1;
}
dispLineInfo = pixelInfo->dispLineInfo;
if (dispLineInfo) {
info->numDispLines = dispLineInfo->numDispLines;
info->entry = dispLineInfo->entry + info->displayLineNo;
info->pixels = dispLineInfo->entry[dispLineInfo->numDispLines - 1].pixels;
} else {
info->pixels = pixelInfo->height;
info->entryBuffer[0].height = pixelInfo->height;
info->entryBuffer[0].pixels = pixelInfo->height;
info->entryBuffer[0].byteOffset = byteOffset;
info->entryBuffer[1].byteOffset = info->nextByteOffset + info->byteOffset;
}
return logicalLinePtr;
}
static void
ComputeMissingMetric(
TkText *textPtr,
DisplayInfo *info,
Threshold thresholdType,
int threshold)
{
int byteOffset, additionalLines;
int displayLineNo;
int *metricPtr = NULL;
unsigned viewHeight;
TkTextIndex index;
assert(threshold >= 0);
if (info->isComplete) {
return;
}
additionalLines = info->numDispLines - info->displayLineNo;
assert(additionalLines > 0);
byteOffset = info->entry[additionalLines].byteOffset;
displayLineNo = info->numDispLines;
viewHeight = Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth;
viewHeight += textPtr->dInfoPtr->dLinePtr ? textPtr->dInfoPtr->dLinePtr->height : 20;
TkTextIndexForwBytes(textPtr, &info->index,
byteOffset - info->entry[additionalLines - 1].byteOffset, &index);
switch (thresholdType) {
case THRESHOLD_BYTE_OFFSET: metricPtr = &byteOffset; break;
case THRESHOLD_LINE_OFFSET: metricPtr = &additionalLines; break;
case THRESHOLD_PIXEL_DISTANCE: metricPtr = &info->pixels; break;
}
while (threshold >= *metricPtr) {
DLine *dlPtr = LayoutDLine(&info->index, displayLineNo++);
info->pixels += dlPtr->height;
byteOffset += dlPtr->byteCount;
info->numDispLines += 1;
additionalLines -= 1;
TkTextIndexForwBytes(textPtr, &info->index, dlPtr->byteCount, &info->index);
InsertDLine(textPtr, info, dlPtr, viewHeight);
if (IsStartOfNotMergedLine(&info->index)) {
info->isComplete = true;
break;
}
}
info->entry = info->pixelInfo->dispLineInfo->entry + info->displayLineNo;
}
static bool
LineIsUpToDate(
TkText *textPtr,
DLine *dlPtr,
unsigned lineMetricUpdateEpoch)
{
const TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, TkTextIndexGetLine(&dlPtr->index));
const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
unsigned epoch = pixelInfo->epoch;
assert(!(epoch & PARTIAL_COMPUTED_BIT) || dispLineInfo);
return (epoch & EPOCH_MASK) == lineMetricUpdateEpoch
&& (!dispLineInfo || dlPtr->displayLineNo < dispLineInfo->numDispLines);
}
static void
UpdateDisplayInfo(
TkText *textPtr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
DLine *topLine;
DLine *bottomLine;
DLine *newTopLine;
DLine *savedDLine;
DLine *prevSavedDLine;
TkTextIndex index;
TkTextLine *lastLinePtr;
TkTextLine *linePtr;
DisplayInfo info;
int y, maxY, xPixelOffset, maxOffset;
int displayLineNo;
unsigned epoch;
if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
return;
}
dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;
UpdateDefaultStyle(textPtr);
dInfoPtr->currChunkPtr = NULL;
FreeDLines(textPtr, NULL, NULL, DLINE_CACHE);
index = textPtr->topIndex;
prevSavedDLine = NULL;
savedDLine = dInfoPtr->savedDLinePtr;
if ((dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index))) {
prevSavedDLine = FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_SAVE);
}
linePtr = TkTextIndexGetLine(&index);
lastLinePtr = TkBTreeGetLastLine(textPtr);
dlPtr = dInfoPtr->dLinePtr;
topLine = bottomLine = NULL;
y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
maxY = dInfoPtr->maxY;
newTopLine = NULL;
epoch = dInfoPtr->lineMetricUpdateEpoch;
dInfoPtr->maxLength = 0;
if (IsStartOfNotMergedLine(&index)) {
displayLineNo = 0;
} else {
ComputeDisplayLineInfo(textPtr, &index, &info);
TkTextIndexBackBytes(textPtr, &index, info.byteOffset, &index);
displayLineNo = info.displayLineNo;
if (info.lastDLinePtr) {
newTopLine = info.lastDLinePtr;
if (newTopLine->prevPtr) {
newTopLine->prevPtr->nextPtr = NULL;
newTopLine->prevPtr = NULL;
} else {
assert(info.dLinePtr == info.lastDLinePtr);
info.dLinePtr = info.lastDLinePtr = NULL;
}
assert(!newTopLine->nextPtr);
}
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
while (savedDLine && TkTextIndexCompare(&savedDLine->index, &index) < 0) {
savedDLine = savedDLine->nextPtr;
}
if (newTopLine) {
assert(!savedDLine || TkTextIndexCompare(&savedDLine->index, &newTopLine->index) > 0);
if ((newTopLine->nextPtr = savedDLine)) {
newTopLine->prevPtr = savedDLine->prevPtr;
savedDLine->prevPtr = newTopLine;
} else if (dInfoPtr->savedDLinePtr) {
dInfoPtr->lastSavedDLinePtr->nextPtr = newTopLine;
newTopLine->prevPtr = dInfoPtr->lastSavedDLinePtr;
dInfoPtr->lastSavedDLinePtr = newTopLine;
}
if (dInfoPtr->savedDLinePtr == savedDLine) {
dInfoPtr->savedDLinePtr = newTopLine;
}
if (!dInfoPtr->lastSavedDLinePtr) {
dInfoPtr->lastSavedDLinePtr = newTopLine;
}
savedDLine = newTopLine;
} else {
newTopLine = savedDLine;
}
if (newTopLine && !prevSavedDLine) {
prevSavedDLine = newTopLine->prevPtr;
}
while (linePtr != lastLinePtr) {
int cmp;
if (!dlPtr
|| TkTextIndexGetLine(&dlPtr->index) != linePtr
|| !LineIsUpToDate(textPtr, dlPtr, epoch)
|| (cmp = TkTextIndexCompare(&index, &dlPtr->index)) < 0) {
TK_TEXT_DEBUG(LogTextRelayout(textPtr, &index));
if (savedDLine && TkTextIndexIsEqual(&index, &savedDLine->index)) {
dlPtr = savedDLine;
savedDLine = savedDLine->nextPtr;
if (dInfoPtr->savedDLinePtr == dlPtr) {
dInfoPtr->savedDLinePtr = dlPtr->nextPtr;
}
if (dInfoPtr->lastSavedDLinePtr == dlPtr) {
dInfoPtr->lastSavedDLinePtr = dlPtr->prevPtr;
}
if (dlPtr->prevPtr) {
dlPtr->prevPtr->nextPtr = dlPtr->nextPtr;
}
if (dlPtr->nextPtr) {
dlPtr->nextPtr->prevPtr = dlPtr->prevPtr;
}
dlPtr->prevPtr = dlPtr->nextPtr = NULL;
DEBUG(stats.numReused++);
} else {
dlPtr = LayoutDLine(&index, displayLineNo);
}
assert(!(dlPtr->flags & (LINKED|CACHED|DELETED)));
if (!bottomLine) {
if ((dlPtr->nextPtr = dInfoPtr->dLinePtr)) {
dInfoPtr->dLinePtr->prevPtr = dlPtr;
}
dInfoPtr->dLinePtr = dlPtr;
} else {
if ((dlPtr->nextPtr = bottomLine->nextPtr)) {
bottomLine->nextPtr->prevPtr = dlPtr;
}
bottomLine->nextPtr = dlPtr;
dlPtr->prevPtr = bottomLine;
if (bottomLine->flags & HAS_3D_BORDER) {
bottomLine->flags |= OLD_Y_INVALID;
}
}
DEBUG(dlPtr->flags |= LINKED);
} else if (cmp == 0) {
if (bottomLine && (dlPtr->flags & HAS_3D_BORDER) && (bottomLine->flags & NEW_LAYOUT)) {
dlPtr->flags |= OLD_Y_INVALID;
}
assert(dlPtr->displayLineNo == displayLineNo);
} else {
DLine *nextPtr = dlPtr->nextPtr;
FreeDLines(textPtr, dlPtr, nextPtr, DLINE_UNLINK);
dlPtr = nextPtr;
continue;
}
dlPtr->y = y;
y += dlPtr->height;
TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
linePtr = TkTextIndexGetLine(&index);
if (linePtr->logicalLine && TkTextIndexIsStartOfLine(&index)) {
displayLineNo = 0;
} else {
displayLineNo += 1;
}
bottomLine = dlPtr;
dlPtr = dlPtr->nextPtr;
if (y >= maxY) {
break;
}
}
FreeDLines(textPtr, dlPtr, NULL, DLINE_UNLINK);
topLine = dInfoPtr->dLinePtr;
if (y < maxY) {
int spaceLeft = maxY - y;
if (spaceLeft <= dInfoPtr->newTopPixelOffset) {
dInfoPtr->newTopPixelOffset -= spaceLeft;
y += spaceLeft;
spaceLeft = 0;
} else {
TkTextLine *linePtr;
TkTextLine *firstLinePtr;
y += dInfoPtr->newTopPixelOffset;
dInfoPtr->newTopPixelOffset = 0;
spaceLeft = maxY - y;
if (spaceLeft > 0) {
firstLinePtr = TkBTreeGetStartLine(textPtr)->prevPtr;
index = topLine ? topLine->index : textPtr->topIndex;
savedDLine = prevSavedDLine;
if (TkTextIndexBackBytes(textPtr, &index, 1, &index) == 1) {
firstLinePtr = linePtr = NULL;
} else {
linePtr = TkTextIndexGetLine(&index);
}
for ( ; linePtr != firstLinePtr && spaceLeft > 0; linePtr = linePtr->prevPtr) {
if (linePtr != TkTextIndexGetLine(&index)) {
TkTextIndexSetToLastChar2(&index, linePtr);
}
linePtr = ComputeDisplayLineInfo(textPtr, &index, &info);
do {
if (info.lastDLinePtr) {
dlPtr = info.lastDLinePtr;
if (dlPtr->prevPtr) {
dlPtr->prevPtr->nextPtr = NULL;
info.lastDLinePtr = dlPtr->prevPtr;
dlPtr->prevPtr = NULL;
assert(dlPtr != info.dLinePtr);
} else {
assert(info.dLinePtr == info.lastDLinePtr);
info.dLinePtr = info.lastDLinePtr = NULL;
}
} else {
TkTextIndexSetToStartOfLine2(&index, linePtr);
TkTextIndexForwBytes(textPtr, &index, info.entry->byteOffset, &index);
if (savedDLine && TkTextIndexIsEqual(&index, &savedDLine->index)) {
dlPtr = savedDLine;
savedDLine = savedDLine->prevPtr;
if (dlPtr->prevPtr) {
dlPtr->prevPtr->nextPtr = dlPtr->nextPtr;
} else {
dInfoPtr->savedDLinePtr = dlPtr->nextPtr;
}
if (dlPtr->nextPtr) {
dlPtr->nextPtr->prevPtr = dlPtr->prevPtr;
} else {
dInfoPtr->lastSavedDLinePtr = dlPtr->prevPtr;
}
dlPtr->prevPtr = dlPtr->nextPtr = NULL;
} else {
dlPtr = LayoutDLine(&index, info.displayLineNo);
}
}
if ((dlPtr->nextPtr = topLine)) {
topLine->prevPtr = dlPtr;
} else {
bottomLine = dlPtr;
}
topLine = dlPtr;
DEBUG(dlPtr->flags |= LINKED);
TK_TEXT_DEBUG(LogTextRelayout(textPtr, &dlPtr->index));
spaceLeft -= dlPtr->height;
info.displayLineNo -= 1;
info.entry -= 1;
} while (spaceLeft > 0 && info.displayLineNo >= 0);
dInfoPtr->dLinePtr = topLine;
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
}
if (spaceLeft < 0) {
dInfoPtr->newTopPixelOffset = -spaceLeft;
assert(dInfoPtr->newTopPixelOffset < topLine->height);
}
}
if (topLine) {
dInfoPtr->dLinePtr = topLine;
textPtr->topIndex = topLine->index;
assert(textPtr->topIndex.textPtr);
TkTextIndexToByteIndex(&textPtr->topIndex);
y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
for (dlPtr = topLine; dlPtr; dlPtr = dlPtr->nextPtr) {
assert(y <= dInfoPtr->maxY);
dlPtr->y = y;
y += dlPtr->height;
}
}
}
if (topLine) {
dInfoPtr->maxLength = MAX(dInfoPtr->maxLength, topLine->length);
if ((topLine->flags & (TOP_LINE|HAS_3D_BORDER)) == HAS_3D_BORDER) {
topLine->flags |= OLD_Y_INVALID;
}
if ((bottomLine->flags & (BOTTOM_LINE|HAS_3D_BORDER)) == HAS_3D_BORDER) {
bottomLine->flags |= OLD_Y_INVALID;
}
if (topLine != bottomLine) {
topLine->flags &= ~BOTTOM_LINE;
bottomLine->flags &= ~TOP_LINE;
for (dlPtr = topLine->nextPtr; dlPtr != bottomLine; dlPtr = dlPtr->nextPtr) {
dInfoPtr->maxLength = MAX(dInfoPtr->maxLength, dlPtr->length);
if ((topLine->flags & HAS_3D_BORDER) && (dlPtr->flags & (TOP_LINE|BOTTOM_LINE))) {
dlPtr->flags |= OLD_Y_INVALID;
}
if ((dlPtr->flags & TOP_LINE) && dInfoPtr->topPixelOffset != 0) {
dlPtr->flags |= OLD_Y_INVALID;
}
dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
}
dInfoPtr->maxLength = MAX(dInfoPtr->maxLength, bottomLine->length);
}
topLine->flags |= TOP_LINE;
bottomLine->flags |= BOTTOM_LINE;
dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset;
dInfoPtr->curYPixelOffset = GetYPixelCount(textPtr, topLine);
dInfoPtr->curYPixelOffset += dInfoPtr->topPixelOffset;
}
dInfoPtr->lastDLinePtr = bottomLine;
FreeDLines(textPtr, dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
textPtr->flags |= UPDATE_SCROLLBARS;
maxOffset = dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
xPixelOffset = MAX(0, MIN(dInfoPtr->newXPixelOffset, maxOffset));
if (!(((Tk_FakeWin *) (textPtr->tkwin))->flags & TK_NEED_CONFIG_NOTIFY)) {
dInfoPtr->newXPixelOffset = xPixelOffset;
}
if (xPixelOffset != dInfoPtr->curXPixelOffset) {
dInfoPtr->curXPixelOffset = xPixelOffset;
for (dlPtr = topLine; dlPtr; dlPtr = dlPtr->nextPtr) {
dlPtr->flags |= OLD_Y_INVALID;
}
textPtr->configureBboxTree = true;
}
}
static bool
LineIsOutsideOfPeer(
const TkText *textPtr,
const TkTextIndex *indexPtr)
{
const TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
if (textPtr->startMarker != sharedTextPtr->startMarker) {
const TkTextLine *linePtr = textPtr->startMarker->sectionPtr->linePtr;
int no1 = TkTextIndexGetLineNumber(indexPtr, NULL);
int no2 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr, NULL);
if (no1 < no2) {
return true;
}
}
if (textPtr->endMarker != sharedTextPtr->endMarker) {
const TkTextLine *linePtr = textPtr->endMarker->sectionPtr->linePtr;
int no1 = TkTextIndexGetLineNumber(indexPtr, NULL);
int no2 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr, NULL);
if (no1 > no2) {
return true;
}
}
return false;
}
static void
ReleaseLines(
TkText *textPtr,
DLine *firstPtr,
DLine *lastPtr,
FreeDLineAction action)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr, *lastDeletedPtr = NULL;
assert(firstPtr);
assert(firstPtr != lastPtr);
for (dlPtr = firstPtr; dlPtr != lastPtr; dlPtr = dlPtr->nextPtr) {
TkTextDispChunk *chunkPtr;
assert(!(dlPtr->flags & DELETED));
assert((action == DLINE_UNLINK || action == DLINE_UNLINK_KEEP_BRKS)
== !!(dlPtr->flags & LINKED));
assert((action == DLINE_CACHE) == !!(dlPtr->flags & CACHED));
assert(dlPtr != dInfoPtr->savedDLinePtr || dlPtr == firstPtr);
assert(dlPtr->chunkPtr || (!dlPtr->lastChunkPtr && !dlPtr->breakInfo));
if (dlPtr->lastChunkPtr) {
TkTextDispChunkSection *sectionPtr = NULL;
for (chunkPtr = dlPtr->lastChunkPtr; chunkPtr; chunkPtr = chunkPtr->prevPtr) {
if (chunkPtr->layoutProcs->undisplayProc) {
chunkPtr->layoutProcs->undisplayProc(textPtr, chunkPtr);
}
LayoutReleaseChunk(textPtr, chunkPtr);
DEBUG(chunkPtr->stylePtr = NULL);
if (chunkPtr->sectionPtr != sectionPtr) {
sectionPtr = chunkPtr->sectionPtr;
sectionPtr->nextPtr = dInfoPtr->sectionPoolPtr;
dInfoPtr->sectionPoolPtr = sectionPtr;
}
}
if (dlPtr->breakInfo
&& (action != DLINE_UNLINK_KEEP_BRKS || LineIsOutsideOfPeer(textPtr, &dlPtr->index))
&& --dlPtr->breakInfo->refCount == 0) {
assert(dlPtr->breakInfo->brks);
free(dlPtr->breakInfo->brks);
free(dlPtr->breakInfo);
Tcl_DeleteHashEntry(Tcl_FindHashEntry(
&textPtr->sharedTextPtr->breakInfoTable,
(void *) TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr,
TkTextIndexGetLine(&dlPtr->index))));
DEBUG_ALLOC(tkTextCountDestroyBreakInfo++);
}
dlPtr->lastChunkPtr->nextPtr = dInfoPtr->chunkPoolPtr;
dInfoPtr->chunkPoolPtr = dlPtr->chunkPtr;
assert(!dInfoPtr->chunkPoolPtr->prevPtr);
}
lastDeletedPtr = dlPtr;
DEBUG(dlPtr->flags = DELETED);
}
assert(lastDeletedPtr);
lastDeletedPtr->nextPtr = dInfoPtr->dLinePoolPtr;
dInfoPtr->dLinePoolPtr = firstPtr;
if (lastPtr) {
lastPtr->prevPtr = firstPtr->prevPtr;
}
if (firstPtr->prevPtr) {
firstPtr->prevPtr->nextPtr = lastPtr;
}
}
static DLine *
FreeDLines(
TkText *textPtr,
DLine *firstPtr,
DLine *lastPtr,
FreeDLineAction action)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
switch (action) {
case DLINE_CACHE:
assert(!lastPtr);
if (firstPtr) {
DLine *prevPtr = firstPtr->prevPtr;
assert(!(firstPtr->flags & LINKED));
assert(!(firstPtr->flags & CACHED));
assert(!(firstPtr->flags & DELETED));
assert(firstPtr != dInfoPtr->savedDLinePtr);
if (firstPtr == dInfoPtr->dLinePtr) {
dInfoPtr->dLinePtr = firstPtr->nextPtr;
}
if (firstPtr == dInfoPtr->lastDLinePtr) {
dInfoPtr->lastDLinePtr = prevPtr;
}
if (prevPtr) {
prevPtr->nextPtr = firstPtr->nextPtr;
}
if (firstPtr->nextPtr) {
firstPtr->nextPtr->prevPtr = prevPtr;
}
firstPtr->prevPtr = NULL;
if ((firstPtr->nextPtr = dInfoPtr->cachedDLinePtr)) {
dInfoPtr->cachedDLinePtr->prevPtr = firstPtr;
} else {
dInfoPtr->lastCachedDLinePtr = firstPtr;
}
dInfoPtr->cachedDLinePtr = firstPtr;
DEBUG(firstPtr->flags &= ~LINKED);
DEBUG(firstPtr->flags |= CACHED);
DEBUG(stats.numCached += 1);
if (dInfoPtr->numCachedLines < MAX_CACHED_DISPLAY_LINES) {
dInfoPtr->numCachedLines += 1;
return NULL;
}
if ((firstPtr = dInfoPtr->lastCachedDLinePtr)) {
firstPtr->prevPtr->nextPtr = NULL;
}
dInfoPtr->lastCachedDLinePtr = dInfoPtr->lastCachedDLinePtr->prevPtr;
} else {
if (!(firstPtr = dInfoPtr->cachedDLinePtr)) {
return NULL;
}
dInfoPtr->cachedDLinePtr = dInfoPtr->lastCachedDLinePtr = NULL;
dInfoPtr->numCachedLines = 0;
}
ReleaseLines(textPtr, firstPtr, lastPtr, action);
break;
case DLINE_METRIC:
assert(!lastPtr);
if (dInfoPtr->lastMetricDLinePtr) {
ReleaseLines(textPtr, dInfoPtr->lastMetricDLinePtr, NULL, DLINE_FREE_TEMP);
dInfoPtr->lastMetricDLinePtr = NULL;
}
if (firstPtr) {
assert(!firstPtr->nextPtr);
assert(!(firstPtr->flags & (LINKED|CACHED|DELETED)));
dInfoPtr->lastMetricDLinePtr = firstPtr;
if (firstPtr->prevPtr) {
firstPtr->prevPtr->nextPtr = NULL;
firstPtr->prevPtr = NULL;
}
}
break;
case DLINE_FREE_TEMP:
if (!firstPtr || firstPtr == lastPtr) {
return NULL;
}
DEBUG(stats.lineHeightsRecalculated += 1);
assert(!(firstPtr->flags & LINKED));
assert(!(firstPtr->flags & CACHED));
if (firstPtr == dInfoPtr->savedDLinePtr) {
dInfoPtr->savedDLinePtr = NULL;
if (!lastPtr) {
dInfoPtr->lastSavedDLinePtr = NULL;
} else {
dInfoPtr->savedDLinePtr = lastPtr;
}
} else {
assert(!lastPtr || lastPtr != dInfoPtr->lastSavedDLinePtr);
}
assert(!dInfoPtr->savedDLinePtr == !dInfoPtr->lastSavedDLinePtr);
ReleaseLines(textPtr, firstPtr, lastPtr, action);
break;
case DLINE_UNLINK:
case DLINE_UNLINK_KEEP_BRKS:
if (!firstPtr || firstPtr == lastPtr) {
return NULL;
}
assert(firstPtr->flags & LINKED);
assert(firstPtr != dInfoPtr->savedDLinePtr);
if (dInfoPtr->dLinePtr == firstPtr) {
if ((dInfoPtr->dLinePtr = lastPtr)) {
lastPtr->prevPtr = NULL;
}
} else {
DLine *prevPtr = firstPtr->prevPtr;
if (prevPtr && (prevPtr->nextPtr = lastPtr)) {
lastPtr->prevPtr = prevPtr;
}
}
if (!lastPtr) {
dInfoPtr->lastDLinePtr = firstPtr->prevPtr;
}
dInfoPtr->dLinesInvalidated = true;
assert(!dInfoPtr->dLinePtr || !dInfoPtr->dLinePtr->prevPtr);
ReleaseLines(textPtr, firstPtr, lastPtr, action);
break;
case DLINE_SAVE: {
if (!firstPtr || firstPtr == lastPtr) {
return NULL;
}
assert(firstPtr == dInfoPtr->dLinePtr);
assert(lastPtr);
unsigned epoch = dInfoPtr->lineMetricUpdateEpoch;
DLine *dlPtr;
assert(lastPtr->prevPtr);
dInfoPtr->dLinePtr = lastPtr;
dlPtr = firstPtr;
while (dlPtr != lastPtr) {
DLine *nextPtr = dlPtr->nextPtr;
assert(dlPtr->flags & LINKED);
if (LineIsUpToDate(textPtr, dlPtr, epoch)) {
DEBUG(dlPtr->flags &= ~LINKED);
} else {
if (dlPtr == firstPtr) {
firstPtr = nextPtr;
}
ReleaseLines(textPtr, dlPtr, nextPtr, DLINE_UNLINK);
}
dlPtr = nextPtr;
}
assert(!firstPtr->prevPtr);
if (firstPtr == lastPtr) {
dInfoPtr->savedDLinePtr = NULL;
dInfoPtr->lastSavedDLinePtr = NULL;
return NULL;
}
lastPtr = lastPtr->prevPtr;
lastPtr->nextPtr->prevPtr = NULL;
lastPtr->nextPtr = NULL;
if (!dInfoPtr->savedDLinePtr) {
dInfoPtr->savedDLinePtr = firstPtr;
dInfoPtr->lastSavedDLinePtr = lastPtr;
} else if (TkTextIndexCompare(&lastPtr->index, &dInfoPtr->savedDLinePtr->index) < 0) {
lastPtr->nextPtr = dInfoPtr->savedDLinePtr;
dInfoPtr->savedDLinePtr->prevPtr = lastPtr;
dInfoPtr->savedDLinePtr = firstPtr;
} else {
assert(TkTextIndexCompare(&firstPtr->index, &dInfoPtr->lastSavedDLinePtr->index) > 0);
firstPtr->prevPtr = dInfoPtr->lastSavedDLinePtr;
dInfoPtr->lastSavedDLinePtr->nextPtr = firstPtr;
dInfoPtr->lastSavedDLinePtr = lastPtr;
}
return lastPtr;
}
}
return NULL;
}
static void
DisplayDLine(
TkText *textPtr,
DLine *dlPtr,
DLine *prevPtr,
Pixmap pixmap)
{
TkTextDispChunk *chunkPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
Display *display;
StyleValues *sValuePtr;
int lineHeight, yOffs;
int yBase, height, baseline, screenY, xOffs;
int xIndent, rMargin;
bool delayBlockCursorDrawing;
if (!dlPtr->chunkPtr) {
return;
}
display = Tk_Display(textPtr->tkwin);
delayBlockCursorDrawing = false;
lineHeight = dlPtr->height;
if (lineHeight + dlPtr->y > dInfoPtr->maxY) {
lineHeight = dInfoPtr->maxY - dlPtr->y;
}
if (dlPtr->y < dInfoPtr->y) {
yOffs = dInfoPtr->y - dlPtr->y;
lineHeight -= yOffs;
} else {
yOffs = 0;
}
Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0,
Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap);
sValuePtr = dlPtr->firstCharChunkPtr->stylePtr->sValuePtr;
rMargin = (sValuePtr->wrapMode == TEXT_WRAPMODE_NONE) ? 0 : sValuePtr->rMargin;
xIndent = GetLeftLineMargin(dlPtr, sValuePtr);
if (sValuePtr->lMarginColor != NULL) {
Tk_Fill3DRectangle(textPtr->tkwin, pixmap, sValuePtr->lMarginColor, 0, 0,
xIndent + dInfoPtr->x - dInfoPtr->curXPixelOffset,
dlPtr->height, 0, TK_RELIEF_FLAT);
}
if (sValuePtr->rMarginColor != NULL) {
Tk_Fill3DRectangle(textPtr->tkwin, pixmap, sValuePtr->rMarginColor,
dInfoPtr->maxX - rMargin + dInfoPtr->curXPixelOffset,
0, rMargin, dlPtr->height, 0, TK_RELIEF_FLAT);
}
yBase = dlPtr->spaceAbove;
height = dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow;
baseline = dlPtr->baseline - dlPtr->spaceAbove;
screenY = dlPtr->y + dlPtr->spaceAbove;
xOffs = dInfoPtr->x - dInfoPtr->curXPixelOffset;
if (dlPtr->cursorChunkPtr && textPtr->state == TK_TEXT_STATE_NORMAL) {
delayBlockCursorDrawing = dInfoPtr->insertFgGC && TkTextDrawBlockCursor(textPtr);
if (!delayBlockCursorDrawing) {
dlPtr->cursorChunkPtr->layoutProcs->displayProc(textPtr, dlPtr->cursorChunkPtr,
dlPtr->cursorChunkPtr->x + xOffs, yBase, height, baseline, display, pixmap, screenY);
}
}
for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
if (chunkPtr == dlPtr->cursorChunkPtr) {
continue;
}
if (chunkPtr->layoutProcs->displayProc) {
int x = chunkPtr->x + xOffs;
if (x + chunkPtr->width <= 0 || dInfoPtr->maxX <= x) {
x = -chunkPtr->width;
}
chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x, yBase, height,
baseline, display, pixmap, screenY);
if (dInfoPtr->dLinesInvalidated) {
return;
}
}
}
if (delayBlockCursorDrawing) {
int cxMin, cxMax, cWidth, cOffs;
GC bgGC;
assert(dInfoPtr->insertFgGC != None);
cxMin = dlPtr->cursorChunkPtr->x + xOffs;
cWidth = TkTextGetCursorWidth(textPtr, &cxMin, &cOffs);
if ((bgGC = dlPtr->cursorChunkPtr->stylePtr->bgGC) == None) {
Tk_3DBorder border;
if (!(border = dlPtr->cursorChunkPtr->stylePtr->sValuePtr->border)) {
border = textPtr->border;
}
bgGC = Tk_GCForColor(Tk_3DBorderColor(border), Tk_WindowId(textPtr->tkwin));
}
cxMin += cOffs;
cxMax = cxMin + cWidth;
#if CLIPPING_IS_WORKING
{
XRectangle crect;
crect.x = cxMin;
crect.y = yBase;
crect.width = cWidth;
crect.height = height;
XFillRectangle(display, pixmap, bgGC, crect.x, crect.y, crect.width, crect.height);
dlPtr->cursorChunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, cxMin, yBase, height,
baseline, display, pixmap, screenY);
XSetClipRectangles(display, dInfoPtr->insertFgGC, 0, 0, &crect, 1, Unsorted);
for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
int x = chunkPtr->x + xOffs;
if (x >= cxMax) {
break;
}
if (IsCharChunk(chunkPtr) && cxMin <= x + chunkPtr->width) {
GC fgGC = chunkPtr->stylePtr->fgGC;
chunkPtr->stylePtr->fgGC = dInfoPtr->insertFgGC;
chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x, yBase, height,
baseline, display, pixmap, screenY);
chunkPtr->stylePtr->fgGC = fgGC;
}
}
}
#else
{
Pixmap pm = Tk_GetPixmap(display, Tk_WindowId(textPtr->tkwin),
cWidth, height, Tk_Depth(textPtr->tkwin));
XFillRectangle(display, pm, bgGC, 0, 0, cWidth, height);
chunkPtr = dlPtr->cursorChunkPtr;
chunkPtr->layoutProcs->displayProc(textPtr, MarkPointer(chunkPtr),
cxMin, yBase, height, baseline, display, pm, screenY);
while (chunkPtr->prevPtr && chunkPtr->x + xOffs + chunkPtr->width > cxMin) {
chunkPtr = chunkPtr->prevPtr;
}
for ( ; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
int x = chunkPtr->x + xOffs;
if (x >= cxMax) {
break;
}
if (IsCharChunk(chunkPtr)) {
GC fgGC = chunkPtr->stylePtr->fgGC;
chunkPtr->stylePtr->fgGC = dInfoPtr->insertFgGC;
chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x - cxMin, 0,
height, baseline, display, pm, screenY);
chunkPtr->stylePtr->fgGC = fgGC;
}
}
XCopyArea(display, pm, pixmap, dInfoPtr->copyGC, 0, 0, cWidth, height, cxMin, yBase);
Tk_FreePixmap(display, pm);
}
#endif
}
xOffs = MIN(textPtr->padX, textPtr->insertWidth/2);
XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
dInfoPtr->x - xOffs, yOffs, dInfoPtr->maxX - dInfoPtr->x + 2*xOffs, lineHeight,
dInfoPtr->x - xOffs, dlPtr->y + yOffs);
DEBUG(stats.linesRedrawn += 1);
}
static bool
SameBackground(
const TextStyle *s1,
const TextStyle *s2)
{
return s1->sValuePtr->border == s2->sValuePtr->border
&& s1->sValuePtr->borderWidth == s2->sValuePtr->borderWidth
&& s1->sValuePtr->relief == s2->sValuePtr->relief
&& s1->sValuePtr->bgStipple == s2->sValuePtr->bgStipple
&& s1->sValuePtr->indentBg == s2->sValuePtr->indentBg;
}
static void
DisplayLineBackground(
TkText *textPtr,
DLine *dlPtr,
DLine *prevPtr,
Pixmap pixmap)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextDispChunk *chunkPtr;
TkTextDispChunk *chunkPtr2;
TkTextDispChunk *nextPtr2;
int leftX;
int leftXIn;
int rightX;
int rightX2;
int matchLeft;
int matchRight;
int minX, maxX, xOffset, xIndent, borderWidth;
StyleValues *sValuePtr;
Display *display;
const int y = 0;
display = Tk_Display(textPtr->tkwin);
minX = dInfoPtr->curXPixelOffset;
xOffset = dInfoPtr->x - minX;
maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
chunkPtr = dlPtr->chunkPtr;
xIndent = 0;
for (leftX = 0; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
if (chunkPtr->nextPtr && SameBackground(chunkPtr->nextPtr->stylePtr, chunkPtr->stylePtr)) {
continue;
}
sValuePtr = chunkPtr->stylePtr->sValuePtr;
rightX = chunkPtr->x + chunkPtr->width;
if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
if (chunkPtr->stylePtr->bgGC != None) {
int indent = 0;
if (rightX + xOffset <= 0) {
leftX = rightX;
continue;
}
if (leftX == 0 && sValuePtr->indentBg) {
xIndent = GetLeftLineMargin(dlPtr, sValuePtr);
if (leftX + xIndent > rightX) {
xIndent = rightX - leftX;
}
indent = xIndent;
}
if (leftX + xOffset + indent < -sValuePtr->borderWidth) {
leftX = -sValuePtr->borderWidth - xOffset - indent;
}
if (rightX - leftX - indent > 32767) {
rightX = leftX + indent + 32767;
}
borderWidth = sValuePtr->borderWidth;
if (leftX + sValuePtr->borderWidth > rightX) {
borderWidth = rightX - leftX;
}
XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,
leftX + xOffset + indent, y, rightX - leftX - indent, dlPtr->height);
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
leftX + xOffset + indent, y, borderWidth,
dlPtr->height, 1, sValuePtr->relief);
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX - borderWidth + xOffset, y, borderWidth,
dlPtr->height, 0, sValuePtr->relief);
}
}
leftX = rightX;
}
chunkPtr = dlPtr->chunkPtr;
leftX = 0;
leftXIn = 1;
rightX = chunkPtr->x + chunkPtr->width;
if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
chunkPtr2 = NULL;
if (prevPtr && prevPtr->chunkPtr) {
nextPtr2 = prevPtr->chunkPtr;
rightX2 = 0;
while (rightX2 <= leftX) {
if (!(chunkPtr2 = nextPtr2)) {
break;
}
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
} else {
nextPtr2 = NULL;
rightX2 = INT_MAX;
}
while (leftX < maxX) {
matchLeft = chunkPtr2 && SameBackground(chunkPtr2->stylePtr, chunkPtr->stylePtr);
sValuePtr = chunkPtr->stylePtr->sValuePtr;
if (rightX <= rightX2) {
if (!chunkPtr->nextPtr
|| !SameBackground(chunkPtr->stylePtr, chunkPtr->nextPtr->stylePtr)) {
if (!matchLeft && sValuePtr->relief != TK_RELIEF_FLAT) {
int indent = (leftX == 0) ? xIndent : 0;
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
sValuePtr->border, leftX + xOffset + indent, y,
rightX - leftX - indent, sValuePtr->borderWidth,
leftXIn, 1, 1, sValuePtr->relief);
}
leftX = rightX;
leftXIn = 1;
if (rightX == rightX2 && chunkPtr2) {
goto nextChunk2;
}
}
chunkPtr = chunkPtr->nextPtr;
if (!chunkPtr) {
break;
}
rightX = chunkPtr->x + chunkPtr->width;
if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
continue;
}
matchRight = nextPtr2 && SameBackground(nextPtr2->stylePtr, chunkPtr->stylePtr);
if (matchLeft && !matchRight) {
borderWidth = sValuePtr->borderWidth;
if (rightX2 - borderWidth < leftX) {
borderWidth = rightX2 - leftX;
}
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX2 - borderWidth + xOffset, y, borderWidth,
sValuePtr->borderWidth, 0, sValuePtr->relief);
}
leftX = rightX2 - borderWidth;
leftXIn = 0;
} else if (!matchLeft && matchRight && sValuePtr->relief != TK_RELIEF_FLAT) {
int indent = (leftX == 0) ? xIndent : 0;
borderWidth = sValuePtr->borderWidth;
if (rightX2 + borderWidth > rightX) {
borderWidth = rightX - rightX2;
}
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, rightX2 + xOffset,
y, borderWidth, sValuePtr->borderWidth, 1, sValuePtr->relief);
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
leftX + xOffset + indent, y,
rightX2 + borderWidth - leftX - indent,
sValuePtr->borderWidth, leftXIn, 0, 1,
sValuePtr->relief);
}
nextChunk2:
chunkPtr2 = nextPtr2;
if (!chunkPtr2) {
rightX2 = INT_MAX;
} else {
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
}
chunkPtr = dlPtr->chunkPtr;
leftX = 0;
leftXIn = 0;
rightX = chunkPtr->x + chunkPtr->width;
if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
chunkPtr2 = NULL;
if (dlPtr->nextPtr && dlPtr->nextPtr->chunkPtr) {
nextPtr2 = dlPtr->nextPtr->chunkPtr;
rightX2 = 0;
while (rightX2 <= leftX) {
chunkPtr2 = nextPtr2;
if (!chunkPtr2) {
break;
}
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
} else {
nextPtr2 = NULL;
rightX2 = INT_MAX;
}
while (leftX < maxX) {
matchLeft = chunkPtr2 && SameBackground(chunkPtr2->stylePtr, chunkPtr->stylePtr);
sValuePtr = chunkPtr->stylePtr->sValuePtr;
if (rightX <= rightX2) {
if (!chunkPtr->nextPtr
|| !SameBackground(chunkPtr->stylePtr, chunkPtr->nextPtr->stylePtr)) {
if (!matchLeft && sValuePtr->relief != TK_RELIEF_FLAT) {
int indent = (leftX == 0) ? xIndent : 0;
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
sValuePtr->border, leftX + xOffset + indent,
y + dlPtr->height - sValuePtr->borderWidth,
rightX - leftX - indent, sValuePtr->borderWidth,
leftXIn, 0, 0, sValuePtr->relief);
}
leftX = rightX;
leftXIn = 0;
if (rightX == rightX2 && chunkPtr2) {
goto nextChunk2b;
}
}
chunkPtr = chunkPtr->nextPtr;
if (!chunkPtr) {
break;
}
rightX = chunkPtr->x + chunkPtr->width;
if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
continue;
}
matchRight = nextPtr2 && SameBackground(nextPtr2->stylePtr, chunkPtr->stylePtr);
if (matchLeft && !matchRight) {
borderWidth = sValuePtr->borderWidth;
if (rightX2 - borderWidth < leftX) {
borderWidth = rightX2 - leftX;
}
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX2 - borderWidth + xOffset,
y + dlPtr->height - sValuePtr->borderWidth,
borderWidth, sValuePtr->borderWidth, 0,
sValuePtr->relief);
}
leftX = rightX2 - borderWidth;
leftXIn = 1;
} else if (!matchLeft && matchRight && sValuePtr->relief != TK_RELIEF_FLAT) {
int indent = (leftX == 0) ? xIndent : 0;
borderWidth = sValuePtr->borderWidth;
if (rightX2 + borderWidth > rightX) {
borderWidth = rightX - rightX2;
}
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
rightX2 + xOffset, y + dlPtr->height - sValuePtr->borderWidth,
borderWidth, sValuePtr->borderWidth, 1, sValuePtr->relief);
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
leftX + xOffset + indent, y + dlPtr->height - sValuePtr->borderWidth,
rightX2 + borderWidth - leftX - indent, sValuePtr->borderWidth,
leftXIn, 1, 0, sValuePtr->relief);
}
nextChunk2b:
chunkPtr2 = nextPtr2;
if (!chunkPtr2) {
rightX2 = INT_MAX;
} else {
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
}
}
static void
AsyncUpdateLineMetrics(
ClientData clientData)
{
TkText *textPtr = clientData;
TextDInfo *dInfoPtr;
if (TkTextReleaseIfDestroyed(textPtr)) {
return;
}
dInfoPtr = textPtr->dInfoPtr;
dInfoPtr->lineUpdateTimer = NULL;
if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
return;
}
if (dInfoPtr->flags & REDRAW_PENDING) {
dInfoPtr->flags |= ASYNC_PENDING|ASYNC_UPDATE;
return;
}
UpdateLineMetrics(textPtr, 256);
TK_TEXT_DEBUG(LogTextInvalidateLine(textPtr, 0));
if (TkRangeListIsEmpty(dInfoPtr->lineMetricUpdateRanges)) {
if (!dInfoPtr->pendingUpdateLineMetricsFinished) {
UpdateLineMetricsFinished(textPtr, false);
GetYView(textPtr->interp, textPtr, true);
}
TkTextDecrRefCountAndTestIfDestroyed(textPtr);
} else {
dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr);
}
}
static unsigned
NextLineNum(
TkTextLine *linePtr,
unsigned lineNum,
const TkTextIndex *indexPtr)
{
TkText *textPtr;
assert(indexPtr->textPtr);
if (linePtr->nextPtr == TkTextIndexGetLine(indexPtr)) {
return lineNum + 1;
}
textPtr = indexPtr->textPtr;
return TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, TkTextIndexGetLine(indexPtr), NULL);
}
static void
UpdateLineMetrics(
TkText *textPtr,
unsigned doThisMuch)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
const TkRange *range = TkRangeListFirst(dInfoPtr->lineMetricUpdateRanges);
unsigned maxDispLines = UINT_MAX;
unsigned count = 0;
assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
while (range) {
TkTextLine *linePtr;
TkTextLine *logicalLinePtr;
int lineNum = range->low;
int high = range->high;
linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
if (linePtr != logicalLinePtr) {
lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, logicalLinePtr, NULL);
linePtr = logicalLinePtr;
}
while (lineNum <= high) {
TkTextPixelInfo *pixelInfo;
TK_TEXT_DEBUG(LogTextInvalidateLine(textPtr, count));
pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
if (pixelInfo->epoch == dInfoPtr->lineMetricUpdateEpoch) {
int firstLineNum = lineNum;
if (linePtr->nextPtr->logicalLine) {
linePtr = linePtr->nextPtr;
lineNum += 1;
} else {
linePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
}
TkRangeListRemove(dInfoPtr->lineMetricUpdateRanges, firstLineNum, lineNum - 1);
} else {
TkTextIndex index;
TkTextIndexClear(&index, textPtr);
TkTextIndexSetToStartOfLine2(&index, linePtr);
if (textPtr->syncTime > 0) {
maxDispLines = (doThisMuch - count + 7)/8;
}
count += 8*TkTextUpdateOneLine(textPtr, linePtr, &index, maxDispLines);
if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
return;
}
lineNum = NextLineNum(linePtr, lineNum, &index);
linePtr = TkTextIndexGetLine(&index);
}
if ((++count >= doThisMuch)) {
return;
}
}
range = TkRangeListFirst(dInfoPtr->lineMetricUpdateRanges);
}
}
void
TkTextUpdateLineMetrics(
TkText *textPtr,
unsigned lineNum,
unsigned endLine)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
const TkRange *range;
assert(lineNum <= endLine);
assert(endLine <= TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
dInfoPtr->insideLineMetricUpdate = true;
if ((range = TkRangeListFindNearest(dInfoPtr->lineMetricUpdateRanges, lineNum))) {
TkTextLine *linePtr = NULL;
unsigned count = 0;
int high = range->high;
lineNum = range->low;
endLine = MIN(endLine, TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr) - 1);
while (true) {
const TkTextPixelInfo *pixelInfo;
if (lineNum > high) {
if (!(range = TkRangeListFindNearest(dInfoPtr->lineMetricUpdateRanges, lineNum))) {
break;
}
linePtr = NULL;
lineNum = range->low;
high = range->high;
}
if (lineNum > endLine) {
break;
}
if (!linePtr) {
linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
linePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
}
TK_TEXT_DEBUG(LogTextInvalidateLine(textPtr, count));
assert(linePtr->nextPtr);
pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
if (pixelInfo->epoch != dInfoPtr->lineMetricUpdateEpoch) {
TkTextIndex index;
TkTextIndexClear(&index, textPtr);
TkTextIndexSetToStartOfLine2(&index, linePtr);
TkTextUpdateOneLine(textPtr, linePtr, &index, UINT_MAX);
assert(IsStartOfNotMergedLine(&index) || TkTextIndexIsEndOfText(&index));
lineNum = NextLineNum(linePtr, lineNum, &index);
linePtr = TkTextIndexGetLine(&index);
} else {
int firstLineNum = lineNum;
if (linePtr->nextPtr->logicalLine) {
linePtr = linePtr->nextPtr;
lineNum += 1;
} else {
linePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
}
TkRangeListRemove(dInfoPtr->lineMetricUpdateRanges, firstLineNum, lineNum - 1);
}
}
}
dInfoPtr->insideLineMetricUpdate = false;
CheckIfLineMetricIsUpToDate(textPtr);
}
static void
ResetPixelInfo(
TkTextPixelInfo *pixelInfo)
{
TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
if (dispLineInfo) {
if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
dispLineInfo->numDispLines = dispLineInfo->entry[dispLineInfo->numDispLines].pixels;
}
}
pixelInfo->epoch = 0;
}
static void
StartAsyncLineCalculation(
TkText *textPtr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
return;
}
dInfoPtr->currChunkPtr = NULL;
InvokeAsyncUpdateLineMetrics(textPtr);
if (!(dInfoPtr->flags & ASYNC_UPDATE)) {
dInfoPtr->flags |= ASYNC_UPDATE;
TkTextGenerateWidgetViewSyncEvent(textPtr, false);
}
}
static void
TextInvalidateLineMetrics(
TkText *textPtr,
TkTextLine *linePtr,
unsigned lineCount,
TkTextInvalidateAction action)
{
TkRangeList *ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
unsigned totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
unsigned epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
bool isMonospaced = UseMonospacedLineHeights(textPtr);
unsigned lineNum = 0;
assert(linePtr || action == TK_TEXT_INVALIDATE_ONLY);
assert(TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL) + lineCount
< totalLines + (action == TK_TEXT_INVALIDATE_INSERT));
if (linePtr) {
int deviation;
lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, &deviation);
assert(lineNum < totalLines);
assert(deviation >= 0);
if (deviation) {
lineCount -= MIN(lineCount, deviation);
}
if (action != TK_TEXT_INVALIDATE_ONLY
&& !isMonospaced
&& linePtr == TkBTreeGetStartLine(textPtr)
&& lineCount + 1 >= totalLines) {
linePtr = NULL;
}
} else if (isMonospaced) {
linePtr = TkBTreeGetStartLine(textPtr);
lineCount = totalLines;
}
if (linePtr) {
if (TkRangeListSize(ranges) >= 200) {
int low = TkRangeListLow(ranges);
int high = TkRangeListHigh(ranges);
TkRangeListClear(ranges);
ranges = TkRangeListAdd(ranges, low, high);
}
switch (action) {
case TK_TEXT_INVALIDATE_ONLY: {
int counter = MIN(lineCount, totalLines - lineNum);
if (isMonospaced) {
TkBTreeUpdatePixelHeights(textPtr, linePtr, lineCount, epoch);
} else {
ranges = TkRangeListAdd(ranges, lineNum, lineNum + lineCount);
ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr));
if (!TkRangeListContainsRange(ranges, lineNum + 1, lineNum + counter)) {
for ( ; counter > 0; --counter) {
ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr = linePtr->nextPtr));
}
}
}
break;
}
case TK_TEXT_INVALIDATE_ELIDE: {
int counter = MIN(lineCount, totalLines - lineNum);
if (isMonospaced) {
TkBTreeUpdatePixelHeights(textPtr, linePtr, lineCount, epoch);
} else {
TkTextLine *mergedLinePtr = NULL;
unsigned count;
if (!linePtr->logicalLine) {
#if 1
assert(linePtr->prevPtr);
linePtr = linePtr->prevPtr;
lineNum -= 1;
lineCount += 1;
#else
TkTextLine *logicalLinePtr =
TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
count = TkBTreeCountLines(textPtr->sharedTextPtr->tree, logicalLinePtr, linePtr);
lineNum -= count;
lineCount += count;
#endif
}
ranges = TkRangeListAdd(ranges, lineNum, lineNum + lineCount);
count = 1;
for ( ; counter > 0; --counter, linePtr = linePtr->nextPtr) {
if (linePtr->logicalLine) {
if (mergedLinePtr) {
TkBTreeResetDisplayLineCounts(textPtr, mergedLinePtr, count);
mergedLinePtr = NULL;
}
ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr));
} else {
if (!mergedLinePtr) {
mergedLinePtr = linePtr;
count = 1;
} else {
count += 1;
}
}
}
if (mergedLinePtr) {
TkBTreeResetDisplayLineCounts(textPtr, mergedLinePtr, count);
}
}
break;
}
case TK_TEXT_INVALIDATE_DELETE:
textPtr->dInfoPtr->lastLineNo -= lineCount;
if (isMonospaced) {
return;
}
if (lineCount > 0) {
TkTextIndex index;
DLine *dlPtr;
TkRangeListDelete(ranges, lineNum + 1, lineNum + lineCount);
TkTextIndexClear(&index, textPtr);
TkTextIndexSetToStartOfLine2(&index, linePtr->nextPtr);
if ((dlPtr = FindDLine(textPtr, textPtr->dInfoPtr->dLinePtr, &index))) {
TkTextIndexSetToEndOfLine2(&index,
TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum + lineCount));
FreeDLines(textPtr, dlPtr, FindDLine(textPtr, dlPtr, &index), DLINE_UNLINK);
}
}
ranges = TkRangeListAdd(ranges, lineNum, lineNum);
ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr));
break;
case TK_TEXT_INVALIDATE_INSERT:
if (lineCount > 0 && lineNum + 1 < totalLines) {
int lastLine = MIN(lineNum + lineCount, totalLines - 1);
ranges = TkRangeListInsert(ranges, lineNum + 1, lastLine);
}
textPtr->dInfoPtr->lastLineNo += lineCount;
if (isMonospaced) {
TkBTreeUpdatePixelHeights(textPtr, linePtr, lineCount, epoch);
} else {
ranges = TkRangeListAdd(ranges, lineNum, lineNum);
ResetPixelInfo(TkBTreeLinePixelInfo(textPtr,
TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr)));
}
break;
}
assert(TkRangeListIsEmpty(ranges) || TkRangeListHigh(ranges) < totalLines);
} else {
textPtr->dInfoPtr->lineMetricUpdateEpoch += 1;
if (action == TK_TEXT_INVALIDATE_DELETE) {
TkRangeListClear(ranges);
FreeDLines(textPtr, textPtr->dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
totalLines -= lineCount;
textPtr->dInfoPtr->lastLineNo -= lineCount;
} else if (action == TK_TEXT_INVALIDATE_INSERT) {
textPtr->dInfoPtr->lastLineNo += lineCount;
}
ranges = TkRangeListAdd(ranges, 0, totalLines - 1);
}
FreeDLines(textPtr, NULL, NULL, DLINE_CACHE);
FreeDLines(textPtr, NULL, NULL, DLINE_METRIC);
FreeDLines(textPtr, textPtr->dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
textPtr->dInfoPtr->lineMetricUpdateRanges = ranges;
textPtr->dInfoPtr->currChunkPtr = NULL;
if (textPtr->syncTime == 0) {
#if 0
textPtr->dInfoPtr->lastLineNo = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL);
#endif
} else {
StartAsyncLineCalculation(textPtr);
}
}
void
TkTextInvalidateLineMetrics(
TkSharedText *sharedTextPtr,
TkText *textPtr,
TkTextLine *linePtr,
unsigned lineCount,
TkTextInvalidateAction action)
{
if (!sharedTextPtr) {
if (textPtr->sharedTextPtr->allowUpdateLineMetrics) {
TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
}
} else if (sharedTextPtr->allowUpdateLineMetrics) {
textPtr = sharedTextPtr->peers;
while (textPtr) {
int numLines = lineCount;
TkTextLine *firstLinePtr = linePtr;
if (textPtr->startMarker != sharedTextPtr->startMarker) {
TkTextLine *startLinePtr = TkBTreeGetStartLine(textPtr);
unsigned lineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, firstLinePtr, NULL);
unsigned firstLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, startLinePtr, NULL);
if (firstLineNo > lineNo) {
firstLinePtr = startLinePtr;
numLines -= firstLineNo - lineNo;
}
}
if (textPtr->endMarker != sharedTextPtr->endMarker) {
TkTextLine *lastLinePtr = TkBTreeGetLastLine(textPtr);
unsigned lineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, firstLinePtr, NULL);
unsigned endLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, lastLinePtr, NULL);
if (endLineNo <= lineNo + numLines) {
numLines = endLineNo - lineNo - 1;
}
}
if (numLines >= 0) {
TextInvalidateLineMetrics(textPtr, firstLinePtr, numLines, action);
}
textPtr = textPtr->next;
}
}
}
void
TkTextFindDisplayIndex(
TkText *textPtr,
TkTextIndex *indexPtr,
int displayLineOffset,
int *xOffset)
{
DisplayInfo info;
TkTextLine *linePtr;
TkTextLine *lastLinePtr;
unsigned byteOffset;
bool upToDate;
int myXOffset;
assert(textPtr);
if (!xOffset) {
xOffset = &myXOffset;
}
lastLinePtr = TkBTreeGetLastLine(textPtr);
linePtr = TkTextIndexGetLine(indexPtr);
if (displayLineOffset >= 0 && linePtr == lastLinePtr) {
*xOffset = 0;
return;
}
if (displayLineOffset <= 0 && TkTextIndexIsStartOfText(indexPtr)) {
*xOffset = 0;
return;
}
if (linePtr == lastLinePtr) {
displayLineOffset += 1;
*xOffset = 0;
xOffset = NULL;
TkTextIndexSetToLastChar2(indexPtr, linePtr->prevPtr);
}
if (displayLineOffset > 0) {
upToDate = TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges);
} else {
upToDate = TestIfLinesUpToDate(indexPtr);
}
linePtr = ComputeDisplayLineInfo(textPtr, indexPtr, &info);
if (xOffset) {
if (IsStartOfNotMergedLine(indexPtr)) {
*xOffset = 0;
} else {
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr = info.lastDLinePtr;
TkTextIndex index = *indexPtr;
TkTextIndexBackBytes(textPtr, &index, info.byteOffset, &index);
if (!dlPtr) {
dlPtr = FindCachedDLine(textPtr, indexPtr);
if (!dlPtr
&& !(dInfoPtr->flags & DINFO_OUT_OF_DATE)
&& TkTextIndexCompare(indexPtr, &textPtr->topIndex) >= 0) {
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
}
if (!dlPtr) {
dlPtr = LayoutDLine(&index, info.displayLineNo);
FreeDLines(textPtr, dlPtr, NULL, DLINE_CACHE);
}
}
*xOffset = DLineXOfIndex(textPtr, dlPtr, TkTextIndexCountBytes(&dlPtr->index, indexPtr));
}
}
if (upToDate) {
const TkTextDispLineInfo *dispLineInfo;
assert(!info.dLinePtr);
if (displayLineOffset == 0) {
byteOffset = info.entry->byteOffset;
} else {
if (displayLineOffset > 0) {
linePtr = TkBTreeNextDisplayLine(textPtr, linePtr, &info.displayLineNo,
displayLineOffset);
} else {
linePtr = TkBTreePrevDisplayLine(textPtr, linePtr, &info.displayLineNo,
-displayLineOffset);
}
dispLineInfo = TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo;
byteOffset = dispLineInfo ? dispLineInfo->entry[info.displayLineNo].byteOffset : 0;
}
} else {
unsigned removedLines;
removedLines = 0;
if (info.lastDLinePtr) {
DLine *prevPtr = info.lastDLinePtr->prevPtr;
FreeDLines(textPtr, info.lastDLinePtr, NULL, DLINE_CACHE);
if (info.dLinePtr == info.lastDLinePtr) { info.dLinePtr = NULL; }
info.lastDLinePtr = prevPtr;
info.numCachedLines -= 1;
removedLines = 1;
}
TkTextIndexBackBytes(textPtr, indexPtr, info.byteOffset, indexPtr);
if (displayLineOffset > 0) {
ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, displayLineOffset);
info.numDispLines -= info.displayLineNo;
while (true) {
const TkTextDispLineEntry *last;
if (info.numDispLines >= displayLineOffset) {
last = info.entry + displayLineOffset;
byteOffset = last->byteOffset;
break;
}
last = info.entry + info.numDispLines;
byteOffset = last->byteOffset;
displayLineOffset -= info.numDispLines;
TkTextIndexForwBytes(textPtr, indexPtr, byteOffset, indexPtr);
linePtr = TkTextIndexGetLine(indexPtr);
if (linePtr == lastLinePtr) {
break;
}
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
ComputeDisplayLineInfo(textPtr, indexPtr, &info);
ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, displayLineOffset);
assert(info.displayLineNo == 0);
}
} else if (displayLineOffset < 0) {
info.numDispLines = info.displayLineNo + 1;
while (true) {
TkTextLine *prevLine;
if (-displayLineOffset < info.numDispLines) {
int skipBack;
byteOffset = (info.entry + displayLineOffset)->byteOffset;
skipBack = displayLineOffset;
if ((skipBack -= removedLines) >= 0 && info.numCachedLines > skipBack) {
DLine *dlPtr = info.lastDLinePtr;
while (dlPtr && skipBack--) {
dlPtr = dlPtr->prevPtr;
}
if (dlPtr == info.dLinePtr) {
info.dLinePtr = dlPtr->nextPtr;
}
if (dlPtr == info.lastDLinePtr) {
info.lastDLinePtr = dlPtr->prevPtr;
}
FreeDLines(textPtr, dlPtr, NULL, DLINE_CACHE);
}
break;
}
displayLineOffset += info.numDispLines;
if (!(prevLine = TkBTreePrevLine(textPtr, linePtr))) {
byteOffset = info.entry[0].byteOffset;
break;
}
TkTextIndexSetToLastChar2(indexPtr, linePtr = prevLine);
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
linePtr = ComputeDisplayLineInfo(textPtr, indexPtr, &info);
removedLines = 0;
}
} else {
byteOffset = info.entry[0].byteOffset;
}
if (info.lastDLinePtr) {
FreeDLines(textPtr, info.lastDLinePtr, NULL, DLINE_CACHE);
if (info.dLinePtr == info.lastDLinePtr) { info.dLinePtr = NULL; }
}
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
DEBUG(indexPtr->discardConsistencyCheck = true);
TkTextIndexSetByteIndex2(indexPtr, linePtr, 0);
DEBUG(indexPtr->discardConsistencyCheck = false);
TkTextIndexForwBytes(textPtr, indexPtr, byteOffset, indexPtr);
}
unsigned
TkTextCountDisplayLines(
TkText *textPtr,
const TkTextIndex *indexFrom,
const TkTextIndex *indexTo)
{
const TkTextPixelInfo *pixelInfo1;
const TkTextPixelInfo *pixelInfo2;
TkTextDispLineInfo *dispLineInfo;
TkTextDispLineEntry *entry;
TkTextDispLineEntry *lastEntry;
TkTextLine *linePtr1;
TkTextLine *linePtr2;
TkTextIndex index;
unsigned byteOffset;
int numLines;
assert(TkTextIndexCompare(indexFrom, indexTo) <= 0);
assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
TkTextUpdateLineMetrics(textPtr, TkTextIndexGetLineNumber(indexFrom, textPtr),
TkTextIndexGetLineNumber(indexTo, textPtr));
linePtr1 = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, TkTextIndexGetLine(indexFrom));
linePtr2 = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, TkTextIndexGetLine(indexTo));
pixelInfo1 = linePtr1->pixelInfo;
pixelInfo2 = linePtr2->pixelInfo;
if (!pixelInfo1->dispLineInfo) {
numLines = 0;
} else {
index = *indexFrom;
TkTextIndexSetToStartOfLine2(&index, linePtr1);
byteOffset = TkTextIndexCountBytes(&index, indexFrom);
dispLineInfo = pixelInfo1->dispLineInfo;
lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
entry = SearchDispLineEntry(dispLineInfo->entry, lastEntry, byteOffset);
numLines = -(entry - dispLineInfo->entry);
}
while (true) {
if (pixelInfo1->dispLineInfo) {
if (pixelInfo1 == pixelInfo2) {
index = *indexTo;
TkTextIndexSetToStartOfLine2(&index, linePtr2);
byteOffset = TkTextIndexCountBytes(&index, indexTo);
dispLineInfo = pixelInfo2->dispLineInfo;
lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
entry = SearchDispLineEntry(dispLineInfo->entry, lastEntry, byteOffset);
return numLines + (entry - dispLineInfo->entry);
}
numLines += pixelInfo1->dispLineInfo->numDispLines;
} else if (pixelInfo1 == pixelInfo2) {
return numLines;
} else {
numLines += 1;
}
linePtr1 = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr1);
pixelInfo1 = linePtr1->pixelInfo;
}
return 0;
}
static void
FindDisplayLineStartEnd(
TkText *textPtr,
TkTextIndex *indexPtr,
bool end,
int cacheType)
{
DisplayInfo info;
int byteCount;
if (TkTextIndexGetLine(indexPtr) == TkBTreeGetLastLine(textPtr)
|| (!end && IsStartOfNotMergedLine(indexPtr))) {
return;
}
ComputeDisplayLineInfo(textPtr, indexPtr, &info);
byteCount = end ? -(info.nextByteOffset - 1) : info.byteOffset;
TkTextIndexBackBytes(textPtr, indexPtr, byteCount, indexPtr);
if (end) {
int offset;
int skipBack = 0;
TkTextSegment *segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
char const *p = segPtr->body.chars + offset;
while (p > segPtr->body.chars && (*p & 0xc0) == 0x80) {
p -= 1;
skipBack += 1;
}
TkTextIndexBackBytes(textPtr, indexPtr, skipBack, indexPtr);
}
if (info.lastDLinePtr) {
FreeDLines(textPtr, info.lastDLinePtr, NULL, cacheType);
if (info.dLinePtr == info.lastDLinePtr) {
info.dLinePtr = NULL;
}
}
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
void
TkTextFindDisplayLineStartEnd(
TkText *textPtr,
TkTextIndex *indexPtr,
bool end)
{
return FindDisplayLineStartEnd(textPtr, indexPtr, end, DLINE_CACHE);
}
#if !NDEBUG
static bool
IsAtStartOfDisplayLine(
const TkTextIndex *indexPtr)
{
TkTextIndex index2 = *indexPtr;
assert(indexPtr->textPtr);
FindDisplayLineStartEnd(indexPtr->textPtr, &index2, DISP_LINE_START, DLINE_METRIC);
return TkTextIndexCompare(&index2, indexPtr) == 0;
}
#endif
static int
CalculateDisplayLineHeight(
TkText *textPtr,
const TkTextIndex *indexPtr,
unsigned *byteCountRef)
{
DisplayInfo info;
assert(IsAtStartOfDisplayLine(indexPtr));
if (TkTextIndexGetLine(indexPtr) == TkBTreeGetLastLine(textPtr)) {
if (byteCountRef) { *byteCountRef = 0; }
return 0;
}
ComputeDisplayLineInfo(textPtr, indexPtr, &info);
if (info.lastDLinePtr) {
FreeDLines(textPtr, info.lastDLinePtr, NULL, DLINE_METRIC);
if (info.dLinePtr == info.lastDLinePtr) {
info.dLinePtr = NULL;
}
}
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
if (byteCountRef) { *byteCountRef = info.nextByteOffset + info.byteOffset; }
assert(info.entry->height != 0xffffffff);
return info.entry->height;
}
void
TkTextGetViewOffset(
TkText *textPtr,
int *x,
int *y)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (dInfoPtr && dInfoPtr->dLinePtr) {
*x = dInfoPtr->curXPixelOffset;
*y = dInfoPtr->curYPixelOffset;
} else {
*x = 0;
*y = 0;
}
}
static unsigned
GetPixelsTo(
TkText *textPtr,
const TkTextIndex *indexPtr,
bool inclusiveLastLine,
DisplayInfo *info)
{
TkTextLine *logicalLinePtr;
const TkTextPixelInfo *pixelInfo;
TkTextDispLineInfo *dispLineInfo;
const TkTextDispLineEntry *lastEntry;
const TkTextDispLineEntry *entry;
TkTextIndex index;
unsigned byteOffset;
logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr,
TkTextIndexGetLine(indexPtr));
if (logicalLinePtr == TkBTreeGetLastLine(textPtr)) {
return 0;
}
pixelInfo = TkBTreeLinePixelInfo(textPtr, logicalLinePtr);
if (!info && (pixelInfo->epoch & EPOCH_MASK) != textPtr->dInfoPtr->lineMetricUpdateEpoch) {
return 0;
}
if (!(dispLineInfo = pixelInfo->dispLineInfo)) {
return inclusiveLastLine ? pixelInfo->height : 0;
}
index = *indexPtr;
TkTextIndexSetToStartOfLine2(&index, logicalLinePtr);
byteOffset = TkTextIndexCountBytes(&index, indexPtr);
lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
entry = SearchDispLineEntry(dispLineInfo->entry, lastEntry, byteOffset);
if (entry == lastEntry) {
if (info) {
unsigned numDispLinesSoFar = dispLineInfo->numDispLines;
ComputeMissingMetric(textPtr, info, THRESHOLD_BYTE_OFFSET, byteOffset);
lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
entry = SearchDispLineEntry(dispLineInfo->entry + numDispLinesSoFar, lastEntry, byteOffset);
if (entry == lastEntry) {
entry -= 1;
}
} else {
assert(dispLineInfo->numDispLines > 0);
entry -= 1;
}
} else if (!inclusiveLastLine && entry-- == dispLineInfo->entry) {
return 0;
}
return entry->pixels;
}
int
TkTextIndexYPixels(
TkText *textPtr,
const TkTextIndex *indexPtr)
{
return TkBTreePixelsTo(textPtr, TkTextIndexGetLine(indexPtr)) +
GetPixelsTo(textPtr, indexPtr, false, NULL);
}
int
TkTextUpdateOneLine(
TkText *textPtr,
TkTextLine *linePtr,
TkTextIndex *indexPtr,
unsigned maxDispLines)
{
TkTextIndex index;
TkTextLine *logicalLinePtr;
TkTextPixelInfo *pixelInfo;
unsigned displayLines;
unsigned updateCounter;
unsigned pixelHeight;
assert(linePtr != TkBTreeGetLastLine(textPtr));
if (!indexPtr) {
TkTextIndexClear(&index, textPtr);
TkTextIndexSetToStartOfLine2(&index, linePtr);
indexPtr = &index;
}
linePtr = TkTextIndexGetLine(indexPtr);
logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
pixelInfo = TkBTreeLinePixelInfo(textPtr, logicalLinePtr);
if (pixelInfo->epoch == (textPtr->dInfoPtr->lineMetricUpdateEpoch | PARTIAL_COMPUTED_BIT)) {
const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
unsigned bytes;
assert(dispLineInfo);
assert(dispLineInfo->numDispLines > 0);
bytes = dispLineInfo->entry[dispLineInfo->numDispLines].byteOffset;
bytes -= dispLineInfo->entry[0].byteOffset;
TkTextIndexSetToStartOfLine2(indexPtr, logicalLinePtr);
TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr);
linePtr = TkTextIndexGetLine(indexPtr);
assert(!linePtr->logicalLine || !TkTextIndexIsStartOfLine(indexPtr));
} else if (!linePtr->logicalLine || !TkTextIndexIsStartOfLine(indexPtr)) {
FindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_START, DLINE_METRIC);
linePtr = TkTextIndexGetLine(indexPtr);
}
assert(linePtr->nextPtr);
updateCounter = textPtr->dInfoPtr->lineMetricUpdateCounter;
pixelHeight = 0;
displayLines = 0;
while (true) {
unsigned bytes, height;
bool atEnd;
height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes);
atEnd = TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr) == 1
|| TkTextIndexIsEndOfText(indexPtr);
assert(bytes > 0);
if (height > 0) {
pixelHeight += height;
displayLines += 1;
}
if (atEnd) {
break;
}
if (linePtr != TkTextIndexGetLine(indexPtr)) {
if (TkTextIndexGetLine(indexPtr)->logicalLine) {
break;
}
linePtr = TkTextIndexGetLine(indexPtr);
} else {
}
if (displayLines == maxDispLines) {
assert(pixelInfo->epoch & PARTIAL_COMPUTED_BIT);
break;
}
}
if (updateCounter != textPtr->dInfoPtr->lineMetricUpdateCounter) {
if (tkTextDebug) {
char buffer[2*TCL_INTEGER_SPACE + 1];
if (!TkBTreeNextLine(textPtr, linePtr)) {
Tcl_Panic("Must never ever update line height of last artificial line");
}
pixelHeight = TkBTreeNumPixels(textPtr);
snprintf(buffer, sizeof(buffer), "%u %u",
TkBTreeLinesTo(indexPtr->tree, textPtr, linePtr, NULL), pixelHeight);
LOG("tk_textNumPixels", buffer);
}
if (!textPtr->dInfoPtr->scrollbarTimer) {
InvokeAsyncUpdateYScrollbar(textPtr);
}
}
return displayLines;
}
static void
DisplayText(
ClientData clientData)
{
TkText *textPtr = clientData;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
Pixmap pixmap;
int maxHeight, borders;
int bottomY = 0;
Tcl_Interp *interp;
#ifdef MAC_OSX_TK
TkWindow *winPtr = (TkWindow *)(textPtr->tkwin);
MacDrawable *macWin = winPtr->privatePtr;
if (macWin && (macWin->flags & TK_DO_NOT_DRAW)) {
dInfoPtr->flags &= ~REDRAW_PENDING;
if (dInfoPtr->flags & ASYNC_PENDING) {
assert(dInfoPtr->flags & ASYNC_UPDATE);
dInfoPtr->flags &= ~ASYNC_PENDING;
InvokeAsyncUpdateLineMetrics(textPtr);
}
return;
}
#endif
if (textPtr->flags & DESTROYED) {
return;
}
interp = textPtr->interp;
Tcl_Preserve(interp);
TK_TEXT_DEBUG(Tcl_SetVar2(interp, "tk_textRelayout", NULL, "", TCL_GLOBAL_ONLY));
if (!Tk_IsMapped(textPtr->tkwin) || dInfoPtr->maxX <= dInfoPtr->x || dInfoPtr->maxY <= dInfoPtr->y) {
UpdateDisplayInfo(textPtr);
dInfoPtr->flags &= ~REDRAW_PENDING;
goto doScrollbars;
}
DEBUG(stats.numRedisplays += 1);
TK_TEXT_DEBUG(Tcl_SetVar2(interp, "tk_textRedraw", NULL, "", TCL_GLOBAL_ONLY));
if (dInfoPtr->flags & REPICK_NEEDED) {
textPtr->refCount += 1;
dInfoPtr->flags &= ~REPICK_NEEDED;
dInfoPtr->currChunkPtr = NULL;
TkTextPickCurrent(textPtr, &textPtr->pickEvent);
if (TkTextDecrRefCountAndTestIfDestroyed(textPtr)) {
goto end;
}
}
UpdateDisplayInfo(textPtr);
dInfoPtr->dLinesInvalidated = false;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
DLine *dlPtr2;
int offset, height, y, oldY;
TkRegion damageRgn;
if ((dlPtr->flags & OLD_Y_INVALID)
|| dlPtr->y == dlPtr->oldY
|| ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY && dlPtr->y < dlPtr->oldY)
|| (dlPtr->oldY < dInfoPtr->y && dlPtr->y > dlPtr->oldY)) {
continue;
}
offset = dlPtr->y - dlPtr->oldY;
height = dlPtr->height;
y = dlPtr->y;
for (dlPtr2 = dlPtr->nextPtr; dlPtr2; dlPtr2 = dlPtr2->nextPtr) {
if ((dlPtr2->flags & OLD_Y_INVALID)
|| dlPtr2->oldY + offset != dlPtr2->y
|| dlPtr2->oldY + dlPtr2->height > dInfoPtr->maxY) {
break;
}
height += dlPtr2->height;
}
if (y + height > dInfoPtr->maxY) {
height = dInfoPtr->maxY - y;
}
oldY = dlPtr->oldY;
if (y < dInfoPtr->y) {
int y_off = dInfoPtr->y - dlPtr->y;
height -= y_off;
oldY += y_off;
y = dInfoPtr->y;
}
#if 0
assert(height > 0);
#else
if (height <= 0) {
fprintf(stderr, "DisplayText: height <= 0 is unexpected\n");
}
#endif
while (true) {
dlPtr->oldY = dlPtr->y;
if (dlPtr->nextPtr == dlPtr2) {
break;
}
dlPtr = dlPtr->nextPtr;
}
for ( ; dlPtr2; dlPtr2 = dlPtr2->nextPtr) {
if (!(dlPtr2->flags & OLD_Y_INVALID)
&& dlPtr2->oldY + dlPtr2->height > y
&& dlPtr2->oldY < y + height) {
dlPtr2->flags |= OLD_Y_INVALID;
}
}
damageRgn = TkCreateRegion();
if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, dInfoPtr->x,
oldY, dInfoPtr->maxX - dInfoPtr->x, height, 0, y - oldY, damageRgn)) {
#ifdef MAC_OSX_TK
#else
TextInvalidateRegion(textPtr, damageRgn);
#endif
}
DEBUG(stats.numCopies += 1);
TkDestroyRegion(damageRgn);
if (y != oldY) {
textPtr->configureBboxTree = true;
}
}
dInfoPtr->flags &= ~REDRAW_PENDING;
if (dInfoPtr->flags & REDRAW_BORDERS) {
TK_TEXT_DEBUG(LOG("tk_textRedraw", "borders"));
if (!textPtr->tkwin) {
goto end;
}
Tk_Draw3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, textPtr->highlightWidth,
textPtr->highlightWidth,
Tk_Width(textPtr->tkwin) - 2*textPtr->highlightWidth,
Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth,
textPtr->borderWidth, textPtr->relief);
if (textPtr->highlightWidth != 0) {
GC fgGC, bgGC;
bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr, Tk_WindowId(textPtr->tkwin));
if (textPtr->flags & HAVE_FOCUS) {
fgGC = Tk_GCForColor(textPtr->highlightColorPtr, Tk_WindowId(textPtr->tkwin));
TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC,
textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
} else {
TkpDrawHighlightBorder(textPtr->tkwin, bgGC, bgGC,
textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
}
}
borders = textPtr->borderWidth + textPtr->highlightWidth;
if (textPtr->padY > 0) {
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, borders, borders,
Tk_Width(textPtr->tkwin) - 2*borders, textPtr->padY,
0, TK_RELIEF_FLAT);
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, borders,
Tk_Height(textPtr->tkwin) - borders - textPtr->padY,
Tk_Width(textPtr->tkwin) - 2*borders,
textPtr->padY, 0, TK_RELIEF_FLAT);
}
if (textPtr->padX > 0) {
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, borders, borders + textPtr->padY,
textPtr->padX,
Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
0, TK_RELIEF_FLAT);
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border,
Tk_Width(textPtr->tkwin) - borders - textPtr->padX,
borders + textPtr->padY, textPtr->padX,
Tk_Height(textPtr->tkwin) - 2*borders -2*textPtr->padY,
0, TK_RELIEF_FLAT);
}
dInfoPtr->flags &= ~REDRAW_BORDERS;
}
maxHeight = -1;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
if (dlPtr->height > maxHeight && ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y)) {
maxHeight = dlPtr->height;
}
bottomY = dlPtr->y + dlPtr->height;
}
if (maxHeight > dInfoPtr->maxY + dInfoPtr->topPixelOffset) {
maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset);
}
if (maxHeight > 0) {
pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
maxHeight, Tk_Depth(textPtr->tkwin));
for (dlPtr = dInfoPtr->dLinePtr; dlPtr && dlPtr->y < dInfoPtr->maxY; dlPtr = dlPtr->nextPtr) {
if (!dlPtr->chunkPtr) {
continue;
}
if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) {
if (tkTextDebug) {
char string[TK_POS_CHARS];
TkTextPrintIndex(textPtr, &dlPtr->index, string);
LOG("tk_textRedraw", string);
}
DisplayDLine(textPtr, dlPtr, dlPtr->prevPtr, pixmap);
if (dInfoPtr->dLinesInvalidated) {
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
goto doScrollbars;
}
dlPtr->oldY = dlPtr->y;
dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID);
} else if (dInfoPtr->countWindows > 0
&& dlPtr->chunkPtr
&& (dlPtr->y < 0 || dlPtr->y + dlPtr->height > dInfoPtr->maxY)) {
TkTextDispChunk *chunkPtr;
for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
int x;
if (chunkPtr->layoutProcs->type != TEXT_DISP_WINDOW) {
continue;
}
x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
if (x + chunkPtr->width <= 0 || x >= dInfoPtr->maxX) {
x = -chunkPtr->width;
}
chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x,
dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, NULL,
(Drawable) None, dlPtr->y + dlPtr->spaceAbove);
}
}
}
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
}
if (dInfoPtr->topOfEof > dInfoPtr->maxY) {
dInfoPtr->topOfEof = dInfoPtr->maxY;
}
if (bottomY < dInfoPtr->topOfEof) {
TK_TEXT_DEBUG(LOG("tk_textRedraw", "eof"));
if (textPtr->flags & DESTROYED) {
goto end;
}
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,
dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
dInfoPtr->topOfEof - bottomY, 0, TK_RELIEF_FLAT);
}
dInfoPtr->topOfEof = bottomY;
doScrollbars:
if (textPtr->flags & UPDATE_SCROLLBARS) {
textPtr->flags &= ~UPDATE_SCROLLBARS;
if (textPtr->yScrollCmd || textPtr->watchCmd) {
GetYView(textPtr->interp, textPtr, true);
}
if (textPtr->xScrollCmd || textPtr->watchCmd) {
GetXView(textPtr->interp, textPtr, true);
}
if (!(TriggerWatchCursor(textPtr))) {
goto end;
}
}
if (dInfoPtr->flags & ASYNC_PENDING) {
assert(dInfoPtr->flags & ASYNC_UPDATE);
dInfoPtr->flags &= ~ASYNC_PENDING;
InvokeAsyncUpdateLineMetrics(textPtr);
}
end:
Tcl_Release(interp);
}
void
TkTextEventuallyRepick(
TkText *textPtr)
{
textPtr->dInfoPtr->flags |= REPICK_NEEDED;
DisplayTextWhenIdle(textPtr);
}
void
TkTextRedrawRegion(
TkText *textPtr,
int x, int y,
int width, int height)
{
TkRegion damageRgn = TkCreateRegion();
XRectangle rect;
rect.x = x;
rect.y = y;
rect.width = width;
rect.height = height;
TkUnionRectWithRegion(&rect, damageRgn, damageRgn);
TextInvalidateRegion(textPtr, damageRgn);
TkDestroyRegion(damageRgn);
DisplayTextWhenIdle(textPtr);
}
static void
TextInvalidateRegion(
TkText *textPtr,
TkRegion region)
{
DLine *dlPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
int maxY, inset;
XRectangle rect;
TkClipBox(region, &rect);
maxY = rect.y + rect.height;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
if (!(dlPtr->flags & OLD_Y_INVALID)
&& TkRectInRegion(region, rect.x, dlPtr->y, rect.width, dlPtr->height) != RectangleOut) {
dlPtr->flags |= OLD_Y_INVALID;
}
}
if (dInfoPtr->topOfEof < maxY) {
dInfoPtr->topOfEof = maxY;
}
dInfoPtr->currChunkPtr = NULL;
inset = textPtr->borderWidth + textPtr->highlightWidth;
if (rect.x < inset + textPtr->padX
|| rect.y < inset + textPtr->padY
|| (int) (rect.x + rect.width) > Tk_Width(textPtr->tkwin) - inset - textPtr->padX
|| maxY > Tk_Height(textPtr->tkwin) - inset - textPtr->padY) {
dInfoPtr->flags |= REDRAW_BORDERS;
}
}
static void
TextChanged(
TkText *textPtr,
const TkTextIndex *index1Ptr,
const TkTextIndex *index2Ptr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextLine *lastLinePtr = TkBTreeGetLastLine(textPtr);
DLine *firstPtr = NULL;
DLine *lastPtr= NULL;
TkTextIndex rounded;
TkTextLine *linePtr;
if ((linePtr = TkTextIndexGetLine(index1Ptr)) != lastLinePtr) {
rounded = *index1Ptr;
TkTextIndexSetLine(&rounded, TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr));
if (!(firstPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded))) {
} else {
rounded = *index2Ptr;
linePtr = TkTextIndexGetLine(index2Ptr);
if (linePtr == lastLinePtr) {
linePtr = NULL;
} else {
linePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
TkTextIndexSetLine(&rounded, linePtr);
}
if (!linePtr) {
lastPtr = NULL;
} else {
lastPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded);
if (lastPtr && lastPtr == firstPtr) {
lastPtr = lastPtr->nextPtr;
}
}
}
}
DisplayTextWhenIdle(textPtr);
dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
dInfoPtr->currChunkPtr = NULL;
FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK_KEEP_BRKS);
}
void
TkTextChanged(
TkSharedText *sharedTextPtr,
TkText *textPtr,
const TkTextIndex *index1Ptr,
const TkTextIndex *index2Ptr)
{
assert(!sharedTextPtr != !textPtr);
if (!sharedTextPtr) {
TextChanged(textPtr, index1Ptr, index2Ptr);
} else {
TkTextIndex index1 = *index1Ptr;
TkTextIndex index2 = *index2Ptr;
for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
DEBUG(index1.discardConsistencyCheck = true);
DEBUG(index2.discardConsistencyCheck = true);
TkTextIndexSetPeer(&index1, textPtr);
TkTextIndexSetPeer(&index2, textPtr);
TextChanged(textPtr, &index1, &index2);
}
}
}
static void
TextRedrawTag(
TkText *textPtr,
const TkTextIndex *index1Ptr,
const TkTextIndex *index2Ptr,
bool affectsDisplayGeometry)
{
TextDInfo *dInfoPtr;
DLine *dlPtr;
DLine *endPtr;
if (textPtr->flags & DESTROYED) {
return;
}
assert(index1Ptr);
assert(index2Ptr);
assert(textPtr);
dInfoPtr = textPtr->dInfoPtr;
dlPtr = dInfoPtr->dLinePtr;
if (!dlPtr) {
return;
}
if (affectsDisplayGeometry) {
TkTextLine *startLine, *endLine;
unsigned lineCount;
dInfoPtr->currChunkPtr = NULL;
endLine = TkTextIndexGetLine(index2Ptr);
if (endLine == textPtr->endMarker->sectionPtr->linePtr) {
assert(endLine->prevPtr);
endLine = endLine->prevPtr;
}
lineCount = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, endLine, NULL);
startLine = TkTextIndexGetLine(index1Ptr);
lineCount -= TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, startLine, NULL);
TkTextInvalidateLineMetrics(NULL, textPtr, startLine, lineCount, TK_TEXT_INVALIDATE_ONLY);
}
if (TkTextIndexCompare(&dlPtr->index, index1Ptr) > 0) {
index1Ptr = &dlPtr->index;
}
DisplayTextWhenIdle(textPtr);
dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
dlPtr = FindDLine(textPtr, dlPtr, index1Ptr);
if (dlPtr) {
endPtr = FindDLine(textPtr, dlPtr, index2Ptr);
if (endPtr && TkTextIndexCompare(&endPtr->index, index2Ptr) < 0) {
endPtr = endPtr->nextPtr;
}
FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK);
}
}
static void
RedrawTagsInPeer(
const TkSharedText *sharedTextPtr,
TkText *textPtr,
TkTextIndex *indexPtr1,
TkTextIndex *indexPtr2,
bool affectsDisplayGeometry)
{
TkTextIndex start, end;
if (!textPtr->dInfoPtr || !textPtr->dInfoPtr->dLinePtr) {
return;
}
if (textPtr->startMarker != sharedTextPtr->startMarker) {
TkTextIndexSetupToStartOfText(&start, textPtr, sharedTextPtr->tree);
if (TkTextIndexCompare(indexPtr1, &start) <= 0) {
indexPtr1 = &start;
}
}
if (textPtr->endMarker != sharedTextPtr->endMarker) {
TkTextIndexSetupToEndOfText(&end, textPtr, sharedTextPtr->tree);
if (TkTextIndexCompare(indexPtr2, &end) <= 0) {
indexPtr2 = &end;
}
}
TkTextIndexSetPeer(indexPtr1, textPtr);
TkTextIndexSetPeer(indexPtr2, textPtr);
TextRedrawTag(textPtr, indexPtr1, indexPtr2, affectsDisplayGeometry);
}
bool
TkTextRedrawTag(
const TkSharedText *sharedTextPtr,
TkText *textPtr,
const TkTextIndex *index1Ptr,
const TkTextIndex *index2Ptr,
const TkTextTag *tagPtr,
bool affectsDisplayGeometry)
{
assert(!index1Ptr == !index2Ptr);
assert(index1Ptr || tagPtr);
assert(sharedTextPtr || textPtr);
if (!sharedTextPtr && !textPtr->dInfoPtr->dLinePtr) {
return false;
}
if (tagPtr && tagPtr->affectsDisplayGeometry) {
affectsDisplayGeometry = true;
}
if (!index1Ptr) {
TkTextSegment *endMarker;
TkTextSearch search;
TkTextIndex startIndex, endIndex;
if (!sharedTextPtr) {
TkTextIndexClear2(&startIndex, NULL, textPtr->sharedTextPtr->tree);
TkTextIndexClear2(&endIndex, NULL, textPtr->sharedTextPtr->tree);
TkTextIndexSetSegment(&startIndex, textPtr->startMarker);
TkTextIndexSetSegment(&endIndex, textPtr->endMarker);
endMarker = textPtr->endMarker;
} else {
TkTextIndexClear2(&startIndex, NULL, sharedTextPtr->tree);
TkTextIndexClear2(&endIndex, NULL, sharedTextPtr->tree);
TkTextIndexSetSegment(&startIndex, sharedTextPtr->startMarker);
TkTextIndexSetSegment(&endIndex, sharedTextPtr->endMarker);
endMarker = sharedTextPtr->endMarker;
}
if (tagPtr) {
bool found = false;
TkBTreeStartSearch(&startIndex, &endIndex, tagPtr, &search, SEARCH_EITHER_TAGON_TAGOFF);
while (true) {
if (!TkBTreeNextTag(&search)) {
return found;
}
if (search.tagon) {
startIndex = search.curIndex;
TkBTreeNextTag(&search);
assert(search.segPtr);
} else {
assert(!found);
}
found = true;
assert(!search.tagon);
if (!sharedTextPtr) {
TextRedrawTag(textPtr, &startIndex, &search.curIndex, affectsDisplayGeometry);
} else {
for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
RedrawTagsInPeer(sharedTextPtr, textPtr, &startIndex, &search.curIndex,
affectsDisplayGeometry);
}
}
}
} else {
const TkBitField *discardTags = NULL;
TkTextSegment *segPtr;
TkTextIndex index2;
if (affectsDisplayGeometry) {
if (sharedTextPtr) {
discardTags = sharedTextPtr->notAffectDisplayTags;
} else {
discardTags = textPtr->sharedTextPtr->notAffectDisplayTags;
}
}
if (!(segPtr = TkBTreeFindNextTagged(&startIndex, &endIndex, discardTags))) {
return false;
}
index2 = endIndex;
while (segPtr) {
TkTextSegment *endPtr;
TkTextIndexSetSegment(&startIndex, segPtr);
endPtr = TkBTreeFindNextUntagged(&startIndex, &endIndex, discardTags);
if (!endPtr) {
endPtr = endMarker;
}
TkTextIndexSetSegment(&index2, endPtr);
if (!sharedTextPtr) {
TextRedrawTag(textPtr, &startIndex, &index2, affectsDisplayGeometry);
} else {
for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
RedrawTagsInPeer(sharedTextPtr, textPtr, &startIndex, &index2,
affectsDisplayGeometry);
}
}
}
}
} else if (!sharedTextPtr) {
TextRedrawTag(textPtr, index1Ptr, index2Ptr, affectsDisplayGeometry);
} else {
TkTextIndex index1 = *index1Ptr;
TkTextIndex index2 = *index2Ptr;
for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
RedrawTagsInPeer(sharedTextPtr, textPtr, &index1, &index2, affectsDisplayGeometry);
}
}
return true;
}
void
TkTextRelayoutWindow(
TkText *textPtr,
int mask)
{
TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
XGCValues gcValues;
GC newGC;
bool recomputeGeometry;
bool asyncLineCalculation;
int firstLineNo;
int lastLineNo;
int maxX;
if ((mask & TK_TEXT_LINE_REDRAW_BOTTOM_LINE) && dInfoPtr->lastDLinePtr) {
dInfoPtr->lastDLinePtr->flags |= OLD_Y_INVALID;
}
DisplayTextWhenIdle(textPtr);
dInfoPtr->flags |= REDRAW_BORDERS|DINFO_OUT_OF_DATE|REPICK_NEEDED;
gcValues.graphics_exposures = False;
newGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);
if (dInfoPtr->copyGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
}
dInfoPtr->copyGC = newGC;
if (dInfoPtr->insertFgGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->insertFgGC);
dInfoPtr->insertFgGC = None;
}
if (textPtr->state == TK_TEXT_STATE_NORMAL
&& textPtr->blockCursorType
&& textPtr->showInsertFgColor) {
gcValues.foreground = textPtr->insertFgColorPtr->pixel;
dInfoPtr->insertFgGC = Tk_GetGC(textPtr->tkwin, GCForeground, &gcValues);
}
maxX = MAX(Tk_Width(textPtr->tkwin) - dInfoPtr->x, dInfoPtr->x + 1);
firstLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, TkBTreeGetStartLine(textPtr), NULL);
lastLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, TkBTreeGetLastLine(textPtr), NULL);
recomputeGeometry = (maxX != dInfoPtr->maxX) || (mask & TK_TEXT_LINE_GEOMETRY);
if (recomputeGeometry || (mask & TK_TEXT_LINE_REDRAW)) {
FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK_KEEP_BRKS);
}
FreeDLines(textPtr, NULL, NULL, DLINE_CACHE);
FreeDLines(textPtr, NULL, NULL, DLINE_METRIC);
FreeDLines(textPtr, dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
assert(textPtr->highlightWidth >= 0);
assert(textPtr->borderWidth >= 0);
dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth + textPtr->padX;
dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth + textPtr->padY;
dInfoPtr->maxX = MAX(Tk_Width(textPtr->tkwin) - dInfoPtr->x, dInfoPtr->x + 1);
dInfoPtr->maxY = MAX(Tk_Height(textPtr->tkwin) - dInfoPtr->y, dInfoPtr->y + 1);
dInfoPtr->topOfEof = dInfoPtr->maxY;
if (!IsStartOfNotMergedLine(&textPtr->topIndex)) {
TkTextFindDisplayLineStartEnd(textPtr, &textPtr->topIndex, DISP_LINE_START);
}
dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
dInfoPtr->currChunkPtr = NULL;
if (mask & TK_TEXT_LINE_GEOMETRY) {
SetupEolSegment(textPtr, dInfoPtr);
}
asyncLineCalculation = false;
#if SPEEDUP_MONOSPACED_LINE_HEIGHTS
if (TestMonospacedLineHeights(textPtr)) {
TkRangeList *ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
if (!TkRangeListIsEmpty(ranges)) {
TkBTreeUpdatePixelHeights(textPtr,
TkBTreeFindLine(sharedTextPtr->tree, textPtr, TkRangeListLow(ranges)),
TkRangeListSpan(ranges), dInfoPtr->lineMetricUpdateEpoch);
TkRangeListClear(ranges);
}
if (dInfoPtr->lineHeight != textPtr->lineHeight) {
TkBTreeUpdatePixelHeights(textPtr, TkBTreeGetStartLine(textPtr), lastLineNo - firstLineNo,
dInfoPtr->lineMetricUpdateEpoch);
dInfoPtr->lineHeight = textPtr->lineHeight;
}
} else
#endif
if (recomputeGeometry) {
dInfoPtr->lineHeight = 0;
TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
if (lastLineNo > firstLineNo) {
dInfoPtr->lineMetricUpdateRanges =
TkRangeListAdd(dInfoPtr->lineMetricUpdateRanges, 0, lastLineNo - firstLineNo - 1);
dInfoPtr->lineMetricUpdateEpoch += 1;
asyncLineCalculation = true;
}
} else {
TkTextIndex index;
DLine *dlPtr;
int numLines;
dInfoPtr->lineHeight = 0;
if (lastLineNo == firstLineNo) {
FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
} else if (dInfoPtr->lastLineNo <= firstLineNo || lastLineNo <= dInfoPtr->firstLineNo) {
FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
dInfoPtr->lineMetricUpdateRanges = TkRangeListAdd(
dInfoPtr->lineMetricUpdateRanges, 0, lastLineNo - firstLineNo - 1);
asyncLineCalculation = true;
} else {
if (firstLineNo < dInfoPtr->firstLineNo) {
dInfoPtr->lineMetricUpdateRanges = TkRangeListInsert(
dInfoPtr->lineMetricUpdateRanges, 0, dInfoPtr->firstLineNo - firstLineNo - 1);
asyncLineCalculation = true;
} else if (dInfoPtr->firstLineNo < firstLineNo) {
TkTextIndexSetupToStartOfText(&index, textPtr, sharedTextPtr->tree);
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK);
numLines = firstLineNo - dInfoPtr->firstLineNo;
TkRangeListDelete(dInfoPtr->lineMetricUpdateRanges, 0, numLines - 1);
}
if (dInfoPtr->lastLineNo < lastLineNo) {
dInfoPtr->lineMetricUpdateRanges = TkRangeListAdd(
dInfoPtr->lineMetricUpdateRanges,
dInfoPtr->lastLineNo - dInfoPtr->firstLineNo,
lastLineNo - firstLineNo - 1);
asyncLineCalculation = true;
} else if (lastLineNo < dInfoPtr->lastLineNo) {
TkTextIndexSetupToEndOfText(&index, textPtr, sharedTextPtr->tree);
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
FreeDLines(textPtr, dlPtr, NULL, DLINE_UNLINK);
TkRangeListTruncateAtEnd(dInfoPtr->lineMetricUpdateRanges, lastLineNo - firstLineNo - 1);
}
}
}
dInfoPtr->firstLineNo = firstLineNo;
dInfoPtr->lastLineNo = lastLineNo;
if (asyncLineCalculation) {
StartAsyncLineCalculation(textPtr);
}
}
void
TkTextSetYView(
TkText *textPtr,
TkTextIndex *indexPtr,
int pickPlace)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
int bottomY, close;
TkTextIndex tmpIndex;
TkTextLine *linePtr;
int lineHeight;
int topLineNo;
int topByteIndex;
int32_t overlap;
if (TkTextIsDeadPeer(textPtr)) {
textPtr->topIndex = *indexPtr;
TkTextIndexSetPeer(&textPtr->topIndex, textPtr);
return;
}
linePtr = TkTextIndexGetLine(indexPtr);
if (linePtr == TkBTreeGetLastLine(textPtr) && TkTextIndexGetByteIndex(indexPtr) == 0) {
assert(linePtr->prevPtr);
assert(TkBTreeGetStartLine(textPtr) != linePtr);
TkTextIndexSetToEndOfLine2(indexPtr, linePtr->prevPtr);
}
if (pickPlace == TK_TEXT_NOPIXELADJUST) {
pickPlace = TkTextIndexIsEqual(&textPtr->topIndex, indexPtr) ? dInfoPtr->topPixelOffset : 0;
}
if (pickPlace != TK_TEXT_PICKPLACE) {
textPtr->topIndex = *indexPtr;
TkTextIndexSetPeer(&textPtr->topIndex, textPtr);
TkTextIndexToByteIndex(&textPtr->topIndex);
if (!IsStartOfNotMergedLine(indexPtr)) {
TkTextFindDisplayLineStartEnd(textPtr, &textPtr->topIndex, DISP_LINE_START);
}
dInfoPtr->newTopPixelOffset = pickPlace;
goto scheduleUpdate;
}
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
}
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
if (dlPtr) {
if (dlPtr->y + dlPtr->height > dInfoPtr->maxY) {
dlPtr = NULL;
} else {
if (TkTextIndexCompare(&dlPtr->index, indexPtr) <= 0) {
if (dInfoPtr->dLinePtr == dlPtr && dInfoPtr->topPixelOffset != 0) {
dInfoPtr->newTopPixelOffset = 0;
goto scheduleUpdate;
}
return;
}
}
}
tmpIndex = *indexPtr;
TkTextFindDisplayLineStartEnd(textPtr, &tmpIndex, DISP_LINE_START);
lineHeight = CalculateDisplayLineHeight(textPtr, &tmpIndex, NULL);
bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2;
close = (dInfoPtr->maxY - dInfoPtr->y)/3;
if (close < 3*textPtr->lineHeight) {
close = 3*textPtr->lineHeight;
}
if (dlPtr) {
MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->lineHeight/2, &tmpIndex, &overlap);
if (TkTextIndexCompare(&tmpIndex, indexPtr) <= 0) {
textPtr->topIndex = *indexPtr;
TkTextIndexSetPeer(&textPtr->topIndex, textPtr);
TkTextIndexToByteIndex(&textPtr->topIndex);
TkTextFindDisplayLineStartEnd(textPtr, &textPtr->topIndex, DISP_LINE_START);
dInfoPtr->newTopPixelOffset = 0;
goto scheduleUpdate;
}
} else {
MeasureUp(textPtr, indexPtr, close + lineHeight - textPtr->lineHeight/2, &tmpIndex, &overlap);
if (FindDLine(textPtr, dInfoPtr->dLinePtr, &tmpIndex)) {
bottomY = dInfoPtr->maxY - dInfoPtr->y;
}
}
if (dInfoPtr->maxY - dInfoPtr->y < lineHeight) {
bottomY = lineHeight;
}
MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
scheduleUpdate:
topLineNo = TkTextIndexGetLineNumber(&textPtr->topIndex, NULL);
topByteIndex = TkTextIndexGetByteIndex(&textPtr->topIndex);
if (dInfoPtr->newTopPixelOffset != dInfoPtr->topPixelOffset
|| dInfoPtr->topLineNo != topLineNo
|| dInfoPtr->topByteIndex != topByteIndex) {
DisplayTextWhenIdle(textPtr);
dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
dInfoPtr->topLineNo = topLineNo;
dInfoPtr->topByteIndex = topByteIndex;
}
}
static const TkTextDispLineEntry *
SearchPixelEntry(
const TkTextDispLineEntry *first,
const TkTextDispLineEntry *last,
unsigned pixels)
{
assert(first != last);
if ((last - 1)->pixels < pixels) {
return last - 1;
}
do {
const TkTextDispLineEntry *mid = first + (last - first)/2;
if (mid->pixels <= pixels) {
first = mid + 1;
} else {
last = mid;
}
} while (first != last);
return first;
}
static unsigned
FindDisplayLineOffset(
TkText *textPtr,
TkTextLine *linePtr,
int32_t *distance)
{
const TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
const TkTextDispLineEntry *lastEntry;
const TkTextDispLineEntry *entry;
assert(distance);
assert(*distance >= 0);
assert(linePtr->logicalLine);
if (!dispLineInfo) {
return 0;
}
lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
entry = SearchPixelEntry(dispLineInfo->entry, lastEntry, *distance);
assert(entry != lastEntry);
if (entry == dispLineInfo->entry) {
return 0;
}
*distance -= (entry - 1)->pixels;
return entry->byteOffset;
}
static bool
AlreadyAtBottom(
const TkText *textPtr)
{
const TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr = dInfoPtr->lastDLinePtr;
TkTextIndex index;
if (!dlPtr) {
return true;
}
if (dlPtr->y + dlPtr->height != dInfoPtr->maxY) {
return false;
}
index = dlPtr->index;
TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
return TkTextIndexIsEndOfText(&index);
}
static bool
MeasureDown(
TkText *textPtr,
TkTextIndex *srcPtr,
int distance,
int32_t *overlap,
bool saveDisplayLines)
{
const TkTextLine *lastLinePtr;
TkTextLine *linePtr;
TkTextIndex index;
int byteOffset;
int32_t myOverlap;
if (AlreadyAtBottom(textPtr)) {
return false;
}
if (!overlap) {
overlap = &myOverlap;
}
linePtr = TkTextIndexGetLine(srcPtr);
lastLinePtr = TkBTreeGetLastLine(textPtr);
if (TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges)) {
int pixelHeight;
pixelHeight = TkBTreePixelsTo(textPtr, linePtr);
pixelHeight += GetPixelsTo(textPtr, srcPtr, false, NULL);
pixelHeight += distance;
linePtr = TkBTreeFindPixelLine(srcPtr->tree, textPtr, pixelHeight, overlap);
if (linePtr == lastLinePtr) {
TkTextLine *prevLinePtr = TkBTreePrevLine(textPtr, linePtr);
if (prevLinePtr) {
linePtr = prevLinePtr;
}
}
byteOffset = FindDisplayLineOffset(textPtr, linePtr, overlap);
} else {
DisplayInfo info;
linePtr = ComputeDisplayLineInfo(textPtr, srcPtr, &info);
distance += GetPixelsTo(textPtr, srcPtr, false, &info);
index = *srcPtr;
while (true) {
ComputeMissingMetric(textPtr, &info, THRESHOLD_PIXEL_DISTANCE, distance);
if (saveDisplayLines) {
SaveDisplayLines(textPtr, &info, true);
} else {
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
if (distance < info.pixels) {
const TkTextDispLineInfo *dispLineInfo = info.pixelInfo->dispLineInfo;
if (dispLineInfo) {
const TkTextDispLineEntry *entry, *last;
last = dispLineInfo->entry + dispLineInfo->numDispLines;
entry = SearchPixelEntry(dispLineInfo->entry, last, distance);
assert(entry < last);
byteOffset = entry->byteOffset;
if (entry != dispLineInfo->entry) {
distance -= (entry - 1)->pixels;
}
} else {
byteOffset = 0;
}
break;
}
if (TkTextIndexGetLine(&info.index) == lastLinePtr) {
byteOffset = 0;
break;
}
linePtr = TkTextIndexGetLine(&info.index);
if ((distance -= info.pixels) == 0) {
byteOffset = 0;
break;
}
TkTextIndexSetToStartOfLine2(&index, linePtr);
linePtr = ComputeDisplayLineInfo(textPtr, &index, &info);
}
*overlap = distance;
}
assert(linePtr != lastLinePtr);
TkTextIndexSetToStartOfLine2(srcPtr, linePtr);
TkTextIndexForwBytes(textPtr, srcPtr, byteOffset, srcPtr);
return true;
}
static bool
AlreadyAtTop(
const TkText *textPtr)
{
const TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (!dInfoPtr->dLinePtr) {
return true;
}
return dInfoPtr->topPixelOffset == 0 && TkTextIndexIsStartOfText(&dInfoPtr->dLinePtr->index);
}
static bool
MeasureUp(
TkText *textPtr,
const TkTextIndex *srcPtr,
int distance,
TkTextIndex *dstPtr,
int32_t *overlap)
{
TkTextLine *linePtr;
TkTextLine *startLinePtr;
unsigned byteOffset;
assert(overlap);
assert(dstPtr);
if (TkTextIndexIsStartOfText(srcPtr) && AlreadyAtTop(textPtr)) {
return false;
}
*dstPtr = *srcPtr;
startLinePtr = TkBTreeGetStartLine(textPtr);
linePtr = TkTextIndexGetLine(srcPtr);
if (TestIfLinesUpToDate(srcPtr)) {
int pixelHeight;
pixelHeight = TkBTreePixelsTo(textPtr, linePtr);
pixelHeight += GetPixelsTo(textPtr, srcPtr, true, NULL);
pixelHeight -= distance;
if (pixelHeight <= 0) {
linePtr = startLinePtr;
byteOffset = *overlap = 0;
} else {
linePtr = TkBTreeFindPixelLine(srcPtr->tree, textPtr, pixelHeight, overlap);
byteOffset = FindDisplayLineOffset(textPtr, linePtr, overlap);
}
} else {
DisplayInfo info;
linePtr = ComputeDisplayLineInfo(textPtr, srcPtr, &info);
SaveDisplayLines(textPtr, &info, false);
distance -= GetPixelsTo(textPtr, srcPtr, true, &info);
while (linePtr != startLinePtr && distance > 0) {
TkTextIndexSetToLastChar2(dstPtr, linePtr->prevPtr);
linePtr = ComputeDisplayLineInfo(textPtr, dstPtr, &info);
SaveDisplayLines(textPtr, &info, false);
distance -= info.pixels;
}
if (distance < 0) {
*overlap = -distance;
byteOffset = FindDisplayLineOffset(textPtr, linePtr, overlap);
} else {
byteOffset = *overlap = 0;
}
}
TkTextIndexSetToStartOfLine2(dstPtr, linePtr);
TkTextIndexForwBytes(textPtr, dstPtr, byteOffset, dstPtr);
return true;
}
int
TkTextSeeCmd(
TkText *textPtr,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextIndex index;
int x, y, width, height, lineWidth, byteCount, oneThird, delta;
DLine *dlPtr;
TkTextDispChunk *chunkPtr;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
return TCL_ERROR;
}
if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
return TCL_ERROR;
}
if (TkTextIsDeadPeer(textPtr)) {
return TCL_OK;
}
if (TkTextIndexGetLine(&index) == TkBTreeGetLastLine(textPtr)) {
TkTextIndexSetToLastChar2(&index, TkTextIndexGetLine(&index)->prevPtr);
}
TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE);
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
}
lineWidth = dInfoPtr->maxX - dInfoPtr->x;
if (dInfoPtr->maxLength < lineWidth) {
return TCL_OK;
}
if (!(dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index))) {
return TCL_OK;
}
byteCount = TkTextIndexCountBytes(&dlPtr->index, &index);
for (chunkPtr = dlPtr->chunkPtr;
chunkPtr && byteCount >= chunkPtr->numBytes;
chunkPtr = chunkPtr->nextPtr) {
byteCount -= chunkPtr->numBytes;
}
if (chunkPtr) {
chunkPtr->layoutProcs->bboxProc(
textPtr, chunkPtr, byteCount,
dlPtr->y + dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove,
&x, &y, &width, &height);
delta = x - dInfoPtr->curXPixelOffset;
oneThird = lineWidth/3;
if (delta < 0) {
if (delta < -oneThird) {
dInfoPtr->newXPixelOffset = x - lineWidth/2;
} else {
dInfoPtr->newXPixelOffset += delta;
}
} else {
delta -= lineWidth - width;
if (delta <= 0) {
return TCL_OK;
}
if (delta > oneThird) {
dInfoPtr->newXPixelOffset = x - lineWidth/2;
} else {
dInfoPtr->newXPixelOffset += delta;
}
}
}
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
DisplayTextWhenIdle(textPtr);
return TCL_OK;
}
int
TkTextXviewCmd(
TkText *textPtr,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
int count;
double fraction;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
}
if (objc == 2) {
GetXView(interp, textPtr, false);
return TCL_OK;
}
switch (TextGetScrollInfoObj(interp, textPtr, objc, objv, &fraction, &count)) {
case SCROLL_ERROR:
return TCL_ERROR;
case SCROLL_MOVETO:
dInfoPtr->newXPixelOffset = (int) (MIN(1.0, MAX(0.0, fraction))*dInfoPtr->maxLength + 0.5);
break;
case SCROLL_PAGES: {
int pixelsPerPage;
pixelsPerPage = dInfoPtr->maxX - dInfoPtr->x - 2*textPtr->charWidth;
dInfoPtr->newXPixelOffset += count*MAX(1, pixelsPerPage);
break;
}
case SCROLL_UNITS:
dInfoPtr->newXPixelOffset += count*textPtr->charWidth;
break;
case SCROLL_PIXELS:
dInfoPtr->newXPixelOffset += count;
break;
}
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
DisplayTextWhenIdle(textPtr);
return TCL_OK;
}
static void
YScrollByPixels(
TkText *textPtr,
int offset)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (offset < 0) {
offset -= CalculateDisplayLineHeight(textPtr, &textPtr->topIndex, NULL);
offset += dInfoPtr->topPixelOffset;
if (!MeasureUp(textPtr, &textPtr->topIndex, -offset,
&textPtr->topIndex, &dInfoPtr->newTopPixelOffset)) {
return;
}
} else if (offset > 0) {
offset += dInfoPtr->topPixelOffset;
if (!MeasureDown(textPtr, &textPtr->topIndex, offset, &dInfoPtr->newTopPixelOffset, true)) {
return;
}
TkTextIndexToByteIndex(&textPtr->topIndex);
} else {
return;
}
assert(TkTextIndexIsEndOfText(&textPtr->topIndex) ?
dInfoPtr->newTopPixelOffset == 0 :
dInfoPtr->newTopPixelOffset < CalculateDisplayLineHeight(textPtr, &textPtr->topIndex, NULL));
DisplayTextWhenIdle(textPtr);
dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
}
static bool
ScrollUp(
TkText *textPtr,
unsigned offset)
{
TkTextLine *linePtr;
unsigned byteOffset;
DisplayInfo info;
bool upToDate;
assert(offset > 0);
if (AlreadyAtTop(textPtr)) {
return false;
}
if (TkTextIndexIsStartOfText(&textPtr->dInfoPtr->dLinePtr->index)) {
textPtr->dInfoPtr->newTopPixelOffset = 0;
return true;
}
upToDate = TestIfLinesUpToDate(&textPtr->topIndex);
linePtr = ComputeDisplayLineInfo(textPtr, &textPtr->topIndex, &info);
if (upToDate) {
const TkTextDispLineInfo *dispLineInfo;
assert(!info.dLinePtr);
linePtr = TkBTreePrevDisplayLine(textPtr, linePtr, &info.displayLineNo, offset);
dispLineInfo = TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo;
byteOffset = dispLineInfo ? dispLineInfo->entry[info.displayLineNo].byteOffset : 0;
} else {
TkTextLine *firstLinePtr;
TkTextIndex index;
firstLinePtr = TkBTreeGetStartLine(textPtr);
index = textPtr->topIndex;
SaveDisplayLines(textPtr, &info, false);
info.numDispLines = info.displayLineNo + 1;
while (true) {
if (info.numDispLines > offset) {
byteOffset = (info.entry - offset)->byteOffset;
break;
}
offset -= info.numDispLines;
if (linePtr == firstLinePtr) {
byteOffset = 0;
break;
}
TkTextIndexSetToLastChar2(&index, linePtr->prevPtr);
linePtr = ComputeDisplayLineInfo(textPtr, &index, &info);
SaveDisplayLines(textPtr, &info, false);
assert(!TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo
|| info.entry == TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo->entry +
info.numDispLines - 1);
}
}
TkTextIndexSetToStartOfLine2(&textPtr->topIndex, linePtr);
TkTextIndexForwBytes(textPtr, &textPtr->topIndex, byteOffset, &textPtr->topIndex);
return true;
}
static bool
ScrollDown(
TkText *textPtr,
unsigned offset)
{
TkTextLine *linePtr;
unsigned byteOffset;
DisplayInfo info;
bool upToDate;
assert(offset > 0);
if (AlreadyAtBottom(textPtr)) {
return false;
}
upToDate = TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges);
linePtr = ComputeDisplayLineInfo(textPtr, &textPtr->topIndex, &info);
if (upToDate) {
const TkTextDispLineInfo *dispLineInfo;
assert(!info.dLinePtr);
linePtr = TkBTreeNextDisplayLine(textPtr, linePtr, &info.displayLineNo, offset);
dispLineInfo = TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo;
byteOffset = dispLineInfo ? dispLineInfo->entry[info.displayLineNo].byteOffset : 0;
} else {
TkTextLine *lastLinePtr;
lastLinePtr = TkBTreeGetLastLine(textPtr);
ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, offset);
SaveDisplayLines(textPtr, &info, true);
info.numDispLines -= info.displayLineNo;
while (true) {
if (info.numDispLines == offset) {
byteOffset = 0;
linePtr = linePtr->nextPtr;
break;
}
if (info.numDispLines > offset) {
byteOffset = (info.entry + offset)->byteOffset;
break;
}
offset -= info.numDispLines;
if (TkTextIndexGetLine(&info.index) == lastLinePtr) {
byteOffset = (info.entry + info.numDispLines - 1)->byteOffset;
break;
}
linePtr = ComputeDisplayLineInfo(textPtr, &info.index, &info);
ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, offset);
SaveDisplayLines(textPtr, &info, true);
}
}
TkTextIndexSetToStartOfLine2(&textPtr->topIndex, linePtr);
TkTextIndexForwBytes(textPtr, &textPtr->topIndex, byteOffset, &textPtr->topIndex);
return true;
}
static void
YScrollByLines(
TkText *textPtr,
int offset)
{
assert(textPtr);
if (offset < 0) {
if (!ScrollUp(textPtr, -offset)) {
return;
}
} else if (offset > 0) {
if (!ScrollDown(textPtr, offset)) {
return;
}
} else {
return;
}
DisplayTextWhenIdle(textPtr);
textPtr->dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
}
static int
MakePixelIndex(
TkText *textPtr,
unsigned pixelIndex,
TkTextIndex *indexPtr)
{
TkTextLine *linePtr;
TkTextLine *lastLinePtr;
int32_t pixelOffset;
assert(!TkTextIsDeadPeer(textPtr));
TkTextIndexClear(indexPtr, textPtr);
linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, textPtr, pixelIndex, &pixelOffset);
lastLinePtr = TkBTreeGetLastLine(textPtr);
if (linePtr != lastLinePtr) {
int byteOffset = FindDisplayLineOffset(textPtr, linePtr, &pixelOffset);
TkTextIndexSetByteIndex2(indexPtr, linePtr, byteOffset);
} else {
assert(lastLinePtr->prevPtr);
linePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr->prevPtr);
TkTextIndexSetToLastChar2(indexPtr, linePtr);
FindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_START, DLINE_CACHE);
pixelOffset = CalculateDisplayLineHeight(textPtr, indexPtr, NULL) - 1;
}
return MAX(0, pixelOffset);
}
static void
Repick(
ClientData clientData)
{
TkText *textPtr = (TkText *) clientData;
if (!TkTextReleaseIfDestroyed(textPtr)) {
textPtr->dInfoPtr->flags &= ~REPICK_NEEDED;
textPtr->dInfoPtr->currChunkPtr = NULL;
textPtr->dInfoPtr->repickTimer = NULL;
textPtr->dontRepick = false;
TkTextPickCurrent(textPtr, &textPtr->pickEvent);
}
}
static void
DelayRepick(
TkText *textPtr)
{
assert(textPtr->dInfoPtr->flags & REPICK_NEEDED);
if (textPtr->responsiveness > 0) {
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (dInfoPtr->repickTimer) {
Tcl_DeleteTimerHandler(dInfoPtr->repickTimer);
} else {
textPtr->refCount += 1;
}
textPtr->dontRepick = true;
dInfoPtr->flags &= ~REPICK_NEEDED;
dInfoPtr->repickTimer = Tcl_CreateTimerHandler(textPtr->responsiveness, Repick, textPtr);
}
}
int
TkTextYviewCmd(
TkText *textPtr,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
int pickPlace;
int pixels, count;
int switchLength;
double fraction;
TkTextIndex index;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
}
if (objc == 2) {
GetYView(interp, textPtr, false);
return TCL_OK;
}
pickPlace = 0;
if (Tcl_GetString(objv[2])[0] == '-') {
const char *switchStr = Tcl_GetStringFromObj(objv[2], &switchLength);
if (switchLength >= 2 && strncmp(switchStr, "-pickplace", switchLength) == 0) {
pickPlace = 1;
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index");
return TCL_ERROR;
}
}
}
if (objc == 3 || pickPlace) {
int lineNum;
if (Tcl_GetIntFromObj(interp, objv[2 + pickPlace], &lineNum) == TCL_OK) {
TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, 0, &index);
TkTextSetYView(textPtr, &index, 0);
} else {
Tcl_ResetResult(interp);
if (!TkTextGetIndexFromObj(interp, textPtr, objv[2 + pickPlace], &index)) {
return TCL_ERROR;
}
TkTextSetYView(textPtr, &index, pickPlace ? TK_TEXT_PICKPLACE : 0);
}
} else {
switch (TextGetScrollInfoObj(interp, textPtr, objc, objv, &fraction, &count)) {
case SCROLL_ERROR:
return TCL_ERROR;
case SCROLL_MOVETO: {
int numPixels = TkBTreeNumPixels(textPtr);
int topMostPixel;
if (numPixels == 0 || TkTextIsDeadPeer(textPtr)) {
break;
}
if (fraction > 1.0) {
fraction = 1.0;
} else if (fraction < 0.0) {
fraction = 0.0;
}
topMostPixel = MAX(0, MIN((int) (fraction*numPixels + 0.5), numPixels - 1));
pixels = MakePixelIndex(textPtr, topMostPixel, &index);
TkTextSetYView(textPtr, &index, pixels);
break;
}
case SCROLL_PAGES: {
int height = dInfoPtr->maxY - dInfoPtr->y;
if (textPtr->lineHeight*4 >= height) {
pixels = 3*height/4;
if (pixels < textPtr->lineHeight) {
if (textPtr->lineHeight < height) {
pixels = textPtr->lineHeight;
} else {
pixels = height;
}
}
pixels *= count;
} else {
pixels = (height - 2*textPtr->lineHeight)*count;
}
YScrollByPixels(textPtr, pixels);
break;
}
case SCROLL_PIXELS:
YScrollByPixels(textPtr, count);
break;
case SCROLL_UNITS:
YScrollByLines(textPtr, count);
break;
}
}
if (dInfoPtr->flags & REPICK_NEEDED) {
DelayRepick(textPtr);
}
return TCL_OK;
}
int
TkTextScanCmd(
TkText *textPtr,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextIndex index;
int c, x, y, totalScroll, gain=10;
size_t length;
if (objc != 5 && objc != 6) {
Tcl_WrongNumArgs(interp, 2, objv, "mark x y");
Tcl_AppendResult(interp, " or \"", Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", NULL);
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
return TCL_ERROR;
}
if (objc == 6 && Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK) {
return TCL_ERROR;
}
c = Tcl_GetString(objv[2])[0];
length = strlen(Tcl_GetString(objv[2]));
if (c == 'd' && strncmp(Tcl_GetString(objv[2]), "dragto", length) == 0) {
int newX, maxX;
newX = dInfoPtr->scanMarkXPixel + gain*(dInfoPtr->scanMarkX - x);
maxX = 1 + dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
if (newX < 0) {
newX = 0;
dInfoPtr->scanMarkXPixel = 0;
dInfoPtr->scanMarkX = x;
} else if (newX > maxX) {
newX = maxX;
dInfoPtr->scanMarkXPixel = maxX;
dInfoPtr->scanMarkX = x;
}
dInfoPtr->newXPixelOffset = newX;
totalScroll = gain*(dInfoPtr->scanMarkY - y);
if (totalScroll != dInfoPtr->scanTotalYScroll) {
index = textPtr->topIndex;
YScrollByPixels(textPtr, totalScroll - dInfoPtr->scanTotalYScroll);
dInfoPtr->scanTotalYScroll = totalScroll;
if (TkTextIndexIsEqual(&index, &textPtr->topIndex)) {
dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
}
}
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
DisplayTextWhenIdle(textPtr);
} else if (c == 'm' && strncmp(Tcl_GetString(objv[2]), "mark", length) == 0) {
dInfoPtr->scanMarkXPixel = dInfoPtr->newXPixelOffset;
dInfoPtr->scanMarkX = x;
dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
} else {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"bad scan option \"%s\": must be mark or dragto", Tcl_GetString(objv[2])));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "scan option", Tcl_GetString(objv[2]), NULL);
return TCL_ERROR;
}
return TCL_OK;
}
static void
GetXView(
Tcl_Interp *interp,
TkText *textPtr,
bool report)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
double first, last;
int xMin, xMax;
int code;
Tcl_Obj *listObj;
if (dInfoPtr->maxLength > 0) {
first = ((double) dInfoPtr->curXPixelOffset)/dInfoPtr->maxLength;
last = ((double) (dInfoPtr->curXPixelOffset + dInfoPtr->maxX - dInfoPtr->x))/dInfoPtr->maxLength;
if (last > 1.0) {
last = 1.0;
}
xMin = dInfoPtr->curXPixelOffset;
xMax = xMin + dInfoPtr->maxX - dInfoPtr->x;
} else {
first = 0.0;
last = 1.0;
xMin = xMax = dInfoPtr->curXPixelOffset;
}
if (!report) {
listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
Tcl_SetObjResult(interp, listObj);
return;
}
if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) &&
FP_EQUAL_SCALE(last, dInfoPtr->xScrollLast, dInfoPtr->maxLength)) {
return;
}
dInfoPtr->xScrollFirst = first;
dInfoPtr->xScrollLast = last;
dInfoPtr->curPixelPos.xFirst = xMin;
dInfoPtr->curPixelPos.xLast = xMax;
if (textPtr->xScrollCmd) {
char buf1[TCL_DOUBLE_SPACE + 1];
char buf2[TCL_DOUBLE_SPACE + 1];
Tcl_DString buf;
buf1[0] = ' ';
buf2[0] = ' ';
Tcl_PrintDouble(NULL, first, buf1 + 1);
Tcl_PrintDouble(NULL, last, buf2 + 1);
Tcl_DStringInit(&buf);
Tcl_DStringAppend(&buf, textPtr->xScrollCmd, -1);
Tcl_DStringAppend(&buf, buf1, -1);
Tcl_DStringAppend(&buf, buf2, -1);
code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0);
Tcl_DStringFree(&buf);
if (code != TCL_OK) {
Tcl_AddErrorInfo(interp,
"\n (horizontal scrolling command executed by text)");
Tcl_BackgroundException(interp, code);
}
}
}
static unsigned
GetYPixelCount(
TkText *textPtr,
DLine *dlPtr)
{
TkTextLine *linePtr;
DisplayInfo info;
linePtr = ComputeDisplayLineInfo(textPtr, &dlPtr->index, &info);
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
return TkBTreePixelsTo(textPtr, linePtr) + info.entry->pixels - info.entry->height;
}
static void
GetYView(
Tcl_Interp *interp,
TkText *textPtr,
bool report)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
double first, last;
DLine *dlPtr;
int totalPixels, code, count;
int yMin, yMax;
Tcl_Obj *listObj;
dlPtr = dInfoPtr->dLinePtr;
if (!dlPtr) {
return;
}
totalPixels = TkBTreeNumPixels(textPtr);
if (totalPixels == 0) {
first = 0.0;
last = 1.0;
yMin = yMax = dInfoPtr->topPixelOffset;
} else {
count = yMin = GetYPixelCount(textPtr, dlPtr);
first = (count + dInfoPtr->topPixelOffset) / (double) totalPixels;
while (dlPtr) {
int extra;
count += dlPtr->height;
extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY;
if (extra > 0) {
count -= extra;
break;
}
dlPtr = dlPtr->nextPtr;
}
if (count > totalPixels) {
#if 0
Tcl_Panic("Counted more pixels (%d) than expected (%d) total "
"pixels in text widget scroll bar calculation.", count,
totalPixels);
#elif 0
fprintf(stderr, "warning: Counted more pixels (%d) than expected (%d)\n",
count, totalPixels);
#endif
count = totalPixels;
}
yMax = count;
last = ((double) count)/((double) totalPixels);
}
if (!report) {
listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
Tcl_SetObjResult(interp, listObj);
} else {
dInfoPtr->curPixelPos.yFirst = yMin + dInfoPtr->topPixelOffset;
dInfoPtr->curPixelPos.yLast = yMax + dInfoPtr->topPixelOffset;
if (!FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) ||
!FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) {
dInfoPtr->yScrollFirst = first;
dInfoPtr->yScrollLast = last;
if (textPtr->yScrollCmd) {
char buf1[TCL_DOUBLE_SPACE + 1];
char buf2[TCL_DOUBLE_SPACE + 1];
Tcl_DString buf;
buf1[0] = ' ';
buf2[0] = ' ';
Tcl_PrintDouble(NULL, first, buf1 + 1);
Tcl_PrintDouble(NULL, last, buf2 + 1);
Tcl_DStringInit(&buf);
Tcl_DStringAppend(&buf, textPtr->yScrollCmd, -1);
Tcl_DStringAppend(&buf, buf1, -1);
Tcl_DStringAppend(&buf, buf2, -1);
code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0);
Tcl_DStringFree(&buf);
if (code != TCL_OK) {
Tcl_AddErrorInfo(interp,
"\n (vertical scrolling command executed by text)");
Tcl_BackgroundException(interp, code);
}
}
}
}
}
static void
AsyncUpdateYScrollbar(
ClientData clientData)
{
TkText *textPtr = clientData;
if (!TkTextReleaseIfDestroyed(textPtr)) {
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (!dInfoPtr->insideLineMetricUpdate) {
textPtr->dInfoPtr->scrollbarTimer = NULL;
GetYView(textPtr->interp, textPtr, true);
}
}
}
static DLine *
FindCachedDLine(
TkText *textPtr,
const TkTextIndex *indexPtr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
for (dlPtr = dInfoPtr->cachedDLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
if (TkBTreeLinePixelInfo(textPtr, TkTextIndexGetLine(&dlPtr->index))->epoch
== dInfoPtr->lineMetricUpdateEpoch
&& TkTextIndexCompare(indexPtr, &dlPtr->index) >= 0) {
TkTextIndex index = dlPtr->index;
TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
if (TkTextIndexCompare(indexPtr, &index) < 0) {
DEBUG(stats.numHits++);
return dlPtr;
}
}
}
return NULL;
}
static DLine *
FindDLine(
TkText *textPtr,
DLine *dlPtr,
const TkTextIndex *indexPtr)
{
DLine *lastDlPtr;
if (!dlPtr) {
return NULL;
}
if (TkTextIndexGetLineNumber(indexPtr, NULL) < TkTextIndexGetLineNumber(&dlPtr->index, NULL)) {
return dlPtr;
}
while (TkTextIndexCompare(&dlPtr->index, indexPtr) < 0) {
lastDlPtr = dlPtr;
dlPtr = dlPtr->nextPtr;
if (!dlPtr) {
TkTextIndex index2;
index2 = lastDlPtr->index;
TkTextIndexForwBytes(textPtr, &index2, lastDlPtr->byteCount, &index2);
if (TkTextIndexCompare(&index2, indexPtr) > 0) {
dlPtr = lastDlPtr;
break;
} else {
return NULL;
}
}
if (TkTextIndexCompare(&dlPtr->index, indexPtr) > 0) {
TkTextIndex index;
TkTextIndexForwBytes(textPtr, &lastDlPtr->index, lastDlPtr->byteCount, &index);
if (TkTextIndexCompare(&index, indexPtr) > 0) {
dlPtr = lastDlPtr;
} else {
}
break;
}
}
return dlPtr;
}
int
TkTextGetFirstXPixel(
const TkText *textPtr)
{
assert(textPtr);
return textPtr->dInfoPtr->x;
}
int
TkTextGetFirstYPixel(
const TkText *textPtr)
{
assert(textPtr);
return textPtr->dInfoPtr->y;
}
int
TkTextGetLastXPixel(
const TkText *textPtr)
{
assert(textPtr);
return textPtr->dInfoPtr->maxX - 1;
}
int
TkTextGetLastYPixel(
const TkText *textPtr)
{
assert(textPtr);
return textPtr->dInfoPtr->maxY - 1;
}
unsigned
TkTextCountVisibleImages(
const TkText *textPtr)
{
assert(textPtr);
return textPtr->dInfoPtr->countImages;
}
unsigned
TkTextCountVisibleWindows(
const TkText *textPtr)
{
assert(textPtr);
return textPtr->dInfoPtr->countWindows;
}
bool
TkTextPixelIndex(
TkText *textPtr,
int x, int y,
TkTextIndex *indexPtr,
bool *nearest)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr = NULL;
DLine *currDLinePtr;
TkTextDispChunk *currChunkPtr;
bool nearby = false;
unsigned epoch;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
}
if (y < dInfoPtr->y) {
y = dInfoPtr->y;
nearby = true;
}
if (x >= dInfoPtr->maxX) {
x = dInfoPtr->maxX - 1;
nearby = true;
}
if (x < dInfoPtr->x) {
x = dInfoPtr->x;
nearby = true;
}
if (!dInfoPtr->dLinePtr) {
if (nearest) {
*nearest = true;
}
*indexPtr = textPtr->topIndex;
return true;
}
epoch = TkBTreeEpoch(textPtr->sharedTextPtr->tree);
currChunkPtr = dInfoPtr->currChunkPtr;
if (currChunkPtr && dInfoPtr->currChunkIndex.stateEpoch == epoch) {
currDLinePtr = dInfoPtr->currDLinePtr;
assert(currChunkPtr->stylePtr);
if (currDLinePtr->y <= y && y < currDLinePtr->y + currDLinePtr->height) {
int rx = x - dInfoPtr->x + dInfoPtr->curXPixelOffset;
if (currChunkPtr->x <= rx && rx < currChunkPtr->x + currChunkPtr->width) {
*indexPtr = dInfoPtr->currChunkIndex;
DLineIndexOfX(textPtr, currChunkPtr, x, indexPtr);
if (nearest) {
*nearest = nearby;
}
return true;
}
dlPtr = currDLinePtr;
}
}
if (!dlPtr) {
DLine *validDlPtr = dInfoPtr->dLinePtr;
for (dlPtr = validDlPtr; y >= dlPtr->y + dlPtr->height; dlPtr = dlPtr->nextPtr) {
if (dlPtr->chunkPtr) {
validDlPtr = dlPtr;
}
if (!dlPtr->nextPtr) {
if (nearest) {
*nearest = true;
}
dInfoPtr->currChunkPtr = NULL;
*indexPtr = dlPtr->index;
assert(dlPtr->byteCount > 0);
TkTextIndexForwBytes(textPtr, indexPtr, dlPtr->byteCount - 1, indexPtr);
return false;
}
}
if (!dlPtr->chunkPtr) {
dlPtr = validDlPtr;
}
}
currChunkPtr = DLineChunkOfX(textPtr, dlPtr, x, indexPtr, &nearby);
if (nearest) {
*nearest = nearby;
}
if (!nearby) {
dInfoPtr->currChunkIndex = *indexPtr;
TkTextIndexSetEpoch(&dInfoPtr->currChunkIndex, epoch);
dInfoPtr->currChunkPtr = currChunkPtr;
dInfoPtr->currDLinePtr = dlPtr;
} else {
dInfoPtr->currChunkPtr = NULL;
}
DLineIndexOfX(textPtr, currChunkPtr, x, indexPtr);
return false;
}
static void
DLineIndexOfX(
TkText *textPtr,
TkTextDispChunk *chunkPtr,
int x,
TkTextIndex *indexPtr)
{
if (chunkPtr && chunkPtr->numBytes > 1) {
x -= textPtr->dInfoPtr->x - textPtr->dInfoPtr->curXPixelOffset;
TkTextIndexAddToByteIndex(indexPtr, chunkPtr->layoutProcs->measureProc(chunkPtr, x));
}
}
static TkTextDispChunk *
DLineChunkOfX(
TkText *textPtr,
DLine *dlPtr,
int x,
TkTextIndex *indexPtr,
bool *nearby)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextDispChunk *chunkPtr;
TkTextDispChunkSection *sectionPtr;
unsigned countBytes;
chunkPtr = dlPtr->chunkPtr;
*indexPtr = dlPtr->index;
if (!chunkPtr) {
if (nearby) {
*nearby = true;
}
return chunkPtr;
}
x -= dInfoPtr->x - dInfoPtr->curXPixelOffset;
if (x < chunkPtr->x) {
if (chunkPtr->stylePtr->sValuePtr->indentBg) {
*nearby = true;
}
return chunkPtr;
}
sectionPtr = chunkPtr->sectionPtr;
countBytes = chunkPtr->byteOffset;
while (sectionPtr->nextPtr && x >= sectionPtr->nextPtr->chunkPtr->x) {
countBytes += sectionPtr->numBytes;
sectionPtr = sectionPtr->nextPtr;
}
chunkPtr = sectionPtr->chunkPtr;
while (chunkPtr->nextPtr && x >= chunkPtr->x + chunkPtr->width) {
countBytes += chunkPtr->numBytes;
chunkPtr = chunkPtr->nextPtr;
}
TkTextIndexForwBytes(textPtr, indexPtr, countBytes, indexPtr);
return chunkPtr;
}
void
TkTextIndexOfX(
TkText *textPtr,
int x,
TkTextIndex *indexPtr)
{
TextDInfo *dInfoPtr;
DLine *dlPtr;
assert(textPtr);
if (TkTextIndexGetLine(indexPtr) == TkBTreeGetLastLine(textPtr)) {
return;
}
dInfoPtr = textPtr->dInfoPtr;
dlPtr = FindCachedDLine(textPtr, indexPtr);
if (!dlPtr
&& !(dInfoPtr->flags & DINFO_OUT_OF_DATE)
&& TkTextIndexCompare(indexPtr, &textPtr->topIndex) >= 0) {
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
}
if (!dlPtr) {
DisplayInfo info;
ComputeDisplayLineInfo(textPtr, indexPtr, &info);
if (!(dlPtr = info.lastDLinePtr)) {
TkTextIndex index = *indexPtr;
TkTextIndexBackBytes(textPtr, &index, info.byteOffset, &index);
dlPtr = LayoutDLine(&index, info.displayLineNo);
} else if ((info.lastDLinePtr = info.lastDLinePtr->prevPtr)) {
dlPtr->prevPtr = info.lastDLinePtr->nextPtr = NULL;
} else {
info.dLinePtr = NULL;
}
FreeDLines(textPtr, dlPtr, NULL, DLINE_CACHE);
FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
x += dInfoPtr->x - dInfoPtr->curXPixelOffset;
DLineIndexOfX(textPtr, DLineChunkOfX(textPtr, dlPtr, x, indexPtr, NULL), x, indexPtr);
}
static int
DLineXOfIndex(
TkText *textPtr,
DLine *dlPtr,
int byteIndex)
{
TkTextDispChunkSection *sectionPtr, *nextPtr;
TkTextDispChunk *chunkPtr;
int x;
if (byteIndex == 0 || !(sectionPtr = dlPtr->chunkPtr->sectionPtr)) {
return 0;
}
while (byteIndex >= sectionPtr->numBytes && (nextPtr = sectionPtr->nextPtr)) {
byteIndex -= sectionPtr->numBytes;
sectionPtr = nextPtr;
}
chunkPtr = sectionPtr->chunkPtr;
assert(chunkPtr);
x = 0;
while (true) {
if (byteIndex < chunkPtr->numBytes) {
int unused;
x = chunkPtr->x;
chunkPtr->layoutProcs->bboxProc(textPtr, chunkPtr, byteIndex,
dlPtr->y + dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, &x, &unused, &unused,
&unused);
break;
}
if (!chunkPtr->nextPtr || byteIndex == chunkPtr->numBytes) {
x = chunkPtr->x + chunkPtr->width;
break;
}
byteIndex -= chunkPtr->numBytes;
chunkPtr = chunkPtr->nextPtr;
}
return x;
}
int
TkTextIndexBbox(
TkText *textPtr,
const TkTextIndex *indexPtr,
int *xPtr, int *yPtr,
int *widthPtr, int *heightPtr,
int *charWidthPtr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
TkTextDispChunk *chunkPtr;
TkTextDispChunkSection *sectionPtr;
int byteCount;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
}
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
if (!dlPtr || !dlPtr->chunkPtr || TkTextIndexCompare(&dlPtr->index, indexPtr) > 0) {
return -1;
}
byteCount = TkTextIndexCountBytes(&dlPtr->index, indexPtr);
sectionPtr = dlPtr->chunkPtr->sectionPtr;
while (byteCount >= sectionPtr->numBytes) {
byteCount -= sectionPtr->numBytes;
if (!(sectionPtr = sectionPtr->nextPtr)) {
return -1;
}
}
chunkPtr = sectionPtr->chunkPtr;
while (byteCount >= chunkPtr->numBytes) {
byteCount -= chunkPtr->numBytes;
if (!(chunkPtr = chunkPtr->nextPtr)) {
return -1;
}
}
chunkPtr->layoutProcs->bboxProc(textPtr, chunkPtr, byteCount,
dlPtr->y + dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
heightPtr);
*xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset;
if (byteCount == chunkPtr->numBytes - 1 && !chunkPtr->nextPtr) {
if (charWidthPtr) {
*charWidthPtr = dInfoPtr->maxX - *xPtr;
if (*charWidthPtr > textPtr->charWidth) {
*charWidthPtr = textPtr->charWidth;
}
}
if (*xPtr > dInfoPtr->maxX) {
*xPtr = dInfoPtr->maxX;
}
*widthPtr = dInfoPtr->maxX - *xPtr;
} else {
if (charWidthPtr) {
*charWidthPtr = *widthPtr;
}
}
if (*widthPtr == 0) {
if (*xPtr < dInfoPtr->x) {
return -1;
}
} else {
if (*xPtr + *widthPtr <= dInfoPtr->x) {
return -1;
}
}
if (*xPtr + *widthPtr > dInfoPtr->maxX) {
*widthPtr = dInfoPtr->maxX - *xPtr;
if (*widthPtr <= 0) {
return -1;
}
}
if (*yPtr + *heightPtr > dInfoPtr->maxY) {
*heightPtr = dInfoPtr->maxY - *yPtr;
if (*heightPtr <= 0) {
return -1;
}
}
return 0;
}
bool
TkTextGetDLineInfo(
TkText *textPtr,
const TkTextIndex *indexPtr,
int *xPtr, int *yPtr,
int *widthPtr, int *heightPtr,
int *basePtr)
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
int dlx;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
}
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
if (!dlPtr || TkTextIndexCompare(&dlPtr->index, indexPtr) > 0) {
return false;
}
dlx = dlPtr->chunkPtr ? dlPtr->chunkPtr->x : 0;
*xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx;
*widthPtr = dlPtr->length - dlx;
*yPtr = dlPtr->y;
if (dlPtr->y + dlPtr->height > dInfoPtr->maxY) {
*heightPtr = dInfoPtr->maxY - dlPtr->y;
} else {
*heightPtr = dlPtr->height;
}
*basePtr = dlPtr->baseline;
return true;
}
static void
ElideBboxProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr,
int index,
int y,
int lineHeight,
int baseline,
int *xPtr, int *yPtr,
int *widthPtr,
int *heightPtr)
{
*xPtr = chunkPtr->x;
*yPtr = y;
*widthPtr = *heightPtr = 0;
}
static int
ElideMeasureProc(
TkTextDispChunk *chunkPtr,
int x)
{
return 0;
}
static int
CharMeasureProc(
TkTextDispChunk *chunkPtr,
int x)
{
return CharChunkMeasureChars(chunkPtr, NULL, 0, 0, chunkPtr->numBytes - 1, chunkPtr->x, x, 0, NULL);
}
static void
CharBboxProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr,
int byteIndex,
int y,
int lineHeight,
int baseline,
int *xPtr, int *yPtr,
int *widthPtr,
int *heightPtr)
{
CharInfo *ciPtr = chunkPtr->clientData;
int offset = ciPtr->baseOffset + byteIndex;
int maxX = chunkPtr->width + chunkPtr->x;
int nextX;
CharChunkMeasureChars(chunkPtr, NULL, 0, 0, byteIndex, chunkPtr->x, -1, 0, xPtr);
if (byteIndex >= ciPtr->numBytes) {
*widthPtr = maxX - *xPtr;
} else if (ciPtr->u.chars[offset] == '\t' && byteIndex == ciPtr->numBytes - 1) {
*widthPtr = maxX - *xPtr;
} else {
CharChunkMeasureChars(chunkPtr, NULL, 0, byteIndex, byteIndex + 1, *xPtr, -1, 0, &nextX);
if (nextX >= maxX) {
*widthPtr = maxX - *xPtr;
} else {
*widthPtr = nextX - *xPtr;
if (chunkPtr->additionalWidth && IsExpandableSpace(ciPtr->u.chars + offset)) {
const char *base = ciPtr->u.chars + ciPtr->baseOffset;
const char *q = ciPtr->u.chars + offset;
unsigned numSpaces = chunkPtr->numSpaces;
unsigned remaining = chunkPtr->additionalWidth;
do {
unsigned space = (remaining + numSpaces - 1)/numSpaces;
*widthPtr += space;
remaining -= space;
assert(numSpaces > 0);
numSpaces -= 1;
if (base == q) {
break;
}
q = Tcl_UtfPrev(q, ciPtr->u.chars);
} while (IsExpandableSpace(q));
}
}
}
*yPtr = y + baseline - chunkPtr->minAscent;
*heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;
}
static TkTextDispChunk *
FindEndOfTab(
TkTextDispChunk *chunkPtr,
int *decimalPtr)
{
TkTextDispChunk *decimalChunkPtr = NULL;
bool gotDigit = false;
*decimalPtr = 0;
for ( ; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
if (IsCharChunk(chunkPtr)) {
CharInfo *ciPtr = chunkPtr->clientData;
const char *s = ciPtr->u.chars + ciPtr->baseOffset;
const char *p;
int i;
for (p = s, i = 0; i < ciPtr->numBytes; ++p, ++i) {
if (isdigit(*p)) {
gotDigit = true;
} else if (*p == '.' || *p == ',') {
*decimalPtr = p - s;
decimalChunkPtr = chunkPtr;
} else if (gotDigit) {
if (!decimalChunkPtr) {
*decimalPtr = p - s;
decimalChunkPtr = chunkPtr;
}
return decimalChunkPtr;
}
}
}
}
return decimalChunkPtr;
}
static void
AdjustForTab(
LayoutData *data)
{
int x, desired = 0, delta, width;
TkTextDispChunk *chunkPtr, *nextChunkPtr, *chPtr;
TkTextTabArray *tabArrayPtr;
TkText *textPtr;
int tabX, tabIndex;
TkTextTabAlign alignment;
assert(data->tabIndex >= 0);
assert(data->tabChunkPtr);
chunkPtr = data->tabChunkPtr;
nextChunkPtr = chunkPtr->nextPtr;
if (!nextChunkPtr) {
return;
}
tabIndex = data->tabIndex;
textPtr = data->textPtr;
tabArrayPtr = data->tabArrayPtr;
x = nextChunkPtr->x;
if (!tabArrayPtr || tabArrayPtr->numTabs == 0) {
unsigned tabWidth = textPtr->charWidth*8;
tabWidth = MAX(1, tabWidth);
if (textPtr->tabStyle == TK_TEXT_TABSTYLE_TABULAR) {
desired = tabWidth*(tabIndex + 1);
} else {
desired = NextTabStop(tabWidth, x, 0);
}
} else {
if (tabIndex < tabArrayPtr->numTabs) {
alignment = tabArrayPtr->tabs[tabIndex].alignment;
tabX = tabArrayPtr->tabs[tabIndex].location;
} else {
tabX = (int) (tabArrayPtr->lastTab +
(tabIndex + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement + 0.5);
alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs - 1].alignment;
}
switch (alignment) {
case LEFT:
desired = tabX;
break;
case CENTER:
case RIGHT:
width = 0;
for (chPtr = nextChunkPtr; chPtr; chPtr = chPtr->nextPtr) {
width += chPtr->width;
}
desired = tabX - (alignment == CENTER ? width/2 : width);
break;
case NUMERIC: {
int decimal;
TkTextDispChunk *decimalChunkPtr = FindEndOfTab(nextChunkPtr, &decimal);
if (decimalChunkPtr) {
int curX;
CharChunkMeasureChars(decimalChunkPtr, NULL, 0, 0, decimal,
decimalChunkPtr->x, -1, 0, &curX);
desired = tabX - (curX - x);
} else {
width = 0;
for (chPtr = nextChunkPtr; chPtr; chPtr = chPtr->nextPtr) {
width += chPtr->width;
}
desired = tabX - width;
}
}
}
}
delta = MAX(textPtr->spaceWidth, desired - x);
for (chPtr = nextChunkPtr; chPtr; chPtr = chPtr->nextPtr) {
chPtr->x += delta;
}
chunkPtr->width += delta;
}
static void
ComputeSizeOfTab(
LayoutData *data)
{
TkText *textPtr;
TkTextTabArray *tabArrayPtr;
unsigned tabX, tabWidth;
TkTextTabAlign alignment;
textPtr = data->textPtr;
tabArrayPtr = data->tabArrayPtr;
if (!tabArrayPtr || tabArrayPtr->numTabs == 0) {
tabWidth = MAX(1, textPtr->charWidth*8);
} else {
tabWidth = 0;
}
do {
data->tabIndex += 1;
if (!tabArrayPtr || tabArrayPtr->numTabs == 0) {
tabX = tabWidth*(data->tabIndex + 1);
alignment = LEFT;
} else if (data->tabIndex < tabArrayPtr->numTabs) {
tabX = tabArrayPtr->tabs[data->tabIndex].location;
alignment = tabArrayPtr->tabs[data->tabIndex].alignment;
} else {
tabX = (int) (tabArrayPtr->lastTab
+ (data->tabIndex + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement
+ 0.5);
alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs - 1].alignment;
}
} while (tabX <= data->x && data->tabStyle == TK_TEXT_TABSTYLE_WORDPROCESSOR);
switch (alignment) {
case CENTER:
if (data->maxX - tabX < tabX - data->x) {
data->tabSize = data->maxX - data->x - 2*(data->maxX - tabX);
} else {
data->tabSize = 0;
}
break;
case RIGHT:
data->tabSize = 0;
break;
case LEFT:
case NUMERIC:
data->tabSize = tabX - data->x;
assert(textPtr->spaceWidth > 0);
break;
}
data->tabSize = MAX(data->tabSize, textPtr->spaceWidth);
}
static int
NextTabStop(
unsigned tabWidth,
int x,
int tabOrigin)
{
int rem;
assert(tabWidth > 0);
tabWidth *= 8;
x += tabWidth;
if ((rem = (x - tabOrigin) % tabWidth) < 0) {
rem += tabWidth;
}
x -= rem;
return x;
}
#if TK_DRAW_IN_CONTEXT
static int
TkpMeasureChars(
Tk_Font tkfont,
const char *source,
int numBytes,
int rangeStart,
int rangeLength,
int maxLength,
int flags,
int *lengthPtr)
{
return TkpMeasureCharsInContext(tkfont, source, numBytes, rangeStart,
rangeLength, maxLength, flags, lengthPtr);
}
#else
static int
TkpMeasureChars(
Tk_Font tkfont,
const char *source,
int numBytes,
int rangeStart,
int rangeLength,
int maxLength,
int flags,
int *lengthPtr)
{
return Tk_MeasureChars(tkfont, source + rangeStart, rangeLength, maxLength, flags, lengthPtr);
}
#endif
static int
MeasureChars(
Tk_Font tkfont,
const char *source,
int maxBytes,
int rangeStart, int rangeLength,
int startX,
int maxX,
int flags,
int *nextXPtr)
{
int curX, width, ch;
const char *special, *end, *start;
ch = 0;
curX = startX;
start = source + rangeStart;
end = start + rangeLength;
special = start;
while (start < end) {
if (start >= special) {
for (special = start; special < end; ++special) {
ch = *special;
if (ch == '\t' || ch == '\n') {
break;
}
}
}
if (maxX >= 0 && curX >= maxX) {
break;
}
start += TkpMeasureChars(tkfont, source, maxBytes, start - source, special - start,
maxX >= 0 ? maxX - curX : -1, flags, &width);
curX += width;
if (start < special) {
break;
}
if (special < end) {
if (ch != '\t') {
break;
}
start += 1;
}
}
if (nextXPtr) {
*nextXPtr = curX;
}
return start - (source + rangeStart);
}
static ScrollMethod
TextGetScrollInfoObj(
Tcl_Interp *interp,
TkText *textPtr,
int objc,
Tcl_Obj *const objv[],
double *dblPtr,
int *intPtr)
{
static const char *const subcommands[] = {
"moveto", "scroll", NULL
};
enum viewSubcmds {
VIEW_MOVETO, VIEW_SCROLL
};
static const char *const units[] = {
"units", "pages", "pixels", NULL
};
enum viewUnits {
VIEW_SCROLL_UNITS, VIEW_SCROLL_PAGES, VIEW_SCROLL_PIXELS
};
int index;
if (Tcl_GetIndexFromObjStruct(interp, objv[2], subcommands, sizeof(char *), "option", 0, &index)
!= TCL_OK) {
return SCROLL_ERROR;
}
switch ((enum viewSubcmds) index) {
case VIEW_MOVETO:
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "fraction");
return SCROLL_ERROR;
}
if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) {
return SCROLL_ERROR;
}
return SCROLL_MOVETO;
case VIEW_SCROLL:
if (objc != 5) {
Tcl_WrongNumArgs(interp, 3, objv, "number units|pages|pixels");
return SCROLL_ERROR;
}
if (Tcl_GetIndexFromObjStruct(interp, objv[4], units, sizeof(char *), "argument", 0, &index)
!= TCL_OK) {
return SCROLL_ERROR;
}
switch ((enum viewUnits) index) {
case VIEW_SCROLL_PAGES:
if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
return SCROLL_ERROR;
}
return SCROLL_PAGES;
case VIEW_SCROLL_PIXELS:
if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3], intPtr) != TCL_OK) {
return SCROLL_ERROR;
}
return SCROLL_PIXELS;
case VIEW_SCROLL_UNITS:
if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
return SCROLL_ERROR;
}
return SCROLL_UNITS;
}
}
assert(!"unexpected switch fallthrough");
return SCROLL_ERROR;
}
static CharInfo *
AllocCharInfo(
TkText *textPtr)
{
TextDInfo *dInfoPtr;
CharInfo *ciPtr;
assert(textPtr);
dInfoPtr = textPtr->dInfoPtr;
if ((ciPtr = dInfoPtr->charInfoPoolPtr)) {
dInfoPtr->charInfoPoolPtr = dInfoPtr->charInfoPoolPtr->u.next;
} else {
ciPtr = malloc(sizeof(CharInfo));
DEBUG_ALLOC(tkTextCountNewCharInfo++);
}
return ciPtr;
}
static void
FreeCharInfo(
TkText *textPtr,
CharInfo *ciPtr)
{
TextDInfo *dInfoPtr;
assert(textPtr);
assert(ciPtr);
TkBTreeFreeSegment(ciPtr->segPtr);
dInfoPtr = textPtr->dInfoPtr;
ciPtr->u.next = dInfoPtr->charInfoPoolPtr;
dInfoPtr->charInfoPoolPtr = ciPtr;
}
static int
ComputeBreakIndex(
TkText *textPtr,
const TkTextDispChunk *chunkPtr,
TkTextSegment *segPtr,
int byteOffset,
TkWrapMode wrapMode,
TkTextSpaceMode spaceMode)
{
switch (wrapMode) {
case TEXT_WRAPMODE_NONE:
break;
case TEXT_WRAPMODE_CHAR:
case TEXT_WRAPMODE_NULL:
return chunkPtr->numBytes;
case TEXT_WRAPMODE_WORD:
case TEXT_WRAPMODE_CODEPOINT: {
TkTextSegment *nextPtr;
const char *p;
int count;
if (segPtr->typePtr == &tkTextHyphenType) {
return 1;
}
if (chunkPtr->numBytes + byteOffset == segPtr->size) {
for (nextPtr = segPtr->nextPtr; nextPtr; nextPtr = nextPtr->nextPtr) {
if (nextPtr->size > 0) {
if (!(nextPtr->typePtr->group & (SEG_GROUP_CHAR|SEG_GROUP_HYPHEN))) {
return chunkPtr->numBytes;
}
break;
} else if (nextPtr->typePtr == &tkTextBranchType) {
nextPtr = nextPtr->body.branch.nextPtr->nextPtr;
}
}
}
count = chunkPtr->numBytes;
if (chunkPtr->endsWithSyllable) {
assert(chunkPtr->numBytes > 0);
count -= 1;
}
p = segPtr->body.chars + byteOffset + count - 1;
if (wrapMode == TEXT_WRAPMODE_WORD) {
for ( ; count > 0; --count, --p) {
switch (*p) {
case ' ':
if (spaceMode == TEXT_SPACEMODE_EXACT) {
return -1;
}
case '\t': case '\n': case '\v': case '\f': case '\r':
return count;
}
}
} else {
const char *brks;
int i;
if (*p == '\n') {
return count;
}
brks = chunkPtr->brks;
i = count - 1;
assert(brks);
for ( ; i >= 0; --i, --p) {
if (brks[i] == LINEBREAK_ALLOWBREAK) {
if (*p == ' ' && spaceMode == TEXT_SPACEMODE_EXACT) {
return -1;
}
return i + 1;
}
}
}
break;
}
}
return -1;
}
void
TkTextCheckDisplayLineConsistency(
const TkText *textPtr)
{
DLine *dlPtr;
for (dlPtr = textPtr->dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
if (dlPtr->chunkPtr) {
const TkTextLine *linePtr = TkTextIndexGetLine(&dlPtr->index);
if (!linePtr->parentPtr || linePtr->parentPtr == (void *) 0x61616161) {
Tcl_Panic("CheckDisplayLineConsisteny: expired index in display line");
}
}
}
for (dlPtr = textPtr->dInfoPtr->savedDLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
if (dlPtr->chunkPtr) {
const TkTextLine *linePtr = TkTextIndexGetLine(&dlPtr->index);
if (!linePtr->parentPtr || linePtr->parentPtr == (void *) 0x61616161) {
Tcl_Panic("CheckDisplayLineConsisteny: expired index in saved display line");
}
}
}
dlPtr = textPtr->dInfoPtr->cachedDLinePtr;
if (dlPtr && dlPtr->chunkPtr) {
const TkTextLine *linePtr = TkTextIndexGetLine(&dlPtr->index);
if (!linePtr->parentPtr || linePtr->parentPtr == (void *) 0x61616161) {
Tcl_Panic("CheckDisplayLineConsisteny: expired index in cached display line");
}
}
}
static void
CheckLineMetricConsistency(
const TkText *textPtr)
{
const TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
unsigned epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
const TkTextLine *lastLinePtr;
const TkTextLine *linePtr;
unsigned lineNum = 0;
unsigned reference;
assert(textPtr->pixelReference >= 0);
linePtr = TkBTreeGetStartLine(textPtr);
lastLinePtr = TkBTreeGetLastLine(textPtr);
if (textPtr->dInfoPtr->firstLineNo != TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr, NULL)) {
Tcl_Panic("CheckLineMetricConsistency: firstLineNo is not up-to-date");
}
if (textPtr->dInfoPtr->lastLineNo != TkBTreeLinesTo(sharedTextPtr->tree, NULL, lastLinePtr, NULL)) {
Tcl_Panic("CheckLineMetricConsistency: lastLineNo is not up-to-date");
}
reference = textPtr->pixelReference;
while (linePtr != lastLinePtr) {
const TkTextPixelInfo *pixelInfo = linePtr->pixelInfo + reference;
const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
if ((pixelInfo->epoch & EPOCH_MASK) != epoch) {
Tcl_Panic("CheckLineMetricConsistency: line metric info is not up-to-date");
}
if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
Tcl_Panic("CheckLineMetricConsistency: computation of this line is not yet complete");
}
linePtr = linePtr->nextPtr;
lineNum += 1;
while (linePtr != lastLinePtr && !linePtr->logicalLine) {
const TkTextPixelInfo *pixelInfo = linePtr->pixelInfo + reference;
if ((pixelInfo->epoch & EPOCH_MASK) != epoch) {
Tcl_Panic("CheckLineMetricConsistency: line metric info is not up-to-date");
}
if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
Tcl_Panic("CheckLineMetricConsistency: partial flag shouldn't be set");
}
if (pixelInfo->dispLineInfo) {
Tcl_Panic("CheckLineMetricConsistency: merged line should not have display line info");
}
if (pixelInfo->height > 0) {
Tcl_Panic("CheckLineMetricConsistency: merged line should not have a height");
}
linePtr = linePtr->nextPtr;
lineNum += 1;
}
if (!lastLinePtr->nextPtr) {
const TkTextPixelInfo *pixelInfo = lastLinePtr->pixelInfo + reference;
if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
Tcl_Panic("CheckLineMetricConsistency: partial flag shouldn't be set in last line");
}
if (pixelInfo->dispLineInfo) {
Tcl_Panic("CheckLineMetricConsistency: last line should not have display line info");
}
if (pixelInfo->height > 0) {
Tcl_Panic("CheckLineMetricConsistency: last line should not have a height");
}
}
if (dispLineInfo) {
unsigned pixels = 0;
unsigned k;
if (dispLineInfo->numDispLines == 1) {
Tcl_Panic("CheckLineMetricConsistency: this line should not have display line info");
}
for (k = 0; k < dispLineInfo->numDispLines; ++k) {
const TkTextDispLineEntry *entry = dispLineInfo->entry + k;
if (k == 0 && entry->byteOffset != 0) {
Tcl_Panic("CheckLineMetricConsistency: first display line (line %d) should "
"have byte offset zero", lineNum);
}
if ((entry + 1)->byteOffset <= entry->byteOffset) {
Tcl_Panic("CheckLineMetricConsistency: display line (line %d) has invalid byte "
"offset %d (previous is %d)", lineNum, (entry + 1)->byteOffset,
entry->byteOffset);
}
if (entry->height == 0) {
Tcl_Panic("CheckLineMetricConsistency: display line (%d) has zero height", lineNum);
}
pixels += entry->height;
}
if (pixels != pixelInfo->height) {
Tcl_Panic("CheckLineMetricConsistency: sum of display line pixels is wrong (line %d)",
lineNum);
}
}
}
}
void
TkTextCheckLineMetricUpdate(
const TkText *textPtr)
{
const TkRangeList *ranges;
const TkRange *range;
TkTextBTree tree;
unsigned epoch;
int n, total;
assert(textPtr);
if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
return;
}
if (!textPtr->endMarker->sectionPtr || !textPtr->startMarker->sectionPtr) {
return;
}
ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
tree = textPtr->sharedTextPtr->tree;
total = TkBTreeNumLines(tree, textPtr);
if (!TkRangeListIsEmpty(ranges) && TkRangeListHigh(ranges) >= total) {
Tcl_Panic("TkTextCheckLineMetricUpdate: line %d is out of range (max=%d)\n",
TkRangeListHigh(ranges), total);
}
range = TkRangeListFirst(ranges);
epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
for (n = 0; n < total - 1; ++n) {
const TkTextPixelInfo *pixelInfo;
if (range && range->low == n) {
n = range->high;
range = TkRangeListNext(ranges, range);
continue;
}
pixelInfo = TkBTreeLinePixelInfo(textPtr, TkBTreeFindLine(tree, textPtr, n));
if (pixelInfo->epoch && (pixelInfo->epoch & EPOCH_MASK) != epoch) {
Tcl_Panic("TkTextCheckLineMetricUpdate: line %d is not up-to-date\n", n);
}
if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
Tcl_Panic("TkTextCheckLineMetricUpdate: line metric computation (line %d) is not "
"yet complete\n", n);
}
}
}
static int
CharChunkMeasureChars(
TkTextDispChunk *chunkPtr,
const char *chars,
int charsLen,
int start, int end,
int startX,
int maxX,
int flags,
int *nextXPtr)
{
Tk_Font tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
CharInfo *ciPtr = chunkPtr->clientData;
int fit, rangeStart;
#if TK_LAYOUT_WITH_BASE_CHUNKS
int widthUntilStart = 0;
assert(chunkPtr->baseChunkPtr);
if (!chars) {
const Tcl_DString *baseChars = &chunkPtr->baseChunkPtr->baseChars;
chars = Tcl_DStringValue(baseChars);
charsLen = Tcl_DStringLength(baseChars);
start += ciPtr->baseOffset;
if (end == -1) {
assert(ciPtr->numBytes >= chunkPtr->wrappedAtSpace);
end = ciPtr->baseOffset + ciPtr->numBytes - chunkPtr->wrappedAtSpace;
} else {
end += ciPtr->baseOffset;
}
if (chunkPtr->wrappedAtSpace) {
assert(charsLen >= 1);
charsLen -= 1;
}
}
if (start != ciPtr->baseOffset) {
MeasureChars(tkfont, chars, charsLen, 0, start, 0, -1, 0, &widthUntilStart);
}
startX = chunkPtr->baseChunkPtr->x + (startX - widthUntilStart - chunkPtr->x);
rangeStart = 0;
#else
rangeStart = start;
if (!chars) {
chars = ciPtr->u.chars;
charsLen = ciPtr->numBytes;
}
#endif
if (end == -1) {
end = charsLen;
}
fit = MeasureChars(tkfont, chars, charsLen, rangeStart, end - rangeStart,
startX, maxX, flags, nextXPtr);
return MAX(0, fit - start);
}
static bool
EndsWithSyllable(
TkTextSegment *segPtr)
{
if (segPtr->typePtr->group == SEG_GROUP_CHAR) {
for (segPtr = segPtr->nextPtr; segPtr; segPtr = segPtr->nextPtr) {
switch (segPtr->typePtr->group) {
case SEG_GROUP_MARK:
break;
case SEG_GROUP_HYPHEN:
return true;
case SEG_GROUP_BRANCH:
if (segPtr->typePtr == &tkTextBranchType) {
segPtr = segPtr->body.branch.nextPtr;
break;
}
default:
return false;
}
}
}
return false;
}
int
TkTextCharLayoutProc(
const TkTextIndex *indexPtr,
TkTextSegment *segPtr,
int byteOffset,
int maxX,
int maxBytes,
bool noCharsYet,
TkWrapMode wrapMode,
TkTextSpaceMode spaceMode,
TkTextDispChunk *chunkPtr)
{
Tk_Font tkfont;
int nextX, bytesThatFit;
Tk_FontMetrics fm;
CharInfo *ciPtr;
char const *p;
assert(indexPtr->textPtr);
assert(chunkPtr->clientData);
tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
ciPtr = chunkPtr->clientData;
chunkPtr->layoutProcs = &layoutCharProcs;
p = segPtr->body.chars + byteOffset;
bytesThatFit = CharChunkMeasureChars(chunkPtr, ciPtr->u.chars, ciPtr->baseOffset + maxBytes,
ciPtr->baseOffset, -1, chunkPtr->x, maxX, TK_ISOLATE_END, &nextX);
if (bytesThatFit < maxBytes) {
if (bytesThatFit == 0 && noCharsYet) {
int ch, chLen = TkUtfToUniChar(p, &ch);
bytesThatFit = CharChunkMeasureChars(chunkPtr, ciPtr->u.chars, ciPtr->baseOffset + chLen,
ciPtr->baseOffset, -1, chunkPtr->x, -1, 0, &nextX);
}
if (spaceMode == TEXT_SPACEMODE_TRIM) {
while (isblank(p[bytesThatFit])) {
bytesThatFit += 1;
}
}
if (p[bytesThatFit] == '\n') {
bytesThatFit += 1;
} else if (spaceMode == TEXT_SPACEMODE_NONE
&& nextX <= maxX
&& ((1 << wrapMode) & ((1 << TEXT_WRAPMODE_WORD) | (1 << TEXT_WRAPMODE_CODEPOINT)))
&& isblank(p[bytesThatFit])
&& !(bytesThatFit == 0
&& chunkPtr->prevCharChunkPtr
&& chunkPtr->prevCharChunkPtr->wrappedAtSpace)) {
nextX = maxX;
bytesThatFit += 1;
chunkPtr->wrappedAtSpace = true;
}
if (bytesThatFit == 0) {
return 0;
}
}
Tk_GetFontMetrics(tkfont, &fm);
chunkPtr->endsWithSyllable =
p[bytesThatFit] == '\0' && indexPtr->textPtr->hyphenate && EndsWithSyllable(segPtr);
chunkPtr->numBytes = bytesThatFit;
chunkPtr->segByteOffset = byteOffset;
chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;
chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;
chunkPtr->minHeight = 0;
chunkPtr->width = nextX - chunkPtr->x;
chunkPtr->breakIndex =
ComputeBreakIndex(indexPtr->textPtr, chunkPtr, segPtr, byteOffset, wrapMode, spaceMode);
ciPtr->numBytes = chunkPtr->numBytes;
return 1;
}
static void
CharDisplayProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr,
int x,
int y,
int height,
int baseline,
Display *display,
Drawable dst,
int screenY)
{
assert(chunkPtr->width == 0 || !chunkPtr->stylePtr->sValuePtr->elide);
if (chunkPtr->width > 0 && x + chunkPtr->width > 0) {
DisplayChars(textPtr, chunkPtr, x, y, baseline, display, dst);
}
}
static void
CharUndisplayProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr)
{
CharInfo *ciPtr = chunkPtr->clientData;
if (!ciPtr) {
return;
}
#if TK_LAYOUT_WITH_BASE_CHUNKS
{
TkTextDispChunk *baseChunkPtr = chunkPtr->baseChunkPtr;
if (chunkPtr == baseChunkPtr) {
Tcl_DStringFree(&baseChunkPtr->baseChars);
DEBUG_ALLOC(tkTextCountDestroyBaseChars++);
} else if (baseChunkPtr && ciPtr->numBytes > 0) {
assert(ciPtr->baseOffset + ciPtr->numBytes == Tcl_DStringLength(&baseChunkPtr->baseChars));
Tcl_DStringSetLength(&baseChunkPtr->baseChars, ciPtr->baseOffset);
baseChunkPtr->baseWidth = 0;
}
if (chunkPtr->prevPtr) {
chunkPtr->x -= chunkPtr->prevPtr->xAdjustment;
}
chunkPtr->baseChunkPtr = NULL;
}
#endif
FreeCharInfo(textPtr, ciPtr);
chunkPtr->clientData = NULL;
}
static void
HyphenUndisplayProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr)
{
TkTextSegment *hyphenPtr = chunkPtr->clientData;
if (hyphenPtr) {
TkBTreeFreeSegment(hyphenPtr);
}
chunkPtr->clientData = NULL;
}
static GC
GetForegroundGC(
const TkText *textPtr,
const TkTextDispChunk *chunkPtr)
{
const TkTextSegment *segPtr = ((const CharInfo *) chunkPtr->clientData)->segPtr;
if (segPtr == textPtr->dInfoPtr->endOfLineSegPtr) {
if (chunkPtr->stylePtr->eolGC != None) {
return chunkPtr->stylePtr->eolGC;
}
} else if (segPtr->typePtr == &tkTextHyphenType) {
if (chunkPtr->stylePtr->hyphenGC != None) {
return chunkPtr->stylePtr->hyphenGC;
}
}
return chunkPtr->stylePtr->fgGC;
}
#if TK_DRAW_IN_CONTEXT
# if defined(_WIN32) || defined(__UNIX__)
static void
DrawCharsInContext(
Display *display,
Drawable drawable,
GC gc,
Tk_Font tkfont,
const char *source,
int numBytes,
int rangeStart,
int rangeLength,
int x, int y,
int xOffset)
{
Tk_DrawChars(display, drawable, gc, tkfont, source + rangeStart, rangeLength, xOffset, y);
}
# else
static void
DrawCharsInContext(
Display *display,
Drawable drawable,
GC gc,
Tk_Font tkfont,
const char *source,
int numBytes,
int rangeStart,
int rangeLength,
int x, int y,
int xOffset)
{
TkpDrawCharsInContext(display, drawable, gc, tkfont,
source, numBytes, rangeStart, rangeLength, x, y);
}
# endif
static void
DrawChars(
TkText *textPtr,
TkTextDispChunk *chunkPtr,
int x,
int y,
int offsetX,
int offsetBytes,
Display *display,
Drawable dst)
{
const TkTextDispChunk *baseChunkPtr;
unsigned numBytes;
assert(chunkPtr->baseChunkPtr);
baseChunkPtr = chunkPtr->baseChunkPtr;
numBytes = Tcl_DStringLength(&baseChunkPtr->baseChars);
if (numBytes > offsetBytes) {
const char *string;
const CharInfo *ciPtr;
const TextStyle *stylePtr;
const StyleValues *sValuePtr;
int xDisplacement, start, len;
GC fgGC;
string = Tcl_DStringValue(&baseChunkPtr->baseChars);
ciPtr = chunkPtr->clientData;
start = ciPtr->baseOffset + offsetBytes;
len = ciPtr->numBytes - offsetBytes;
assert(ciPtr->numBytes >= offsetBytes);
if (len == 0 || (string[start + len - 1] == '\t' && --len == 0)) {
return;
}
stylePtr = chunkPtr->stylePtr;
sValuePtr = stylePtr->sValuePtr;
ciPtr = chunkPtr->clientData;
xDisplacement = x - chunkPtr->x;
fgGC = GetForegroundGC(textPtr, chunkPtr);
DrawCharsInContext(display, dst, fgGC, sValuePtr->tkfont, string, numBytes,
start, len, baseChunkPtr->x + xDisplacement, y - sValuePtr->offset,
chunkPtr->x + textPtr->dInfoPtr->x);
if (sValuePtr->underline) {
TkUnderlineCharsInContext(display, dst, stylePtr->ulGC, sValuePtr->tkfont, string,
numBytes, baseChunkPtr->x + xDisplacement, y - sValuePtr->offset,
start, start + len);
}
if (sValuePtr->overstrike) {
Tk_FontMetrics fm;
Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
TkUnderlineCharsInContext(display, dst, stylePtr->ovGC, sValuePtr->tkfont, string,
numBytes, baseChunkPtr->x + xDisplacement,
y - sValuePtr->offset - fm.descent - (fm.ascent*3)/10,
start, start + len);
}
}
}
#else
static void
DrawChars(
TkText *textPtr,
TkTextDispChunk *chunkPtr,
int x,
int y,
int offsetX,
int offsetBytes,
Display *display,
Drawable dst)
{
const CharInfo *ciPtr;
int numBytes;
ciPtr = chunkPtr->clientData;
numBytes = ciPtr->numBytes;
assert(offsetBytes >= ciPtr->baseOffset);
if (numBytes > offsetBytes) {
const TextStyle *stylePtr = chunkPtr->stylePtr;
if (stylePtr->fgGC != None) {
const StyleValues *sValuePtr;
const char *string;
GC fgGC;
string = ciPtr->u.chars + offsetBytes;
numBytes -= offsetBytes;
if (string[numBytes - 1] == '\t' && --numBytes == 0) {
return;
}
sValuePtr = stylePtr->sValuePtr;
fgGC = GetForegroundGC(textPtr, chunkPtr);
Tk_DrawChars(display, dst, fgGC, sValuePtr->tkfont, string, numBytes,
offsetX, y - sValuePtr->offset);
if (sValuePtr->underline) {
Tk_UnderlineChars(display, dst, stylePtr->ulGC, sValuePtr->tkfont,
string, offsetX, y - sValuePtr->offset, 0, numBytes);
}
if (sValuePtr->overstrike) {
Tk_FontMetrics fm;
Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
Tk_UnderlineChars(display, dst, stylePtr->ovGC, sValuePtr->tkfont, string, offsetX,
y - sValuePtr->offset - fm.descent - (fm.ascent*3)/10, 0, numBytes);
}
}
}
}
#endif
static void
DisplayChars(
TkText *textPtr,
TkTextDispChunk *chunkPtr,
int x,
int y,
int baseline,
Display *display,
Drawable dst)
{
const TextStyle *stylePtr = chunkPtr->stylePtr;
int offsetBytes, offsetX;
assert(!stylePtr->sValuePtr->elide);
if (stylePtr->fgGC == None) {
return;
}
offsetX = x;
offsetBytes = (x >= 0) ? CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, x, 0, 0, &offsetX) : 0;
DrawChars(textPtr, chunkPtr, x, y + baseline, offsetX, offsetBytes, display, dst);
}