1 // Scintilla source code edit control
2 /** @file LineMarker.cxx
3  ** Defines the look of a line marker in the margin.
4  **/
5 // Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <cstring>
9 #include <cmath>
10 
11 #include <stdexcept>
12 #include <string>
13 #include <string_view>
14 #include <vector>
15 #include <map>
16 #include <algorithm>
17 #include <memory>
18 
19 #include "Platform.h"
20 
21 #include "Scintilla.h"
22 
23 #include "IntegerRectangle.h"
24 #include "XPM.h"
25 #include "LineMarker.h"
26 
27 using namespace Scintilla;
28 
LineMarker(const LineMarker & other)29 LineMarker::LineMarker(const LineMarker &other) {
30 	// Defined to avoid pxpm and image being blindly copied, not as a complete copy constructor.
31 	markType = other.markType;
32 	fore = other.fore;
33 	back = other.back;
34 	backSelected = other.backSelected;
35 	alpha = other.alpha;
36 	if (other.pxpm)
37 		pxpm = std::make_unique<XPM>(*other.pxpm);
38 	else
39 		pxpm = nullptr;
40 	if (other.image)
41 		image = std::make_unique<RGBAImage>(*other.image);
42 	else
43 		image = nullptr;
44 	customDraw = other.customDraw;
45 }
46 
operator =(const LineMarker & other)47 LineMarker &LineMarker::operator=(const LineMarker &other) {
48 	// Defined to avoid pxpm and image being blindly copied, not as a complete assignment operator.
49 	if (this != &other) {
50 		markType = other.markType;
51 		fore = other.fore;
52 		back = other.back;
53 		backSelected = other.backSelected;
54 		alpha = other.alpha;
55 		if (other.pxpm)
56 			pxpm = std::make_unique<XPM>(*other.pxpm);
57 		else
58 			pxpm = nullptr;
59 		if (other.image)
60 			image = std::make_unique<RGBAImage>(*other.image);
61 		else
62 			image = nullptr;
63 		customDraw = other.customDraw;
64 	}
65 	return *this;
66 }
67 
SetXPM(const char * textForm)68 void LineMarker::SetXPM(const char *textForm) {
69 	pxpm = std::make_unique<XPM>(textForm);
70 	markType = SC_MARK_PIXMAP;
71 }
72 
SetXPM(const char * const * linesForm)73 void LineMarker::SetXPM(const char *const *linesForm) {
74 	pxpm = std::make_unique<XPM>(linesForm);
75 	markType = SC_MARK_PIXMAP;
76 }
77 
SetRGBAImage(Point sizeRGBAImage,float scale,const unsigned char * pixelsRGBAImage)78 void LineMarker::SetRGBAImage(Point sizeRGBAImage, float scale, const unsigned char *pixelsRGBAImage) {
79 	image = std::make_unique<RGBAImage>(static_cast<int>(sizeRGBAImage.x), static_cast<int>(sizeRGBAImage.y), scale, pixelsRGBAImage);
80 	markType = SC_MARK_RGBAIMAGE;
81 }
82 
DrawBox(Surface * surface,int centreX,int centreY,int armSize,ColourDesired fore,ColourDesired back)83 static void DrawBox(Surface *surface, int centreX, int centreY, int armSize, ColourDesired fore, ColourDesired back) {
84 	const PRectangle rc = PRectangle::FromInts(
85 				      centreX - armSize,
86 				      centreY - armSize,
87 				      centreX + armSize + 1,
88 				      centreY + armSize + 1);
89 	surface->RectangleDraw(rc, back, fore);
90 }
91 
DrawCircle(Surface * surface,int centreX,int centreY,int armSize,ColourDesired fore,ColourDesired back)92 static void DrawCircle(Surface *surface, int centreX, int centreY, int armSize, ColourDesired fore, ColourDesired back) {
93 	const PRectangle rcCircle = PRectangle::FromInts(
94 					    centreX - armSize,
95 					    centreY - armSize,
96 					    centreX + armSize + 1,
97 					    centreY + armSize + 1);
98 	surface->Ellipse(rcCircle, back, fore);
99 }
100 
DrawPlus(Surface * surface,int centreX,int centreY,int armSize,ColourDesired fore)101 static void DrawPlus(Surface *surface, int centreX, int centreY, int armSize, ColourDesired fore) {
102 	const PRectangle rcV = PRectangle::FromInts(centreX, centreY - armSize + 2, centreX + 1, centreY + armSize - 2 + 1);
103 	surface->FillRectangle(rcV, fore);
104 	const PRectangle rcH = PRectangle::FromInts(centreX - armSize + 2, centreY, centreX + armSize - 2 + 1, centreY + 1);
105 	surface->FillRectangle(rcH, fore);
106 }
107 
DrawMinus(Surface * surface,int centreX,int centreY,int armSize,ColourDesired fore)108 static void DrawMinus(Surface *surface, int centreX, int centreY, int armSize, ColourDesired fore) {
109 	const PRectangle rcH = PRectangle::FromInts(centreX - armSize + 2, centreY, centreX + armSize - 2 + 1, centreY + 1);
110 	surface->FillRectangle(rcH, fore);
111 }
112 
Draw(Surface * surface,PRectangle & rcWhole,Font & fontForCharacter,FoldPart part,int marginStyle) const113 void LineMarker::Draw(Surface *surface, PRectangle &rcWhole, Font &fontForCharacter, FoldPart part, int marginStyle) const {
114 	if (customDraw) {
115 		customDraw(surface, rcWhole, fontForCharacter, static_cast<int>(part), marginStyle, this);
116 		return;
117 	}
118 
119 	ColourDesired colourHead = back;
120 	ColourDesired colourBody = back;
121 	ColourDesired colourTail = back;
122 
123 	switch (part) {
124 	case FoldPart::head:
125 	case FoldPart::headWithTail:
126 		colourHead = backSelected;
127 		colourTail = backSelected;
128 		break;
129 	case FoldPart::body:
130 		colourHead = backSelected;
131 		colourBody = backSelected;
132 		break;
133 	case FoldPart::tail:
134 		colourBody = backSelected;
135 		colourTail = backSelected;
136 		break;
137 	default:
138 		// FoldPart::undefined
139 		break;
140 	}
141 
142 	if ((markType == SC_MARK_PIXMAP) && (pxpm)) {
143 		pxpm->Draw(surface, rcWhole);
144 		return;
145 	}
146 	if ((markType == SC_MARK_RGBAIMAGE) && (image)) {
147 		// Make rectangle just large enough to fit image centred on centre of rcWhole
148 		PRectangle rcImage;
149 		rcImage.top = ((rcWhole.top + rcWhole.bottom) - image->GetScaledHeight()) / 2;
150 		rcImage.bottom = rcImage.top + image->GetScaledHeight();
151 		rcImage.left = ((rcWhole.left + rcWhole.right) - image->GetScaledWidth()) / 2;
152 		rcImage.right = rcImage.left + image->GetScaledWidth();
153 		surface->DrawRGBAImage(rcImage, image->GetWidth(), image->GetHeight(), image->Pixels());
154 		return;
155 	}
156 
157 	const IntegerRectangle ircWhole(rcWhole);
158 	// Restrict most shapes a bit
159 	const PRectangle rc(rcWhole.left, rcWhole.top + 1, rcWhole.right, rcWhole.bottom - 1);
160 	// Ensure does not go beyond edge
161 	const int minDim = std::min(ircWhole.Width(), ircWhole.Height() - 2) - 1;
162 	int centreX = (ircWhole.right + ircWhole.left) / 2;
163 	const int centreY = (ircWhole.bottom + ircWhole.top) / 2;
164 	const int dimOn2 = minDim / 2;
165 	const int dimOn4 = minDim / 4;
166 	const int blobSize = dimOn2 - 1;
167 	const int armSize = dimOn2 - 2;
168 	if (marginStyle == SC_MARGIN_NUMBER || marginStyle == SC_MARGIN_TEXT || marginStyle == SC_MARGIN_RTEXT) {
169 		// On textual margins move marker to the left to try to avoid overlapping the text
170 		centreX = ircWhole.left + dimOn2 + 1;
171 	}
172 
173 	switch (markType) {
174 	case SC_MARK_ROUNDRECT: {
175 			PRectangle rcRounded = rc;
176 			rcRounded.left = rc.left + 1;
177 			rcRounded.right = rc.right - 1;
178 			surface->RoundedRectangle(rcRounded, fore, back);
179 		}
180 		break;
181 
182 	case SC_MARK_CIRCLE: {
183 			const PRectangle rcCircle = PRectangle::FromInts(
184 							    centreX - dimOn2,
185 							    centreY - dimOn2,
186 							    centreX + dimOn2,
187 							    centreY + dimOn2);
188 			surface->Ellipse(rcCircle, fore, back);
189 		}
190 		break;
191 
192 	case SC_MARK_ARROW: {
193 			Point pts[] = {
194 				Point::FromInts(centreX - dimOn4, centreY - dimOn2),
195 				Point::FromInts(centreX - dimOn4, centreY + dimOn2),
196 				Point::FromInts(centreX + dimOn2 - dimOn4, centreY),
197 			};
198 			surface->Polygon(pts, std::size(pts), fore, back);
199 		}
200 		break;
201 
202 	case SC_MARK_ARROWDOWN: {
203 			Point pts[] = {
204 				Point::FromInts(centreX - dimOn2, centreY - dimOn4),
205 				Point::FromInts(centreX + dimOn2, centreY - dimOn4),
206 				Point::FromInts(centreX, centreY + dimOn2 - dimOn4),
207 			};
208 			surface->Polygon(pts, std::size(pts), fore, back);
209 		}
210 		break;
211 
212 	case SC_MARK_PLUS: {
213 			Point pts[] = {
214 				Point::FromInts(centreX - armSize, centreY - 1),
215 				Point::FromInts(centreX - 1, centreY - 1),
216 				Point::FromInts(centreX - 1, centreY - armSize),
217 				Point::FromInts(centreX + 1, centreY - armSize),
218 				Point::FromInts(centreX + 1, centreY - 1),
219 				Point::FromInts(centreX + armSize, centreY - 1),
220 				Point::FromInts(centreX + armSize, centreY + 1),
221 				Point::FromInts(centreX + 1, centreY + 1),
222 				Point::FromInts(centreX + 1, centreY + armSize),
223 				Point::FromInts(centreX - 1, centreY + armSize),
224 				Point::FromInts(centreX - 1, centreY + 1),
225 				Point::FromInts(centreX - armSize, centreY + 1),
226 			};
227 			surface->Polygon(pts, std::size(pts), fore, back);
228 		}
229 		break;
230 
231 	case SC_MARK_MINUS: {
232 			Point pts[] = {
233 				Point::FromInts(centreX - armSize, centreY - 1),
234 				Point::FromInts(centreX + armSize, centreY - 1),
235 				Point::FromInts(centreX + armSize, centreY + 1),
236 				Point::FromInts(centreX - armSize, centreY + 1),
237 			};
238 			surface->Polygon(pts, std::size(pts), fore, back);
239 		}
240 		break;
241 
242 	case SC_MARK_SMALLRECT: {
243 			PRectangle rcSmall;
244 			rcSmall.left = rc.left + 1;
245 			rcSmall.top = rc.top + 2;
246 			rcSmall.right = rc.right - 1;
247 			rcSmall.bottom = rc.bottom - 2;
248 			surface->RectangleDraw(rcSmall, fore, back);
249 		}
250 		break;
251 
252 	case SC_MARK_EMPTY:
253 	case SC_MARK_BACKGROUND:
254 	case SC_MARK_UNDERLINE:
255 	case SC_MARK_AVAILABLE:
256 		// An invisible marker so don't draw anything
257 		break;
258 
259 	case SC_MARK_VLINE: {
260 			surface->PenColour(colourBody);
261 			surface->MoveTo(centreX, ircWhole.top);
262 			surface->LineTo(centreX, ircWhole.bottom);
263 		}
264 		break;
265 
266 	case SC_MARK_LCORNER: {
267 			surface->PenColour(colourTail);
268 			surface->MoveTo(centreX, ircWhole.top);
269 			surface->LineTo(centreX, centreY);
270 			surface->LineTo(ircWhole.right - 1, centreY);
271 		}
272 		break;
273 
274 	case SC_MARK_TCORNER: {
275 			surface->PenColour(colourTail);
276 			surface->MoveTo(centreX, centreY);
277 			surface->LineTo(ircWhole.right - 1, centreY);
278 
279 			surface->PenColour(colourBody);
280 			surface->MoveTo(centreX, ircWhole.top);
281 			surface->LineTo(centreX, centreY + 1);
282 
283 			surface->PenColour(colourHead);
284 			surface->LineTo(centreX, ircWhole.bottom);
285 		}
286 		break;
287 
288 	case SC_MARK_LCORNERCURVE: {
289 			surface->PenColour(colourTail);
290 			surface->MoveTo(centreX, ircWhole.top);
291 			surface->LineTo(centreX, centreY - 3);
292 			surface->LineTo(centreX + 3, centreY);
293 			surface->LineTo(ircWhole.right - 1, centreY);
294 		}
295 		break;
296 
297 	case SC_MARK_TCORNERCURVE: {
298 			surface->PenColour(colourTail);
299 			surface->MoveTo(centreX, centreY - 3);
300 			surface->LineTo(centreX + 3, centreY);
301 			surface->LineTo(ircWhole.right - 1, centreY);
302 
303 			surface->PenColour(colourBody);
304 			surface->MoveTo(centreX, ircWhole.top);
305 			surface->LineTo(centreX, centreY - 2);
306 
307 			surface->PenColour(colourHead);
308 			surface->LineTo(centreX, ircWhole.bottom);
309 		}
310 		break;
311 
312 	case SC_MARK_BOXPLUS: {
313 			DrawBox(surface, centreX, centreY, blobSize, fore, colourHead);
314 			DrawPlus(surface, centreX, centreY, blobSize, colourTail);
315 		}
316 		break;
317 
318 	case SC_MARK_BOXPLUSCONNECTED: {
319 			if (part == FoldPart::headWithTail)
320 				surface->PenColour(colourTail);
321 			else
322 				surface->PenColour(colourBody);
323 			surface->MoveTo(centreX, centreY + blobSize);
324 			surface->LineTo(centreX, ircWhole.bottom);
325 
326 			surface->PenColour(colourBody);
327 			surface->MoveTo(centreX, ircWhole.top);
328 			surface->LineTo(centreX, centreY - blobSize);
329 
330 			DrawBox(surface, centreX, centreY, blobSize, fore, colourHead);
331 			DrawPlus(surface, centreX, centreY, blobSize, colourTail);
332 
333 			if (part == FoldPart::body) {
334 				surface->PenColour(colourTail);
335 				surface->MoveTo(centreX + 1, centreY + blobSize);
336 				surface->LineTo(centreX + blobSize + 1, centreY + blobSize);
337 
338 				surface->MoveTo(centreX + blobSize, centreY + blobSize);
339 				surface->LineTo(centreX + blobSize, centreY - blobSize);
340 
341 				surface->MoveTo(centreX + 1, centreY - blobSize);
342 				surface->LineTo(centreX + blobSize + 1, centreY - blobSize);
343 			}
344 		}
345 		break;
346 
347 	case SC_MARK_BOXMINUS: {
348 			DrawBox(surface, centreX, centreY, blobSize, fore, colourHead);
349 			DrawMinus(surface, centreX, centreY, blobSize, colourTail);
350 
351 			surface->PenColour(colourHead);
352 			surface->MoveTo(centreX, centreY + blobSize);
353 			surface->LineTo(centreX, ircWhole.bottom);
354 		}
355 		break;
356 
357 	case SC_MARK_BOXMINUSCONNECTED: {
358 			DrawBox(surface, centreX, centreY, blobSize, fore, colourHead);
359 			DrawMinus(surface, centreX, centreY, blobSize, colourTail);
360 
361 			surface->PenColour(colourHead);
362 			surface->MoveTo(centreX, centreY + blobSize);
363 			surface->LineTo(centreX, ircWhole.bottom);
364 
365 			surface->PenColour(colourBody);
366 			surface->MoveTo(centreX, ircWhole.top);
367 			surface->LineTo(centreX, centreY - blobSize);
368 
369 			if (part == FoldPart::body) {
370 				surface->PenColour(colourTail);
371 				surface->MoveTo(centreX + 1, centreY + blobSize);
372 				surface->LineTo(centreX + blobSize + 1, centreY + blobSize);
373 
374 				surface->MoveTo(centreX + blobSize, centreY + blobSize);
375 				surface->LineTo(centreX + blobSize, centreY - blobSize);
376 
377 				surface->MoveTo(centreX + 1, centreY - blobSize);
378 				surface->LineTo(centreX + blobSize + 1, centreY - blobSize);
379 			}
380 		}
381 		break;
382 
383 	case SC_MARK_CIRCLEPLUS: {
384 			DrawCircle(surface, centreX, centreY, blobSize, fore, colourHead);
385 			DrawPlus(surface, centreX, centreY, blobSize, colourTail);
386 		}
387 		break;
388 
389 	case SC_MARK_CIRCLEPLUSCONNECTED: {
390 			if (part == FoldPart::headWithTail)
391 				surface->PenColour(colourTail);
392 			else
393 				surface->PenColour(colourBody);
394 			surface->MoveTo(centreX, centreY + blobSize);
395 			surface->LineTo(centreX, ircWhole.bottom);
396 
397 			surface->PenColour(colourBody);
398 			surface->MoveTo(centreX, ircWhole.top);
399 			surface->LineTo(centreX, centreY - blobSize);
400 
401 			DrawCircle(surface, centreX, centreY, blobSize, fore, colourHead);
402 			DrawPlus(surface, centreX, centreY, blobSize, colourTail);
403 		}
404 		break;
405 
406 	case SC_MARK_CIRCLEMINUS: {
407 			surface->PenColour(colourHead);
408 			surface->MoveTo(centreX, centreY + blobSize);
409 			surface->LineTo(centreX, ircWhole.bottom);
410 
411 			DrawCircle(surface, centreX, centreY, blobSize, fore, colourHead);
412 			DrawMinus(surface, centreX, centreY, blobSize, colourTail);
413 		}
414 		break;
415 
416 	case SC_MARK_CIRCLEMINUSCONNECTED: {
417 			surface->PenColour(colourHead);
418 			surface->MoveTo(centreX, centreY + blobSize);
419 			surface->LineTo(centreX, ircWhole.bottom);
420 
421 			surface->PenColour(colourBody);
422 			surface->MoveTo(centreX, ircWhole.top);
423 			surface->LineTo(centreX, centreY - blobSize);
424 
425 			DrawCircle(surface, centreX, centreY, blobSize, fore, colourHead);
426 			DrawMinus(surface, centreX, centreY, blobSize, colourTail);
427 		}
428 		break;
429 
430 	case SC_MARK_DOTDOTDOT: {
431 			XYPOSITION right = static_cast<XYPOSITION>(centreX - 6);
432 			for (int b = 0; b < 3; b++) {
433 				const PRectangle rcBlob(right, rc.bottom - 4, right + 2, rc.bottom - 2);
434 				surface->FillRectangle(rcBlob, fore);
435 				right += 5.0f;
436 			}
437 		}
438 		break;
439 
440 	case SC_MARK_ARROWS: {
441 			surface->PenColour(fore);
442 			int right = centreX - 2;
443 			const int armLength = dimOn2 - 1;
444 			for (int b = 0; b < 3; b++) {
445 				surface->MoveTo(right, centreY);
446 				surface->LineTo(right - armLength, centreY - armLength);
447 				surface->MoveTo(right, centreY);
448 				surface->LineTo(right - armLength, centreY + armLength);
449 				right += 4;
450 			}
451 		}
452 		break;
453 
454 	case SC_MARK_SHORTARROW: {
455 			Point pts[] = {
456 				Point::FromInts(centreX, centreY + dimOn2),
457 				Point::FromInts(centreX + dimOn2, centreY),
458 				Point::FromInts(centreX, centreY - dimOn2),
459 				Point::FromInts(centreX, centreY - dimOn4),
460 				Point::FromInts(centreX - dimOn4, centreY - dimOn4),
461 				Point::FromInts(centreX - dimOn4, centreY + dimOn4),
462 				Point::FromInts(centreX, centreY + dimOn4),
463 				Point::FromInts(centreX, centreY + dimOn2),
464 			};
465 			surface->Polygon(pts, std::size(pts), fore, back);
466 		}
467 		break;
468 
469 	case SC_MARK_FULLRECT:
470 		surface->FillRectangle(rcWhole, back);
471 		break;
472 
473 	case SC_MARK_LEFTRECT: {
474 			PRectangle rcLeft = rcWhole;
475 			rcLeft.right = rcLeft.left + 4;
476 			surface->FillRectangle(rcLeft, back);
477 		}
478 		break;
479 
480 	case SC_MARK_BOOKMARK: {
481 			const int halfHeight = minDim / 3;
482 			Point pts[] = {
483 				Point::FromInts(ircWhole.left, centreY - halfHeight),
484 				Point::FromInts(ircWhole.right - 3, centreY - halfHeight),
485 				Point::FromInts(ircWhole.right - 3 - halfHeight, centreY),
486 				Point::FromInts(ircWhole.right - 3, centreY + halfHeight),
487 				Point::FromInts(ircWhole.left, centreY + halfHeight),
488 			};
489 			surface->Polygon(pts, std::size(pts), fore, back);
490 		}
491 		break;
492 
493 	case SC_MARK_VERTICALBOOKMARK: {
494 			const int halfWidth = minDim / 3;
495 			Point pts[] = {
496 				Point::FromInts(centreX - halfWidth, centreY - dimOn2),
497 				Point::FromInts(centreX + halfWidth, centreY - dimOn2),
498 				Point::FromInts(centreX + halfWidth, centreY + dimOn2),
499 				Point::FromInts(centreX, centreY + dimOn2 - halfWidth),
500 				Point::FromInts(centreX - halfWidth, centreY + dimOn2),
501 			};
502 			surface->Polygon(pts, std::size(pts), fore, back);
503 		}
504 		break;
505 
506 	default:
507 		if (markType >= SC_MARK_CHARACTER) {
508 			std::string character(1, static_cast<char>(markType - SC_MARK_CHARACTER));
509 			const XYPOSITION width = surface->WidthText(fontForCharacter, character);
510 			PRectangle rcText = rc;
511 			rcText.left += (rc.Width() - width) / 2;
512 			rcText.right = rc.left + width;
513 			surface->DrawTextClipped(rcText, fontForCharacter, rcText.bottom - 2,
514 						 character, fore, back);
515 		} else {
516 			// treat as SC_MARK_FULLRECT
517 			surface->FillRectangle(rcWhole, back);
518 		}
519 		break;
520 	}
521 }
522