diff options
Diffstat (limited to 'source/blender/editors/curve/editfont.c')
-rw-r--r-- | source/blender/editors/curve/editfont.c | 1625 |
1 files changed, 1625 insertions, 0 deletions
diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c new file mode 100644 index 00000000000..1b2c8ea6b11 --- /dev/null +++ b/source/blender/editors/curve/editfont.c @@ -0,0 +1,1625 @@ +/** + * $Id$ + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <wchar.h> + +#ifndef WIN32 +#include <unistd.h> +#else +#include <io.h> +#endif + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_arithb.h" + +#include "DNA_curve_types.h" +#include "DNA_object_types.h" +#include "DNA_vfont_types.h" +#include "DNA_scene_types.h" +#include "DNA_text_types.h" +#include "DNA_view3d_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_depsgraph.h" +#include "BKE_font.h" +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_object.h" +#include "BKE_report.h" +#include "BKE_utildefines.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_curve.h" +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_util.h" + +#include "curve_intern.h" + +#define MAXTEXT 32766 + +/************************* utilities ******************************/ + +static char findaccent(char char1, unsigned int code) +{ + char new= 0; + + if(char1=='a') { + if(code=='`') new= 224; + else if(code==39) new= 225; + else if(code=='^') new= 226; + else if(code=='~') new= 227; + else if(code=='"') new= 228; + else if(code=='o') new= 229; + else if(code=='e') new= 230; + else if(code=='-') new= 170; + } + else if(char1=='c') { + if(code==',') new= 231; + if(code=='|') new= 162; + } + else if(char1=='e') { + if(code=='`') new= 232; + else if(code==39) new= 233; + else if(code=='^') new= 234; + else if(code=='"') new= 235; + } + else if(char1=='i') { + if(code=='`') new= 236; + else if(code==39) new= 237; + else if(code=='^') new= 238; + else if(code=='"') new= 239; + } + else if(char1=='n') { + if(code=='~') new= 241; + } + else if(char1=='o') { + if(code=='`') new= 242; + else if(code==39) new= 243; + else if(code=='^') new= 244; + else if(code=='~') new= 245; + else if(code=='"') new= 246; + else if(code=='/') new= 248; + else if(code=='-') new= 186; + else if(code=='e') new= 143; + } + else if(char1=='s') { + if(code=='s') new= 167; + } + else if(char1=='u') { + if(code=='`') new= 249; + else if(code==39) new= 250; + else if(code=='^') new= 251; + else if(code=='"') new= 252; + } + else if(char1=='y') { + if(code==39) new= 253; + else if(code=='"') new= 255; + } + else if(char1=='A') { + if(code=='`') new= 192; + else if(code==39) new= 193; + else if(code=='^') new= 194; + else if(code=='~') new= 195; + else if(code=='"') new= 196; + else if(code=='o') new= 197; + else if(code=='e') new= 198; + } + else if(char1=='C') { + if(code==',') new= 199; + } + else if(char1=='E') { + if(code=='`') new= 200; + else if(code==39) new= 201; + else if(code=='^') new= 202; + else if(code=='"') new= 203; + } + else if(char1=='I') { + if(code=='`') new= 204; + else if(code==39) new= 205; + else if(code=='^') new= 206; + else if(code=='"') new= 207; + } + else if(char1=='N') { + if(code=='~') new= 209; + } + else if(char1=='O') { + if(code=='`') new= 210; + else if(code==39) new= 211; + else if(code=='^') new= 212; + else if(code=='~') new= 213; + else if(code=='"') new= 214; + else if(code=='/') new= 216; + else if(code=='e') new= 141; + } + else if(char1=='U') { + if(code=='`') new= 217; + else if(code==39) new= 218; + else if(code=='^') new= 219; + else if(code=='"') new= 220; + } + else if(char1=='Y') { + if(code==39) new= 221; + } + else if(char1=='1') { + if(code=='4') new= 188; + if(code=='2') new= 189; + } + else if(char1=='3') { + if(code=='4') new= 190; + } + else if(char1==':') { + if(code=='-') new= 247; + } + else if(char1=='-') { + if(code==':') new= 247; + if(code=='|') new= 135; + if(code=='+') new= 177; + } + else if(char1=='|') { + if(code=='-') new= 135; + if(code=='=') new= 136; + } + else if(char1=='=') { + if(code=='|') new= 136; + } + else if(char1=='+') { + if(code=='-') new= 177; + } + + if(new) return new; + else return char1; +} + + +void update_string(Curve *cu) +{ + EditFont *ef= cu->editfont; + int len; + + // Free the old curve string + MEM_freeN(cu->str); + + // Calculate the actual string length in UTF-8 variable characters + len = wcsleninu8(ef->textbuf); + + // Alloc memory for UTF-8 variable char length string + cu->str = MEM_callocN(len + sizeof(wchar_t), "str"); + + // Copy the wchar to UTF-8 + wcs2utf8s(cu->str, ef->textbuf); +} + +static int insert_into_textbuf(Object *obedit, uintptr_t c) +{ + Curve *cu= obedit->data; + + if(cu->len<MAXTEXT-1) { + EditFont *ef= cu->editfont; + int x; + + for(x= cu->len; x>cu->pos; x--) ef->textbuf[x]= ef->textbuf[x-1]; + for(x= cu->len; x>cu->pos; x--) ef->textbufinfo[x]= ef->textbufinfo[x-1]; + ef->textbuf[cu->pos]= c; + ef->textbufinfo[cu->pos] = cu->curinfo; + ef->textbufinfo[cu->pos].kern = 0; + if(obedit->actcol>0) + ef->textbufinfo[cu->pos].mat_nr = obedit->actcol; + else + ef->textbufinfo[cu->pos].mat_nr = 0; + + cu->pos++; + cu->len++; + ef->textbuf[cu->len]='\0'; + + update_string(cu); + + return 1; + } + else + return 0; +} + +static void text_update_edited(bContext *C, Scene *scene, Object *obedit, int recalc, int mode) +{ + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + + if(cu->pos) + cu->curinfo = ef->textbufinfo[cu->pos-1]; + else + cu->curinfo = ef->textbufinfo[0]; + + if(obedit->totcol>0) + obedit->actcol= ef->textbufinfo[cu->pos-1].mat_nr; + + update_string(cu); + BKE_text_to_curve(scene, obedit, mode); + + if(recalc) + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); +} + +/********************** insert lorem operator *********************/ + +static int insert_lorem_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + char *p, *p2; + int i; + static char *lastlorem; + + if(lastlorem) + p= lastlorem; + else + p= ED_lorem; + + i= rand()/(RAND_MAX/6)+4; + + for(p2=p; *p2 && i; p2++) { + insert_into_textbuf(obedit, *p2); + + if(*p2=='.') + i--; + } + + lastlorem = p2+1; + if(strlen(lastlorem)<5) + lastlorem = ED_lorem; + + insert_into_textbuf(obedit, '\n'); + insert_into_textbuf(obedit, '\n'); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + return OPERATOR_FINISHED; +} + +void FONT_OT_insert_lorem(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Insert Lorem"; + ot->idname= "FONT_OT_insert_lorem"; + + /* api callbacks */ + ot->exec= insert_lorem_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; +} + +/******************* paste file operator ********************/ + +/* note this handles both ascii and utf8 unicode, previously + * there were 3 functions that did effectively the same thing. */ + +static int paste_file(bContext *C, ReportList *reports, char *filename) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + FILE *fp; + int filelen; + char *strp; + + fp= fopen(filename, "r"); + + if(!fp) { + if(reports) + BKE_reportf(reports, RPT_ERROR, "Failed to open file %s.", filename); + return OPERATOR_CANCELLED; + } + + fseek(fp, 0L, SEEK_END); + filelen = ftell(fp); + fseek(fp, 0L, SEEK_SET); + + strp= MEM_callocN(filelen+4, "tempstr"); + + // fread() instead of read(), because windows read() converts text + // to DOS \r\n linebreaks, causing double linebreaks in the 3d text + filelen = fread(strp, 1, filelen, fp); + fclose(fp); + strp[filelen]= 0; + + if(cu->len+filelen<MAXTEXT) { + int tmplen; + wchar_t *mem = MEM_callocN((sizeof(wchar_t)*filelen)+(4*sizeof(wchar_t)), "temporary"); + tmplen = utf8towchar(mem, strp); + wcscat(ef->textbuf, mem); + MEM_freeN(mem); + cu->len += tmplen; + cu->pos= cu->len; + } + MEM_freeN(strp); + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +static int paste_file_exec(bContext *C, wmOperator *op) +{ + char *filename; + int retval; + + filename= RNA_string_get_alloc(op->ptr, "filename", NULL, 0); + retval= paste_file(C, op->reports, filename); + MEM_freeN(filename); + + return retval; +} + +static int paste_file_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + if(RNA_property_is_set(op->ptr, "filename")) + return paste_file_exec(C, op); + + WM_event_add_fileselect(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +void FONT_OT_file_paste(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Paste File"; + ot->idname= "FONT_OT_file_paste"; + + /* api callbacks */ + ot->exec= paste_file_exec; + ot->invoke= paste_file_invoke; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_string_file_path(ot->srna, "filename", "", 0, "Filename", "File path of text file to load."); +} + +/******************* paste buffer operator ********************/ + +static int paste_buffer_exec(bContext *C, wmOperator *op) +{ + char *filename; + +#ifdef WIN32 + filename= "C:\\windows\\temp\\cutbuf.txt"; + +// The following is more likely to work on all Win32 installations. +// suggested by Douglas Toltzman. Needs windows include files... +/* + char tempFileName[MAX_PATH]; + DWORD pathlen; + static const char cutbufname[]="cutbuf.txt"; + + if((pathlen=GetTempPath(sizeof(tempFileName),tempFileName)) > 0 && + pathlen + sizeof(cutbufname) <= sizeof(tempFileName)) + { + strcat(tempFileName,cutbufname); + filename= tempFilename; + } +*/ +#else + filename= "/tmp/.cutbuffer"; +#endif + + return paste_file(C, NULL, filename); +} + +void FONT_OT_buffer_paste(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Paste Buffer"; + ot->idname= "FONT_OT_buffer_paste"; + + /* api callbacks */ + ot->exec= paste_buffer_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; +} + +/******************* text to object operator ********************/ + +static void txt_add_object(bContext *C, TextLine *firstline, int totline, float offset[3]) +{ + Scene *scene= CTX_data_scene(C); + Curve *cu; + Object *obedit; + Base *base; + struct TextLine *tmp; + int nchars = 0, a; + + obedit= add_object(scene, OB_FONT); + base= scene->basact; + + ED_object_base_init_from_view(C, base); + where_is_object(scene, obedit); + + obedit->loc[0] += offset[0]; + obedit->loc[1] += offset[1]; + obedit->loc[2] += offset[2]; + + cu= obedit->data; + cu->vfont= get_builtin_font(); + cu->vfont->id.us++; + + for(tmp=firstline, a=0; cu->len<MAXTEXT && a<totline; tmp=tmp->next, a++) + nchars += strlen(tmp->line) + 1; + + if(cu->str) MEM_freeN(cu->str); + if(cu->strinfo) MEM_freeN(cu->strinfo); + + cu->str= MEM_callocN(nchars+4, "str"); + cu->strinfo= MEM_callocN((nchars+4)*sizeof(CharInfo), "strinfo"); + + cu->str[0]= '\0'; + cu->len= 0; + cu->pos= 0; + + for(tmp=firstline, a=0; cu->len<MAXTEXT && a<totline; tmp=tmp->next, a++) { + strcat(cu->str, tmp->line); + cu->len+= strlen(tmp->line); + + if(tmp->next) { + strcat(cu->str, "\n"); + cu->len++; + } + + cu->pos= cu->len; + } + + WM_event_add_notifier(C, NC_OBJECT|NA_ADDED, obedit); +} + +void ED_text_to_object(bContext *C, Text *text, int split_lines) +{ + RegionView3D *rv3d= CTX_wm_region_view3d(C); + TextLine *line; + float offset[3]; + int linenum= 0; + + if(!text || !text->lines.first) return; + + if(split_lines) { + for(line=text->lines.first; line; line=line->next) { + /* skip lines with no text, but still make space for them */ + if(line->line[0] == '\0') { + linenum++; + continue; + } + + /* do the translation */ + offset[0] = 0; + offset[1] = -linenum; + offset[2] = 0; + + if(rv3d) + Mat4Mul3Vecfl(rv3d->viewinv, offset); + + txt_add_object(C, line, 1, offset); + + linenum++; + } + } + else { + offset[0]= 0.0f; + offset[1]= 0.0f; + offset[2]= 0.0f; + + txt_add_object(C, text->lines.first, BLI_countlist(&text->lines), offset); + } +} + +/********************** utilities ***************************/ + +static short next_word(Curve *cu) +{ + short s; + for(s=cu->pos; (cu->str[s]) && (cu->str[s]!=' ') && (cu->str[s]!='\n') && + (cu->str[s]!=1) && (cu->str[s]!='\r'); s++); + if(cu->str[s]) return(s+1); else return(s); +} + +static short prev_word(Curve *cu) +{ + short s; + + if(cu->pos==0) return(0); + for(s=cu->pos-2; (cu->str[s]) && (cu->str[s]!=' ') && (cu->str[s]!='\n') && + (cu->str[s]!=1) && (cu->str[s]!='\r'); s--); + if(cu->str[s]) return(s+1); else return(s); +} + +static int kill_selection(Object *obedit, int ins) /* 1 == new character */ +{ + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int selend, selstart, direction; + int offset = 0; + int getfrom; + + direction = BKE_font_getselection(obedit, &selstart, &selend); + if(direction) { + int size; + if(ins) offset = 1; + if(cu->pos >= selstart) cu->pos = selstart+offset; + if((direction == -1) && ins) { + selstart++; + selend++; + } + getfrom = selend+offset; + if(ins==0) getfrom++; + size = (cu->len * sizeof(wchar_t)) - (selstart * sizeof(wchar_t)) + (offset*sizeof(wchar_t)); + memmove(ef->textbuf+selstart, ef->textbuf+getfrom, size); + memmove(ef->textbufinfo+selstart, ef->textbufinfo+getfrom, ((cu->len-selstart)+offset)*sizeof(CharInfo)); + cu->len -= (selend-selstart)+offset; + cu->selstart = cu->selend = 0; + } + + return(direction); +} + +/******************* set style operator ********************/ + +static EnumPropertyItem style_items[]= { + {CU_BOLD, "BOLD", "Bold", ""}, + {CU_ITALIC, "ITALIC", "Italic", ""}, + {CU_UNDERLINE, "UNDERLINE", "Underline", ""}, + {0, NULL, NULL, NULL}}; + +static int set_style(bContext *C, int style, int clear) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int i, selstart, selend; + + if(!BKE_font_getselection(obedit, &selstart, &selend)) + return OPERATOR_CANCELLED; + + for(i=selstart; i<=selend; i++) { + if(clear) + ef->textbufinfo[i].flag &= ~style; + else + ef->textbufinfo[i].flag |= style; + } + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + return OPERATOR_FINISHED; +} + +static int set_style_exec(bContext *C, wmOperator *op) +{ + int style, clear; + + style= RNA_enum_get(op->ptr, "style"); + clear= RNA_enum_get(op->ptr, "clear"); + + return set_style(C, style, clear); +} + +void FONT_OT_style_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Set Style"; + ot->idname= "FONT_OT_style_set"; + + /* api callbacks */ + ot->exec= set_style_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, "style", style_items, CU_BOLD, "Style", "Style to set selection to."); + RNA_def_boolean(ot->srna, "clear", 0, "Clear", "Clear style rather than setting it."); +} + +/******************* toggle style operator ********************/ + +static int toggle_style_exec(bContext *C, wmOperator *op) +{ + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + int style, clear, selstart, selend; + + if(!BKE_font_getselection(obedit, &selstart, &selend)) + return OPERATOR_CANCELLED; + + style= RNA_enum_get(op->ptr, "style"); + + cu->curinfo.flag ^= style; + clear= (cu->curinfo.flag & style) == 0; + + return set_style(C, style, clear); +} + +void FONT_OT_style_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Toggle Style"; + ot->idname= "FONT_OT_style_toggle"; + + /* api callbacks */ + ot->exec= toggle_style_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, "style", style_items, CU_BOLD, "Style", "Style to set selection to."); +} + +/******************* set material operator ********************/ + +static int set_material_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int i, mat_nr, selstart, selend; + + if(!BKE_font_getselection(obedit, &selstart, &selend)) + return OPERATOR_CANCELLED; + + if(RNA_property_is_set(op->ptr, "index")) + mat_nr= RNA_int_get(op->ptr, "index"); + else + mat_nr= obedit->actcol; + + for(i=selstart; i<=selend; i++) + ef->textbufinfo[i].mat_nr = mat_nr; + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + return OPERATOR_FINISHED; +} + +void FONT_OT_material_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Set Material"; + ot->idname= "FONT_OT_material_set"; + + /* api callbacks */ + ot->exec= set_material_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Material Index", "Material slot index.", 0, INT_MAX); +} + +/******************* copy text operator ********************/ + +static void copy_selection(Object *obedit) +{ + int selstart, selend; + + if(BKE_font_getselection(obedit, &selstart, &selend)) { + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + + memcpy(ef->copybuf, ef->textbuf+selstart, ((selend-selstart)+1)*sizeof(wchar_t)); + ef->copybuf[(selend-selstart)+1]=0; + memcpy(ef->copybufinfo, ef->textbufinfo+selstart, ((selend-selstart)+1)*sizeof(CharInfo)); + } +} + +static int copy_text_exec(bContext *C, wmOperator *op) +{ + Object *obedit= CTX_data_edit_object(C); + + copy_selection(obedit); + + return OPERATOR_FINISHED; +} + +void FONT_OT_text_copy(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Copy Text"; + ot->idname= "FONT_OT_text_copy"; + + /* api callbacks */ + ot->exec= copy_text_exec; + ot->poll= ED_operator_editfont; +} + +/******************* cut text operator ********************/ + +static int cut_text_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + int selstart, selend; + + if(!BKE_font_getselection(obedit, &selstart, &selend)) + return OPERATOR_CANCELLED; + + copy_selection(obedit); + kill_selection(obedit, 0); + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +void FONT_OT_text_cut(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Cut Text"; + ot->idname= "FONT_OT_text_cut"; + + /* api callbacks */ + ot->exec= cut_text_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; +} + +/******************* paste text operator ********************/ + +static int paste_selection(Object *obedit, ReportList *reports) +{ + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int len= wcslen(ef->copybuf); + + // Verify that the copy buffer => [copy buffer len] + cu->len < MAXTEXT + if(cu->len + len <= MAXTEXT) { + if(len) { + int size = (cu->len * sizeof(wchar_t)) - (cu->pos*sizeof(wchar_t)) + sizeof(wchar_t); + memmove(ef->textbuf+cu->pos+len, ef->textbuf+cu->pos, size); + memcpy(ef->textbuf+cu->pos, ef->copybuf, len * sizeof(wchar_t)); + + memmove(ef->textbufinfo+cu->pos+len, ef->textbufinfo+cu->pos, (cu->len-cu->pos+1)*sizeof(CharInfo)); + memcpy(ef->textbufinfo+cu->pos, ef->copybufinfo, len*sizeof(CharInfo)); + + cu->len += len; + cu->pos += len; + + return 1; + } + } + else + BKE_report(reports, RPT_WARNING, "Text too long."); + + return 0; +} + +static int paste_text_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + + if(!paste_selection(obedit, op->reports)) + return OPERATOR_CANCELLED; + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +void FONT_OT_text_paste(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Paste Text"; + ot->idname= "FONT_OT_text_paste"; + + /* api callbacks */ + ot->exec= paste_text_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; +} + +/************************ move operator ************************/ + +static EnumPropertyItem move_type_items[]= { + {LINE_BEGIN, "LINE_BEGIN", "Line Begin", ""}, + {LINE_END, "LINE_END", "Line End", ""}, + {PREV_CHAR, "PREVIOUS_CHARACTER", "Previous Character", ""}, + {NEXT_CHAR, "NEXT_CHARACTER", "Next Character", ""}, + {PREV_WORD, "PREVIOUS_WORD", "Previous Word", ""}, + {NEXT_WORD, "NEXT_WORD", "Next Word", ""}, + {PREV_LINE, "PREVIOUS_LINE", "Previous Line", ""}, + {NEXT_LINE, "NEXT_LINE", "Next Line", ""}, + {PREV_PAGE, "PREVIOUS_PAGE", "Previous Page", ""}, + {NEXT_PAGE, "NEXT_PAGE", "Next Page", ""}, + {0, NULL, NULL, NULL}}; + +static int move_cursor(bContext *C, int type, int select) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int cursmove= 0; + + switch(type) { + case LINE_BEGIN: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + while(cu->pos>0) { + if(ef->textbuf[cu->pos-1]=='\n') break; + if(ef->textbufinfo[cu->pos-1].flag & CU_WRAP ) break; + cu->pos--; + } + cursmove=FO_CURS; + break; + + case LINE_END: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + while(cu->pos<cu->len) { + if(ef->textbuf[cu->pos]==0) break; + if(ef->textbuf[cu->pos]=='\n') break; + if(ef->textbufinfo[cu->pos].flag & CU_WRAP ) break; + cu->pos++; + } + cursmove=FO_CURS; + break; + + case PREV_WORD: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cu->pos= prev_word(cu); + cursmove= FO_CURS; + break; + + case NEXT_WORD: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cu->pos= next_word(cu); + cursmove= FO_CURS; + break; + + case PREV_CHAR: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cu->pos--; + cursmove=FO_CURS; + break; + + case NEXT_CHAR: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cu->pos++; + cursmove= FO_CURS; + + break; + + case PREV_LINE: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cursmove=FO_CURSUP; + break; + + case NEXT_LINE: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cursmove= FO_CURSDOWN; + break; + + case PREV_PAGE: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cursmove=FO_PAGEUP; + break; + + case NEXT_PAGE: + if((select) && (cu->selstart==0)) cu->selstart = cu->selend = cu->pos+1; + cursmove=FO_PAGEDOWN; + break; + } + + if(!cursmove) + return OPERATOR_CANCELLED; + + if(select == 0) { + if(cu->selstart) { + cu->selstart = cu->selend = 0; + update_string(cu); + BKE_text_to_curve(scene, obedit, FO_SELCHANGE); + } + } + + if(cu->pos>cu->len) cu->pos= cu->len; + else if(cu->pos>=MAXTEXT) cu->pos= MAXTEXT; + else if(cu->pos<0) cu->pos= 0; + + text_update_edited(C, scene, obedit, select, cursmove); + + if(select) + cu->selend = cu->pos; + + return OPERATOR_FINISHED; +} + +static int move_exec(bContext *C, wmOperator *op) +{ + int type= RNA_enum_get(op->ptr, "type"); + + return move_cursor(C, type, 0); +} + +void FONT_OT_move(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Move Cursor"; + ot->idname= "FONT_OT_move"; + + /* api callbacks */ + ot->exec= move_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to."); +} + +/******************* move select operator ********************/ + +static int move_select_exec(bContext *C, wmOperator *op) +{ + int type= RNA_enum_get(op->ptr, "type"); + + return move_cursor(C, type, 1); +} + +void FONT_OT_move_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Move Select"; + ot->idname= "FONT_OT_move_select"; + + /* api callbacks */ + ot->exec= move_select_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to, to make a selection."); +} + +/************************* change spacing **********************/ + +static int change_spacing_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int kern, delta= RNA_int_get(op->ptr, "delta"); + + kern = ef->textbufinfo[cu->pos-1].kern; + kern += delta; + CLAMP(kern, -20, 20); + + if(ef->textbufinfo[cu->pos-1].kern == kern) + return OPERATOR_CANCELLED; + + ef->textbufinfo[cu->pos-1].kern = kern; + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +void FONT_OT_change_spacing(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Change Spacing"; + ot->idname= "FONT_OT_change_spacing"; + + /* api callbacks */ + ot->exec= change_spacing_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "delta", 1, -20, 20, "Delta", "Amount to decrease or increasing character spacing with.", -20, 20); +} + +/************************* change character **********************/ + +static int change_character_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int character, delta= RNA_int_get(op->ptr, "delta"); + + if(cu->pos <= 0) + return OPERATOR_CANCELLED; + + character= ef->textbuf[cu->pos - 1]; + character += delta; + CLAMP(character, 0, 255); + + if(character == ef->textbuf[cu->pos - 1]) + return OPERATOR_CANCELLED; + + ef->textbuf[cu->pos - 1]= character; + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +void FONT_OT_change_character(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Change Character"; + ot->idname= "FONT_OT_change_character"; + + /* api callbacks */ + ot->exec= change_character_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "delta", 1, -255, 255, "Delta", "Number to increase or decrease character code with.", -255, 255); +} + +/******************* line break operator ********************/ + +static int line_break_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int ctrl= RNA_enum_get(op->ptr, "ctrl"); + + if(ctrl) { + insert_into_textbuf(obedit, 1); + if(ef->textbuf[cu->pos]!='\n') + insert_into_textbuf(obedit, '\n'); + } + else + insert_into_textbuf(obedit, '\n'); + + cu->selstart = cu->selend = 0; + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +void FONT_OT_line_break(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Line Break"; + ot->idname= "FONT_OT_line_break"; + + /* api callbacks */ + ot->exec= line_break_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "ctrl", 0, "Ctrl", ""); // XXX what is this? +} + +/******************* delete operator **********************/ + +static EnumPropertyItem delete_type_items[]= { + {DEL_ALL, "ALL", "All", ""}, + {DEL_NEXT_CHAR, "NEXT_CHARACTER", "Next Character", ""}, + {DEL_PREV_CHAR, "PREVIOUS_CHARACTER", "Previous Character", ""}, + {DEL_SELECTION, "SELECTION", "Selection", ""}, + {DEL_NEXT_SEL, "NEXT_OR_SELECTION", "Next or Selection", ""}, + {DEL_PREV_SEL, "PREVIOUS_OR_SELECTION", "Previous or Selection", ""}, + {0, NULL, NULL, NULL}}; + +static int delete_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + int x, selstart, selend, type= RNA_enum_get(op->ptr, "type"); + + if(cu->len == 0) + return OPERATOR_CANCELLED; + + if(BKE_font_getselection(obedit, &selstart, &selend)) { + if(type == DEL_NEXT_SEL) type= DEL_SELECTION; + else if(type == DEL_PREV_SEL) type= DEL_SELECTION; + } + else { + if(type == DEL_NEXT_SEL) type= DEL_NEXT_CHAR; + else if(type == DEL_PREV_SEL) type= DEL_PREV_CHAR; + } + + switch(type) { + case DEL_ALL: + cu->len = cu->pos = 0; + ef->textbuf[0]= 0; + break; + case DEL_SELECTION: + if(!kill_selection(obedit, 0)) + return OPERATOR_CANCELLED; + break; + case DEL_PREV_CHAR: + if(cu->pos<=0) + return OPERATOR_CANCELLED; + + for(x=cu->pos;x<=cu->len;x++) + ef->textbuf[x-1]= ef->textbuf[x]; + for(x=cu->pos;x<=cu->len;x++) + ef->textbufinfo[x-1]= ef->textbufinfo[x]; + + cu->pos--; + ef->textbuf[--cu->len]='\0'; + break; + case DEL_NEXT_CHAR: + if(cu->pos>=cu->len) + return OPERATOR_CANCELLED; + + for(x=cu->pos;x<cu->len;x++) + ef->textbuf[x]= ef->textbuf[x+1]; + for(x=cu->pos;x<cu->len;x++) + ef->textbufinfo[x]= ef->textbufinfo[x+1]; + + ef->textbuf[--cu->len]='\0'; + break; + default: + return OPERATOR_CANCELLED; + } + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +void FONT_OT_delete(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Delete"; + ot->idname= "FONT_OT_delete"; + + /* api callbacks */ + ot->exec= delete_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, "type", delete_type_items, DEL_ALL, "Type", "Which part of the text to delete."); +} + +/*********************** insert text operator *************************/ + +static int insert_text_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + char *inserted_utf8; + wchar_t *inserted_text, first; + int len; + + if(!RNA_property_is_set(op->ptr, "text")) + return OPERATOR_CANCELLED; + + inserted_utf8= RNA_string_get_alloc(op->ptr, "text", NULL, 0); + len= strlen(inserted_utf8); + + inserted_text= MEM_callocN(sizeof(wchar_t)*(len+1), "FONT_insert_text"); + utf8towchar(inserted_text, inserted_utf8); + first= inserted_text[0]; + + MEM_freeN(inserted_text); + MEM_freeN(inserted_utf8); + + if(!first) + return OPERATOR_CANCELLED; + + insert_into_textbuf(obedit, first); + kill_selection(obedit, 1); + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +static int insert_text_invoke(bContext *C, wmOperator *op, wmEvent *evt) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + static int accentcode= 0; + uintptr_t ascii = evt->ascii; + int alt= evt->alt, shift= evt->shift, ctrl= evt->ctrl; + int event= evt->type, val= evt->val; + wchar_t inserted_text[2]= {0}; + + if(RNA_property_is_set(op->ptr, "text")) + return insert_text_exec(C, op); + + /* tab should exit editmode, but we allow it to be typed using modifier keys */ + if(event==TABKEY) { + if((alt||ctrl||shift) == 0) + return OPERATOR_PASS_THROUGH; + else + ascii= 9; + } + else if(event==BACKSPACEKEY) + ascii= 0; + + if(val && ascii) { + /* handle case like TAB (== 9) */ + if((ascii > 31 && ascii < 254 && ascii != 127) || (ascii==13) || (ascii==10) || (ascii==8)) { + if(accentcode) { + if(cu->pos>0) { + inserted_text[0]= findaccent(ef->textbuf[cu->pos-1], ascii); + ef->textbuf[cu->pos-1]= inserted_text[0]; + } + accentcode= 0; + } + else if(cu->len<MAXTEXT-1) { + if(alt) { + /* might become obsolete, apple has default values for this, other OS's too? */ + if(ascii=='t') ascii= 137; + else if(ascii=='c') ascii= 169; + else if(ascii=='f') ascii= 164; + else if(ascii=='g') ascii= 176; + else if(ascii=='l') ascii= 163; + else if(ascii=='r') ascii= 174; + else if(ascii=='s') ascii= 223; + else if(ascii=='y') ascii= 165; + else if(ascii=='.') ascii= 138; + else if(ascii=='1') ascii= 185; + else if(ascii=='2') ascii= 178; + else if(ascii=='3') ascii= 179; + else if(ascii=='%') ascii= 139; + else if(ascii=='?') ascii= 191; + else if(ascii=='!') ascii= 161; + else if(ascii=='x') ascii= 215; + else if(ascii=='>') ascii= 187; + else if(ascii=='<') ascii= 171; + } + + inserted_text[0]= ascii; + insert_into_textbuf(obedit, ascii); + } + + kill_selection(obedit, 1); + text_update_edited(C, scene, obedit, 1, 0); + } + else { + inserted_text[0]= ascii; + insert_into_textbuf(obedit, ascii); + text_update_edited(C, scene, obedit, 1, 0); + } + } + else if(val && event == BACKSPACEKEY) { + if(alt && cu->len!=0 && cu->pos>0) + accentcode= 1; + + return OPERATOR_PASS_THROUGH; + } + else + return OPERATOR_PASS_THROUGH; + + if(inserted_text[0]) { + /* store as utf8 in RNA string */ + char inserted_utf8[8] = {0}; + + wcs2utf8s(inserted_utf8, inserted_text); + RNA_string_set(op->ptr, "text", inserted_utf8); + } + + return OPERATOR_FINISHED; +} + +void FONT_OT_text_insert(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Insert Text"; + ot->idname= "FONT_OT_text_insert"; + + /* api callbacks */ + ot->exec= insert_text_exec; + ot->invoke= insert_text_invoke; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_string(ot->srna, "text", "", 0, "Text", "Text to insert at the cursor position."); +} + +/***************** editmode enter/exit ********************/ + +void make_editText(Object *obedit) +{ + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + + if(ef==NULL) { + ef= cu->editfont= MEM_callocN(sizeof(EditFont), "editfont"); + + ef->textbuf= MEM_callocN((MAXTEXT+4)*sizeof(wchar_t), "texteditbuf"); + ef->textbufinfo= MEM_callocN((MAXTEXT+4)*sizeof(CharInfo), "texteditbufinfo"); + ef->copybuf= MEM_callocN((MAXTEXT+4)*sizeof(wchar_t), "texteditcopybuf"); + ef->copybufinfo= MEM_callocN((MAXTEXT+4)*sizeof(CharInfo), "texteditcopybufinfo"); + ef->oldstr= MEM_callocN((MAXTEXT+4)*sizeof(wchar_t), "oldstrbuf"); + ef->oldstrinfo= MEM_callocN((MAXTEXT+4)*sizeof(wchar_t), "oldstrbuf"); + } + + // Convert the original text to wchar_t + utf8towchar(ef->textbuf, cu->str); + wcscpy(ef->oldstr, ef->textbuf); + + cu->len= wcslen(ef->textbuf); + + memcpy(ef->textbufinfo, cu->strinfo, (cu->len)*sizeof(CharInfo)); + memcpy(ef->oldstrinfo, cu->strinfo, (cu->len)*sizeof(CharInfo)); + + if(cu->pos>cu->len) cu->pos= cu->len; + + if(cu->pos) + cu->curinfo = ef->textbufinfo[cu->pos-1]; + else + cu->curinfo = ef->textbufinfo[0]; + + // Convert to UTF-8 + update_string(cu); +} + +void load_editText(Object *obedit) +{ + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + + MEM_freeN(ef->oldstr); + ef->oldstr= NULL; + MEM_freeN(ef->oldstrinfo); + ef->oldstrinfo= NULL; + + update_string(cu); + + if(cu->strinfo) + MEM_freeN(cu->strinfo); + cu->strinfo= MEM_callocN((cu->len+4)*sizeof(CharInfo), "texteditinfo"); + memcpy(cu->strinfo, ef->textbufinfo, (cu->len)*sizeof(CharInfo)); + + cu->len= strlen(cu->str); + + /* this memory system is weak... */ + + if(cu->selboxes) { + MEM_freeN(cu->selboxes); + cu->selboxes= NULL; + } +} + +void free_editText(Object *obedit) +{ + BKE_free_editfont((Curve *)obedit->data); +} + +/********************** set case operator *********************/ + +static EnumPropertyItem case_items[]= { + {CASE_LOWER, "LOWER", "Lower", ""}, + {CASE_UPPER, "UPPER", "Upper", ""}, + {0, NULL, NULL, NULL}}; + +static int set_case(bContext *C, int ccase) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + wchar_t *str; + int len; + + len= wcslen(ef->textbuf); + str= ef->textbuf; + while(len) { + if(*str>='a' && *str<='z') + *str-= 32; + len--; + str++; + } + + if(ccase == CASE_LOWER) { + len= wcslen(ef->textbuf); + str= ef->textbuf; + while(len) { + if(*str>='A' && *str<='Z') { + *str+= 32; + } + len--; + str++; + } + } + + text_update_edited(C, scene, obedit, 1, 0); + + return OPERATOR_FINISHED; +} + +static int set_case_exec(bContext *C, wmOperator *op) +{ + return set_case(C, RNA_enum_get(op->ptr, "case")); +} + +void FONT_OT_case_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Set Case"; + ot->idname= "FONT_OT_case_set"; + + /* api callbacks */ + ot->exec= set_case_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, "case", case_items, CASE_LOWER, "Case", "Lower or upper case."); +} + +/********************** toggle case operator *********************/ + +static int toggle_case_exec(bContext *C, wmOperator *op) +{ + Object *obedit= CTX_data_edit_object(C); + Curve *cu= obedit->data; + EditFont *ef= cu->editfont; + wchar_t *str; + int len, ccase= CASE_UPPER; + + len= wcslen(ef->textbuf); + str= ef->textbuf; + while(len) { + if(*str>='a' && *str<='z') { + ccase= CASE_LOWER; + break; + } + + len--; + str++; + } + + return set_case(C, ccase); +} + +void FONT_OT_case_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Toggle Case"; + ot->idname= "FONT_OT_case_toggle"; + + /* api callbacks */ + ot->exec= toggle_case_exec; + ot->poll= ED_operator_editfont; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; +} + +/* **************** undo for font object ************** */ + +static void undoFont_to_editFont(void *strv, void *ecu) +{ + Curve *cu= (Curve *)ecu; + EditFont *ef= cu->editfont; + char *str= strv; + + cu->pos= *((short *)str); + cu->len= *((short *)(str+2)); + + memcpy(ef->textbuf, str+4, (cu->len+1)*sizeof(wchar_t)); + memcpy(ef->textbufinfo, str+4 + (cu->len+1)*sizeof(wchar_t), cu->len*sizeof(CharInfo)); + + cu->selstart = cu->selend = 0; + + update_string(cu); +} + +static void *editFont_to_undoFont(void *ecu) +{ + Curve *cu= (Curve *)ecu; + EditFont *ef= cu->editfont; + char *str; + + // The undo buffer includes [MAXTEXT+6]=actual string and [MAXTEXT+4]*sizeof(CharInfo)=charinfo + str= MEM_callocN((MAXTEXT+6)*sizeof(wchar_t) + (MAXTEXT+4)*sizeof(CharInfo), "string undo"); + + // Copy the string and string information + memcpy(str+4, ef->textbuf, (cu->len+1)*sizeof(wchar_t)); + memcpy(str+4 + (cu->len+1)*sizeof(wchar_t), ef->textbufinfo, cu->len*sizeof(CharInfo)); + + *((short *)str)= cu->pos; + *((short *)(str+2))= cu->len; + + return str; +} + +static void free_undoFont(void *strv) +{ + MEM_freeN(strv); +} + +static void *get_undoFont(bContext *C) +{ + Object *obedit= CTX_data_edit_object(C); + if(obedit && obedit->type==OB_FONT) { + return obedit->data; + } + return NULL; +} + +/* and this is all the undo system needs to know */ +void undo_push_font(bContext *C, char *name) +{ + undo_editmode_push(C, name, get_undoFont, free_undoFont, undoFont_to_editFont, editFont_to_undoFont, NULL); +} + |