1 // Scintilla source code edit control
2 /** @file Indicator.cxx
3  ** Defines the style of indicators which are text decorations such as underlining.
4  **/
5 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <cmath>
9 
10 #include <stdexcept>
11 #include <string_view>
12 #include <vector>
13 #include <map>
14 #include <algorithm>
15 #include <memory>
16 
17 #include "Platform.h"
18 
19 #include "Scintilla.h"
20 #include "IntegerRectangle.h"
21 #include "Indicator.h"
22 #include "XPM.h"
23 
24 using namespace Scintilla;
25 
PixelGridAlign(const PRectangle & rc)26 static PRectangle PixelGridAlign(const PRectangle &rc) noexcept {
27 	// Move left and right side to nearest pixel to avoid blurry visuals
28 	return PRectangle(std::round(rc.left), std::floor(rc.top),
29 		std::round(rc.right), std::floor(rc.bottom));
30 }
31 
Draw(Surface * surface,const PRectangle & rc,const PRectangle & rcLine,const PRectangle & rcCharacter,State state,int value) const32 void Indicator::Draw(Surface *surface, const PRectangle &rc, const PRectangle &rcLine, const PRectangle &rcCharacter, State state, int value) const {
33 	StyleAndColour sacDraw = sacNormal;
34 	if (Flags() & SC_INDICFLAG_VALUEFORE) {
35 		sacDraw.fore = ColourDesired(value & SC_INDICVALUEMASK);
36 	}
37 	if (state == State::hover) {
38 		sacDraw = sacHover;
39 	}
40 	const IntegerRectangle irc(rc);
41 	surface->PenColour(sacDraw.fore);
42 	const int ymid = (irc.bottom + irc.top) / 2;
43 
44 	switch (sacDraw.style) {
45 	case INDIC_SQUIGGLE: {
46 			const IntegerRectangle ircSquiggle(PixelGridAlign(rc));
47 			int x = ircSquiggle.left;
48 			const int xLast = ircSquiggle.right;
49 			int y = 0;
50 			surface->MoveTo(x, irc.top + y);
51 			while (x < xLast) {
52 				if ((x + 2) > xLast) {
53 					y = 1;
54 					x = xLast;
55 				} else {
56 					x += 2;
57 					y = 2 - y;
58 				}
59 				surface->LineTo(x, irc.top + y);
60 			}
61 		}
62 		break;
63 
64 	case INDIC_SQUIGGLEPIXMAP: {
65 			const PRectangle rcSquiggle = PixelGridAlign(rc);
66 
67 			const int width = std::min(4000, static_cast<int>(rcSquiggle.Width()));
68 			RGBAImage image(width, 3, 1.0, nullptr);
69 			enum { alphaFull = 0xff, alphaSide = 0x2f, alphaSide2=0x5f };
70 			for (int x = 0; x < width; x++) {
71 				if (x%2) {
72 					// Two halfway columns have a full pixel in middle flanked by light pixels
73 					image.SetPixel(x, 0, sacDraw.fore, alphaSide);
74 					image.SetPixel(x, 1, sacDraw.fore, alphaFull);
75 					image.SetPixel(x, 2, sacDraw.fore, alphaSide);
76 				} else {
77 					// Extreme columns have a full pixel at bottom or top and a mid-tone pixel in centre
78 					image.SetPixel(x, (x % 4) ? 0 : 2, sacDraw.fore, alphaFull);
79 					image.SetPixel(x, 1, sacDraw.fore, alphaSide2);
80 				}
81 			}
82 			surface->DrawRGBAImage(rcSquiggle, image.GetWidth(), image.GetHeight(), image.Pixels());
83 		}
84 		break;
85 
86 	case INDIC_SQUIGGLELOW: {
87 			surface->MoveTo(irc.left, irc.top);
88 			int x = irc.left + 3;
89 			int y = 0;
90 			while (x < rc.right) {
91 				surface->LineTo(x - 1, irc.top + y);
92 				y = 1 - y;
93 				surface->LineTo(x, irc.top + y);
94 				x += 3;
95 			}
96 			surface->LineTo(irc.right, irc.top + y);	// Finish the line
97 		}
98 		break;
99 
100 	case INDIC_TT: {
101 			surface->MoveTo(irc.left, ymid);
102 			int x = irc.left + 5;
103 			while (x < rc.right) {
104 				surface->LineTo(x, ymid);
105 				surface->MoveTo(x-3, ymid);
106 				surface->LineTo(x-3, ymid+2);
107 				x++;
108 				surface->MoveTo(x, ymid);
109 				x += 5;
110 			}
111 			surface->LineTo(irc.right, ymid);	// Finish the line
112 			if (x - 3 <= rc.right) {
113 				surface->MoveTo(x-3, ymid);
114 				surface->LineTo(x-3, ymid+2);
115 			}
116 		}
117 		break;
118 
119 	case INDIC_DIAGONAL: {
120 			int x = irc.left;
121 			while (x < rc.right) {
122 				surface->MoveTo(x, irc.top + 2);
123 				int endX = x+3;
124 				int endY = irc.top - 1;
125 				if (endX > rc.right) {
126 					endY += endX - irc.right;
127 					endX = irc.right;
128 				}
129 				surface->LineTo(endX, endY);
130 				x += 4;
131 			}
132 		}
133 		break;
134 
135 	case INDIC_STRIKE: {
136 			surface->MoveTo(irc.left, irc.top - 4);
137 			surface->LineTo(irc.right, irc.top - 4);
138 		}
139 		break;
140 
141 	case INDIC_HIDDEN:
142 	case INDIC_TEXTFORE:
143 		// Draw nothing
144 		break;
145 
146 	case INDIC_BOX: {
147 			surface->MoveTo(irc.left, ymid + 1);
148 			surface->LineTo(irc.right, ymid + 1);
149 			const int lineTop = static_cast<int>(rcLine.top) + 1;
150 			surface->LineTo(irc.right, lineTop);
151 			surface->LineTo(irc.left, lineTop);
152 			surface->LineTo(irc.left, ymid + 1);
153 		}
154 		break;
155 
156 	case INDIC_ROUNDBOX:
157 	case INDIC_STRAIGHTBOX:
158 	case INDIC_FULLBOX: {
159 			PRectangle rcBox = rcLine;
160 			if (sacDraw.style != INDIC_FULLBOX)
161 				rcBox.top = rcLine.top + 1;
162 			rcBox.left = rc.left;
163 			rcBox.right = rc.right;
164 			surface->AlphaRectangle(rcBox, (sacDraw.style == INDIC_ROUNDBOX) ? 1 : 0,
165 						sacDraw.fore, fillAlpha, sacDraw.fore, outlineAlpha, 0);
166 		}
167 		break;
168 
169 	case INDIC_GRADIENT:
170 	case INDIC_GRADIENTCENTRE: {
171 			PRectangle rcBox = rc;
172 			rcBox.top = rcLine.top + 1;
173 			rcBox.bottom = rcLine.bottom;
174 			const Surface::GradientOptions options = Surface::GradientOptions::topToBottom;
175 			const ColourAlpha start(sacDraw.fore, fillAlpha);
176 			const ColourAlpha end(sacDraw.fore, 0);
177 			std::vector<ColourStop> stops;
178 			switch (sacDraw.style) {
179 			case INDIC_GRADIENT:
180 				stops.push_back(ColourStop(0.0, start));
181 				stops.push_back(ColourStop(1.0, end));
182 				break;
183 			case INDIC_GRADIENTCENTRE:
184 				stops.push_back(ColourStop(0.0, end));
185 				stops.push_back(ColourStop(0.5, start));
186 				stops.push_back(ColourStop(1.0, end));
187 				break;
188 			}
189 			surface->GradientRectangle(rcBox, stops, options);
190 		}
191 		break;
192 
193 	case INDIC_DOTBOX: {
194 			PRectangle rcBox = PixelGridAlign(rc);
195 			rcBox.top = rcLine.top + 1;
196 			rcBox.bottom = rcLine.bottom;
197 			const IntegerRectangle ircBox(rcBox);
198 			// Cap width at 4000 to avoid large allocations when mistakes made
199 			const int width = std::min(ircBox.Width(), 4000);
200 			RGBAImage image(width, ircBox.Height(), 1.0, nullptr);
201 			// Draw horizontal lines top and bottom
202 			for (int x=0; x<width; x++) {
203 				for (int y = 0; y<ircBox.Height(); y += ircBox.Height() - 1) {
204 					image.SetPixel(x, y, sacDraw.fore, ((x + y) % 2) ? outlineAlpha : fillAlpha);
205 				}
206 			}
207 			// Draw vertical lines left and right
208 			for (int y = 1; y<ircBox.Height(); y++) {
209 				for (int x=0; x<width; x += width-1) {
210 					image.SetPixel(x, y, sacDraw.fore, ((x + y) % 2) ? outlineAlpha : fillAlpha);
211 				}
212 			}
213 			surface->DrawRGBAImage(rcBox, image.GetWidth(), image.GetHeight(), image.Pixels());
214 		}
215 		break;
216 
217 	case INDIC_DASH: {
218 			int x = irc.left;
219 			while (x < rc.right) {
220 				surface->MoveTo(x, ymid);
221 				surface->LineTo(std::min(x + 4, irc.right), ymid);
222 				x += 7;
223 			}
224 		}
225 		break;
226 
227 	case INDIC_DOTS: {
228 			int x = irc.left;
229 			while (x < irc.right) {
230 				const PRectangle rcDot = PRectangle::FromInts(x, ymid, x + 1, ymid + 1);
231 				surface->FillRectangle(rcDot, sacDraw.fore);
232 				x += 2;
233 			}
234 		}
235 		break;
236 
237 	case INDIC_COMPOSITIONTHICK: {
238 			const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom);
239 			surface->FillRectangle(rcComposition, sacDraw.fore);
240 		}
241 		break;
242 
243 	case INDIC_COMPOSITIONTHIN: {
244 			const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom-1);
245 			surface->FillRectangle(rcComposition, sacDraw.fore);
246 		}
247 		break;
248 
249 	case INDIC_POINT:
250 	case INDIC_POINTCHARACTER:
251 		if (rcCharacter.Width() >= 0.1) {
252 			const XYPOSITION pixelHeight = std::floor(rc.Height() - 1.0f);	// 1 pixel onto next line if multiphase
253 			const XYPOSITION x = (sacDraw.style == INDIC_POINT) ? (rcCharacter.left) : ((rcCharacter.right + rcCharacter.left) / 2);
254 			const XYPOSITION ix = std::round(x);
255 			const XYPOSITION iy = std::floor(rc.top + 1.0f);
256 			Point pts[] = {
257 				Point(ix - pixelHeight, iy + pixelHeight),	// Left
258 				Point(ix + pixelHeight, iy + pixelHeight),	// Right
259 				Point(ix, iy)								// Top
260 			};
261 			surface->Polygon(pts, std::size(pts), sacDraw.fore, sacDraw.fore);
262 		}
263 		break;
264 
265 	default:
266 		// Either INDIC_PLAIN or unknown
267 		surface->MoveTo(irc.left, ymid);
268 		surface->LineTo(irc.right, ymid);
269 	}
270 }
271 
SetFlags(int attributes_)272 void Indicator::SetFlags(int attributes_) noexcept {
273 	attributes = attributes_;
274 }
275