1 /* 2 * RichEdit style management functions 3 * 4 * Copyright 2004 by Krzysztof Foltman 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 */ 20 21 #include "editor.h" 22 23 WINE_DEFAULT_DEBUG_CHANNEL(richedit); 24 WINE_DECLARE_DEBUG_CHANNEL(richedit_style); 25 26 static int all_refs = 0; 27 28 /* the following routines assume that: 29 * - char2[AW] extends char[AW] by adding fields at the end of the charA form) 30 * - szFaceName is the last field of char[AW] form, and wWeight the first of 2[AW] 31 * - the difference between A and W form is the szFaceName as Ansi vs Unicode string 32 * - because of alignment, offset of wWeight field in 2[AW] structure *IS NOT* 33 * sizeof(char[AW]) 34 */ 35 36 BOOL cfany_to_cf2w(CHARFORMAT2W *to, const CHARFORMAT2W *from) 37 { 38 if (from->cbSize == sizeof(CHARFORMATA)) 39 { 40 CHARFORMATA *f = (CHARFORMATA *)from; 41 CopyMemory(to, f, FIELD_OFFSET(CHARFORMATA, szFaceName)); 42 to->cbSize = sizeof(CHARFORMAT2W); 43 if (f->dwMask & CFM_FACE) { 44 MultiByteToWideChar(CP_ACP, 0, f->szFaceName, -1, to->szFaceName, ARRAY_SIZE(to->szFaceName)); 45 } 46 return TRUE; 47 } 48 if (from->cbSize == sizeof(CHARFORMATW)) 49 { 50 CHARFORMATW *f = (CHARFORMATW *)from; 51 CopyMemory(to, f, sizeof(*f)); 52 /* theoretically, we don't need to zero the remaining memory */ 53 ZeroMemory(&to->wWeight, sizeof(CHARFORMAT2W)-FIELD_OFFSET(CHARFORMAT2W, wWeight)); 54 to->cbSize = sizeof(CHARFORMAT2W); 55 return TRUE; 56 } 57 if (from->cbSize == sizeof(CHARFORMAT2A)) 58 { 59 CHARFORMAT2A *f = (CHARFORMAT2A *)from; 60 /* copy the A structure without face name */ 61 CopyMemory(to, f, FIELD_OFFSET(CHARFORMATA, szFaceName)); 62 /* convert face name */ 63 if (f->dwMask & CFM_FACE) 64 MultiByteToWideChar(CP_ACP, 0, f->szFaceName, -1, to->szFaceName, ARRAY_SIZE(to->szFaceName)); 65 /* copy the rest of the 2A structure to 2W */ 66 CopyMemory(&to->wWeight, &f->wWeight, sizeof(CHARFORMAT2A)-FIELD_OFFSET(CHARFORMAT2A, wWeight)); 67 to->cbSize = sizeof(CHARFORMAT2W); 68 return TRUE; 69 } 70 if (from->cbSize == sizeof(CHARFORMAT2W)) 71 { 72 CopyMemory(to, from, sizeof(CHARFORMAT2W)); 73 return TRUE; 74 } 75 76 return FALSE; 77 } 78 79 BOOL cf2w_to_cfany(CHARFORMAT2W *to, const CHARFORMAT2W *from) 80 { 81 assert(from->cbSize == sizeof(CHARFORMAT2W)); 82 if (to->cbSize == sizeof(CHARFORMATA)) 83 { 84 CHARFORMATA *t = (CHARFORMATA *)to; 85 CopyMemory(t, from, FIELD_OFFSET(CHARFORMATA, szFaceName)); 86 WideCharToMultiByte(CP_ACP, 0, from->szFaceName, -1, t->szFaceName, sizeof(t->szFaceName), NULL, NULL); 87 t->cbSize = sizeof(*t); /* it was overwritten by CopyMemory */ 88 t->dwMask &= CFM_ALL; 89 t->dwEffects &= CFM_EFFECTS; 90 return TRUE; 91 } 92 if (to->cbSize == sizeof(CHARFORMATW)) 93 { 94 CHARFORMATW *t = (CHARFORMATW *)to; 95 CopyMemory(t, from, sizeof(*t)); 96 t->cbSize = sizeof(*t); /* it was overwritten by CopyMemory */ 97 t->dwMask &= CFM_ALL; 98 t->dwEffects &= CFM_EFFECTS; 99 return TRUE; 100 } 101 if (to->cbSize == sizeof(CHARFORMAT2A)) 102 { 103 CHARFORMAT2A *t = (CHARFORMAT2A *)to; 104 /* copy the A structure without face name */ 105 CopyMemory(t, from, FIELD_OFFSET(CHARFORMATA, szFaceName)); 106 /* convert face name */ 107 WideCharToMultiByte(CP_ACP, 0, from->szFaceName, -1, t->szFaceName, sizeof(t->szFaceName), NULL, NULL); 108 /* copy the rest of the 2A structure to 2W */ 109 CopyMemory(&t->wWeight, &from->wWeight, sizeof(CHARFORMAT2W)-FIELD_OFFSET(CHARFORMAT2W,wWeight)); 110 t->cbSize = sizeof(*t); /* it was overwritten by CopyMemory */ 111 return TRUE; 112 } 113 if (to->cbSize == sizeof(CHARFORMAT2W)) 114 { 115 CopyMemory(to, from, sizeof(CHARFORMAT2W)); 116 return TRUE; 117 } 118 return FALSE; 119 } 120 121 ME_Style *ME_MakeStyle(CHARFORMAT2W *style) 122 { 123 ME_Style *s = heap_alloc(sizeof(*s)); 124 125 assert(style->cbSize == sizeof(CHARFORMAT2W)); 126 s->fmt = *style; 127 s->nRefs = 1; 128 s->font_cache = NULL; 129 memset(&s->tm, 0, sizeof(s->tm)); 130 s->tm.tmAscent = -1; 131 s->script_cache = NULL; 132 list_init(&s->entry); 133 all_refs++; 134 TRACE_(richedit_style)("ME_MakeStyle %p, total refs=%d\n", s, all_refs); 135 return s; 136 } 137 138 #define COPY_STYLE_ITEM(mask, member) \ 139 if (mod->dwMask & mask) { \ 140 fmt.dwMask |= mask;\ 141 fmt.member = mod->member;\ 142 } 143 144 #define COPY_STYLE_ITEM_MEMCPY(mask, member) \ 145 if (mod->dwMask & mask) { \ 146 fmt.dwMask |= mask;\ 147 CopyMemory(fmt.member, mod->member, sizeof(mod->member));\ 148 } 149 150 void ME_InitCharFormat2W(CHARFORMAT2W *pFmt) 151 { 152 ZeroMemory(pFmt, sizeof(CHARFORMAT2W)); 153 pFmt->cbSize = sizeof(CHARFORMAT2W); 154 } 155 156 ME_Style *ME_ApplyStyle(ME_TextEditor *editor, ME_Style *sSrc, CHARFORMAT2W *mod) 157 { 158 CHARFORMAT2W fmt = sSrc->fmt; 159 ME_Style *s; 160 161 assert(mod->cbSize == sizeof(CHARFORMAT2W)); 162 COPY_STYLE_ITEM(CFM_ANIMATION, bAnimation); 163 COPY_STYLE_ITEM(CFM_BACKCOLOR, crBackColor); 164 COPY_STYLE_ITEM(CFM_CHARSET, bCharSet); 165 COPY_STYLE_ITEM(CFM_COLOR, crTextColor); 166 COPY_STYLE_ITEM_MEMCPY(CFM_FACE, szFaceName); 167 COPY_STYLE_ITEM(CFM_KERNING, wKerning); 168 COPY_STYLE_ITEM(CFM_LCID, lcid); 169 COPY_STYLE_ITEM(CFM_OFFSET, yOffset); 170 COPY_STYLE_ITEM(CFM_REVAUTHOR, bRevAuthor); 171 if (mod->dwMask & CFM_SIZE) { 172 fmt.dwMask |= CFM_SIZE; 173 fmt.yHeight = min(mod->yHeight, yHeightCharPtsMost * 20); 174 } 175 COPY_STYLE_ITEM(CFM_SPACING, sSpacing); 176 COPY_STYLE_ITEM(CFM_STYLE, sStyle); 177 COPY_STYLE_ITEM(CFM_WEIGHT, wWeight); 178 /* FIXME: this is not documented this way, but that's the more logical */ 179 COPY_STYLE_ITEM(CFM_FACE, bPitchAndFamily); 180 181 fmt.dwEffects &= ~(mod->dwMask); 182 fmt.dwEffects |= mod->dwEffects & mod->dwMask; 183 fmt.dwMask |= mod->dwMask; 184 if (mod->dwMask & CFM_COLOR) 185 { 186 if (mod->dwEffects & CFE_AUTOCOLOR) 187 fmt.dwEffects |= CFE_AUTOCOLOR; 188 else 189 fmt.dwEffects &= ~CFE_AUTOCOLOR; 190 } 191 192 COPY_STYLE_ITEM(CFM_UNDERLINETYPE, bUnderlineType); 193 /* If the CFM_UNDERLINE effect is not specified, set it appropriately */ 194 if ((mod->dwMask & CFM_UNDERLINETYPE) && !(mod->dwMask & CFM_UNDERLINE)) 195 { 196 fmt.dwMask |= CFM_UNDERLINE; 197 if (mod->bUnderlineType == CFU_UNDERLINENONE) 198 fmt.dwEffects &= ~CFE_UNDERLINE; 199 else 200 fmt.dwEffects |= CFE_UNDERLINE; 201 } 202 203 if (mod->dwMask & CFM_BOLD && !(mod->dwMask & CFM_WEIGHT)) 204 { 205 fmt.wWeight = (mod->dwEffects & CFE_BOLD) ? FW_BOLD : FW_NORMAL; 206 } else if (mod->dwMask & CFM_WEIGHT && !(mod->dwMask & CFM_BOLD)) { 207 if (mod->wWeight > FW_NORMAL) 208 fmt.dwEffects |= CFE_BOLD; 209 else 210 fmt.dwEffects &= ~CFE_BOLD; 211 } 212 213 LIST_FOR_EACH_ENTRY(s, &editor->style_list, ME_Style, entry) 214 { 215 if (!memcmp( &s->fmt, &fmt, sizeof(fmt) )) 216 { 217 TRACE_(richedit_style)("found existing style %p\n", s); 218 ME_AddRefStyle( s ); 219 return s; 220 } 221 } 222 223 s = ME_MakeStyle( &fmt ); 224 if (s) 225 list_add_head( &editor->style_list, &s->entry ); 226 TRACE_(richedit_style)("created new style %p\n", s); 227 return s; 228 } 229 230 void ME_CopyCharFormat(CHARFORMAT2W *pDest, const CHARFORMAT2W *pSrc) 231 { 232 /* using this with non-2W structs is forbidden */ 233 assert(pSrc->cbSize == sizeof(CHARFORMAT2W)); 234 assert(pDest->cbSize == sizeof(CHARFORMAT2W)); 235 *pDest = *pSrc; 236 } 237 238 static void ME_DumpStyleEffect(char **p, const char *name, const CHARFORMAT2W *fmt, int mask) 239 { 240 *p += sprintf(*p, "%-22s%s\n", name, (fmt->dwMask & mask) ? ((fmt->dwEffects & mask) ? "YES" : "no") : "N/A"); 241 } 242 243 void ME_DumpStyle(ME_Style *s) 244 { 245 char buf[2048]; 246 ME_DumpStyleToBuf(&s->fmt, buf); 247 TRACE_(richedit_style)("%s\n", buf); 248 } 249 250 void ME_DumpStyleToBuf(CHARFORMAT2W *pFmt, char buf[2048]) 251 { 252 /* FIXME only CHARFORMAT styles implemented */ 253 /* this function sucks, doesn't check for buffer overruns but it's "good enough" as for debug code */ 254 char *p; 255 p = buf; 256 p += sprintf(p, "Font face: "); 257 if (pFmt->dwMask & CFM_FACE) { 258 WCHAR *q = pFmt->szFaceName; 259 while(*q) { 260 *p++ = (*q > 255) ? '?' : *q; 261 q++; 262 } 263 } else 264 p += sprintf(p, "N/A"); 265 266 if (pFmt->dwMask & CFM_SIZE) 267 p += sprintf(p, "\nFont size: %d\n", pFmt->yHeight); 268 else 269 p += sprintf(p, "\nFont size: N/A\n"); 270 271 if (pFmt->dwMask & CFM_OFFSET) 272 p += sprintf(p, "Char offset: %d\n", pFmt->yOffset); 273 else 274 p += sprintf(p, "Char offset: N/A\n"); 275 276 if (pFmt->dwMask & CFM_CHARSET) 277 p += sprintf(p, "Font charset: %d\n", (int)pFmt->bCharSet); 278 else 279 p += sprintf(p, "Font charset: N/A\n"); 280 281 /* I'm assuming CFM_xxx and CFE_xxx are the same values, fortunately it IS true wrt used flags*/ 282 ME_DumpStyleEffect(&p, "Font bold:", pFmt, CFM_BOLD); 283 ME_DumpStyleEffect(&p, "Font italic:", pFmt, CFM_ITALIC); 284 ME_DumpStyleEffect(&p, "Font underline:", pFmt, CFM_UNDERLINE); 285 ME_DumpStyleEffect(&p, "Font strikeout:", pFmt, CFM_STRIKEOUT); 286 ME_DumpStyleEffect(&p, "Hidden text:", pFmt, CFM_HIDDEN); 287 p += sprintf(p, "Text color: "); 288 if (pFmt->dwMask & CFM_COLOR) 289 { 290 if (pFmt->dwEffects & CFE_AUTOCOLOR) 291 p += sprintf(p, "auto\n"); 292 else 293 p += sprintf(p, "%06x\n", (int)pFmt->crTextColor); 294 } 295 else 296 p += sprintf(p, "N/A\n"); 297 ME_DumpStyleEffect(&p, "Text protected:", pFmt, CFM_PROTECTED); 298 } 299 300 301 static void 302 ME_LogFontFromStyle(ME_Context* c, LOGFONTW *lf, const ME_Style *s) 303 { 304 ZeroMemory(lf, sizeof(LOGFONTW)); 305 lstrcpyW(lf->lfFaceName, s->fmt.szFaceName); 306 307 lf->lfHeight = ME_twips2pointsY(c, -s->fmt.yHeight); 308 309 lf->lfWeight = FW_NORMAL; 310 if (s->fmt.dwEffects & s->fmt.dwMask & CFM_BOLD) 311 lf->lfWeight = FW_BOLD; 312 if (s->fmt.dwMask & CFM_WEIGHT) 313 lf->lfWeight = s->fmt.wWeight; 314 if (s->fmt.dwEffects & s->fmt.dwMask & CFM_ITALIC) 315 lf->lfItalic = 1; 316 if ((s->fmt.dwEffects & s->fmt.dwMask & CFM_UNDERLINE) && 317 !(s->fmt.dwEffects & CFE_LINK) && 318 s->fmt.bUnderlineType == CFU_CF1UNDERLINE) 319 lf->lfUnderline = 1; 320 if (s->fmt.dwEffects & s->fmt.dwMask & CFM_STRIKEOUT) 321 lf->lfStrikeOut = 1; 322 if (s->fmt.dwEffects & s->fmt.dwMask & (CFM_SUBSCRIPT|CFM_SUPERSCRIPT)) 323 lf->lfHeight = (lf->lfHeight*2)/3; 324 /*lf.lfQuality = PROOF_QUALITY; */ 325 if (s->fmt.dwMask & CFM_FACE) 326 lf->lfPitchAndFamily = s->fmt.bPitchAndFamily; 327 if (s->fmt.dwMask & CFM_CHARSET) 328 lf->lfCharSet = s->fmt.bCharSet; 329 } 330 331 void ME_CharFormatFromLogFont(HDC hDC, const LOGFONTW *lf, CHARFORMAT2W *fmt) 332 { 333 int ry; 334 335 ME_InitCharFormat2W(fmt); 336 ry = GetDeviceCaps(hDC, LOGPIXELSY); 337 lstrcpyW(fmt->szFaceName, lf->lfFaceName); 338 fmt->dwEffects = 0; 339 fmt->dwMask = CFM_WEIGHT|CFM_BOLD|CFM_ITALIC|CFM_UNDERLINE|CFM_UNDERLINETYPE|CFM_STRIKEOUT|CFM_SIZE|CFM_FACE|CFM_CHARSET; 340 fmt->wWeight = lf->lfWeight; 341 fmt->yHeight = -lf->lfHeight*1440/ry; 342 if (lf->lfWeight > FW_NORMAL) fmt->dwEffects |= CFM_BOLD; 343 if (lf->lfItalic) fmt->dwEffects |= CFM_ITALIC; 344 if (lf->lfUnderline) fmt->dwEffects |= CFM_UNDERLINE; 345 fmt->bUnderlineType = CFU_UNDERLINE; 346 if (lf->lfStrikeOut) fmt->dwEffects |= CFM_STRIKEOUT; 347 fmt->bPitchAndFamily = lf->lfPitchAndFamily; 348 fmt->bCharSet = lf->lfCharSet; 349 } 350 351 static BOOL ME_IsFontEqual(const LOGFONTW *p1, const LOGFONTW *p2) 352 { 353 if (memcmp(p1, p2, sizeof(LOGFONTW)-sizeof(p1->lfFaceName))) 354 return FALSE; 355 if (wcscmp(p1->lfFaceName, p2->lfFaceName)) 356 return FALSE; 357 return TRUE; 358 } 359 360 static void release_font_cache(ME_FontCacheItem *item) 361 { 362 if (item->nRefs > 0) 363 { 364 item->nRefs--; 365 item->nAge = 0; 366 } 367 } 368 369 void select_style( ME_Context *c, ME_Style *s ) 370 { 371 HFONT old_font; 372 LOGFONTW lf; 373 int i, empty, age = 0x7FFFFFFF; 374 ME_FontCacheItem *item; 375 376 if (c->current_style == s) return; 377 378 if (s) 379 { 380 ME_LogFontFromStyle( c, &lf, s ); 381 382 for (i = 0; i < HFONT_CACHE_SIZE; i++) 383 c->editor->pFontCache[i].nAge++; 384 for (i = 0, empty = -1, age = 0; i < HFONT_CACHE_SIZE; i++) 385 { 386 item = &c->editor->pFontCache[i]; 387 if (!item->nRefs) 388 { 389 if (item->nAge > age) 390 { 391 empty = i; 392 age = item->nAge; 393 } 394 } 395 396 if (item->hFont && ME_IsFontEqual( &item->lfSpecs, &lf )) 397 break; 398 } 399 400 if (i < HFONT_CACHE_SIZE) /* found */ 401 { 402 item = &c->editor->pFontCache[i]; 403 TRACE_(richedit_style)( "font reused %d\n", i ); 404 item->nRefs++; 405 } 406 else 407 { 408 assert(empty != -1); 409 item = &c->editor->pFontCache[empty]; 410 if (item->hFont) 411 { 412 TRACE_(richedit_style)( "font deleted %d\n", empty ); 413 DeleteObject(item->hFont); 414 item->hFont = NULL; 415 } 416 item->hFont = CreateFontIndirectW( &lf ); 417 TRACE_(richedit_style)( "font created %d\n", empty ); 418 item->nRefs = 1; 419 item->lfSpecs = lf; 420 } 421 s->font_cache = item; 422 old_font = SelectObject( c->hDC, item->hFont ); 423 GetTextMetricsW( c->hDC, &s->tm ); 424 if (!c->orig_font) c->orig_font = old_font; 425 } 426 else 427 { 428 SelectObject( c->hDC, c->orig_font ); 429 c->orig_font = NULL; 430 } 431 432 if (c->current_style && c->current_style->font_cache) 433 { 434 release_font_cache( c->current_style->font_cache ); 435 c->current_style->font_cache = NULL; 436 } 437 c->current_style = s; 438 439 return; 440 } 441 442 void ME_DestroyStyle(ME_Style *s) 443 { 444 list_remove( &s->entry ); 445 if (s->font_cache) 446 { 447 release_font_cache( s->font_cache ); 448 s->font_cache = NULL; 449 } 450 ScriptFreeCache( &s->script_cache ); 451 heap_free(s); 452 } 453 454 void ME_AddRefStyle(ME_Style *s) 455 { 456 assert(s->nRefs>0); /* style with 0 references isn't supposed to exist */ 457 s->nRefs++; 458 all_refs++; 459 TRACE_(richedit_style)("ME_AddRefStyle %p, new refs=%d, total refs=%d\n", s, s->nRefs, all_refs); 460 } 461 462 void ME_ReleaseStyle(ME_Style *s) 463 { 464 s->nRefs--; 465 all_refs--; 466 if (s->nRefs==0) 467 TRACE_(richedit_style)("destroy style %p, total refs=%d\n", s, all_refs); 468 else 469 TRACE_(richedit_style)("release style %p, new refs=%d, total refs=%d\n", s, s->nRefs, all_refs); 470 if (!all_refs) TRACE("all style references freed (good!)\n"); 471 assert(s->nRefs>=0); 472 if (!s->nRefs) 473 ME_DestroyStyle(s); 474 } 475 476 ME_Style *ME_GetInsertStyle(ME_TextEditor *editor, int nCursor) 477 { 478 if (ME_IsSelection(editor)) 479 { 480 ME_Cursor *from, *to; 481 482 ME_GetSelection(editor, &from, &to); 483 ME_AddRefStyle(from->pRun->member.run.style); 484 return from->pRun->member.run.style; 485 } 486 if (editor->pBuffer->pCharStyle) { 487 ME_AddRefStyle(editor->pBuffer->pCharStyle); 488 return editor->pBuffer->pCharStyle; 489 } 490 else 491 { 492 ME_Cursor *pCursor = &editor->pCursors[nCursor]; 493 ME_DisplayItem *pRunItem = pCursor->pRun; 494 ME_DisplayItem *pPrevItem = NULL; 495 if (pCursor->nOffset) { 496 ME_Run *pRun = &pRunItem->member.run; 497 ME_AddRefStyle(pRun->style); 498 return pRun->style; 499 } 500 pPrevItem = ME_FindItemBack(pRunItem, diRunOrParagraph); 501 if (pPrevItem->type == diRun) 502 { 503 ME_AddRefStyle(pPrevItem->member.run.style); 504 return pPrevItem->member.run.style; 505 } 506 else 507 { 508 ME_AddRefStyle(pRunItem->member.run.style); 509 return pRunItem->member.run.style; 510 } 511 } 512 } 513 514 void ME_SaveTempStyle(ME_TextEditor *editor, ME_Style *style) 515 { 516 ME_Style *old_style = editor->pBuffer->pCharStyle; 517 518 if (style) ME_AddRefStyle( style ); 519 editor->pBuffer->pCharStyle = style; 520 if (old_style) ME_ReleaseStyle( old_style ); 521 } 522 523 void ME_ClearTempStyle(ME_TextEditor *editor) 524 { 525 if (!editor->pBuffer->pCharStyle) return; 526 ME_ReleaseStyle(editor->pBuffer->pCharStyle); 527 editor->pBuffer->pCharStyle = NULL; 528 } 529 530 /****************************************************************************** 531 * ME_SetDefaultCharFormat 532 * 533 * Applies a style change to the default character style. 534 * 535 * The default style is special in that it is mutable - runs 536 * in the document that have this style should change if the 537 * default style changes. That means we need to fix up this 538 * style manually. 539 */ 540 void ME_SetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *mod) 541 { 542 ME_Style *style, *def = editor->pBuffer->pDefaultStyle; 543 544 assert(mod->cbSize == sizeof(CHARFORMAT2W)); 545 style = ME_ApplyStyle(editor, def, mod); 546 def->fmt = style->fmt; 547 def->tm = style->tm; 548 if (def->font_cache) 549 { 550 release_font_cache( def->font_cache ); 551 def->font_cache = NULL; 552 } 553 ScriptFreeCache( &def->script_cache ); 554 ME_ReleaseStyle( style ); 555 ME_MarkAllForWrapping( editor ); 556 } 557