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 <vector>
12 #include <map>
13 #include <algorithm>
14 #include <memory>
15 
16 #include "Platform.h"
17 
18 #include "Scintilla.h"
19 #include "StringCopy.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) {
27 	// Move left and right side to nearest pixel to avoid blurry visuals
28 	return PRectangle(round(rc.left), floor(rc.top),
29 		round(rc.right), floor(rc.bottom));
30 }
31 
Draw(Surface * surface,const PRectangle & rc,const PRectangle & rcLine,const PRectangle & rcCharacter,DrawState drawState,int value) const32 void Indicator::Draw(Surface *surface, const PRectangle &rc, const PRectangle &rcLine, const PRectangle &rcCharacter, DrawState drawState, int value) const {
33 	StyleAndColour sacDraw = sacNormal;
34 	if (Flags() & SC_INDICFLAG_VALUEFORE) {
35 		sacDraw.fore = ColourDesired(value & SC_INDICVALUEMASK);
36 	}
37 	if (drawState == drawHover) {
38 		sacDraw = sacHover;
39 	}
40 	const IntegerRectangle irc(rc);
41 	surface->PenColour(sacDraw.fore);
42 	const int ymid = (irc.bottom + irc.top) / 2;
43 	if (sacDraw.style == INDIC_SQUIGGLE) {
44 		const IntegerRectangle ircSquiggle(PixelGridAlign(rc));
45 		int x = ircSquiggle.left;
46 		const int xLast = ircSquiggle.right;
47 		int y = 0;
48 		surface->MoveTo(x, irc.top + y);
49 		while (x < xLast) {
50 			if ((x + 2) > xLast) {
51 				y = 1;
52 				x = xLast;
53 			} else {
54 				x += 2;
55 				y = 2 - y;
56 			}
57 			surface->LineTo(x, irc.top + y);
58 		}
59 	} else if (sacDraw.style == INDIC_SQUIGGLEPIXMAP) {
60 		const PRectangle rcSquiggle = PixelGridAlign(rc);
61 
62 		const int width = std::min(4000, static_cast<int>(rcSquiggle.Width()));
63 		RGBAImage image(width, 3, 1.0, nullptr);
64 		enum { alphaFull = 0xff, alphaSide = 0x2f, alphaSide2=0x5f };
65 		for (int x = 0; x < width; x++) {
66 			if (x%2) {
67 				// Two halfway columns have a full pixel in middle flanked by light pixels
68 				image.SetPixel(x, 0, sacDraw.fore, alphaSide);
69 				image.SetPixel(x, 1, sacDraw.fore, alphaFull);
70 				image.SetPixel(x, 2, sacDraw.fore, alphaSide);
71 			} else {
72 				// Extreme columns have a full pixel at bottom or top and a mid-tone pixel in centre
73 				image.SetPixel(x, (x % 4) ? 0 : 2, sacDraw.fore, alphaFull);
74 				image.SetPixel(x, 1, sacDraw.fore, alphaSide2);
75 			}
76 		}
77 		surface->DrawRGBAImage(rcSquiggle, image.GetWidth(), image.GetHeight(), image.Pixels());
78 	} else if (sacDraw.style == INDIC_SQUIGGLELOW) {
79 		surface->MoveTo(irc.left, irc.top);
80 		int x = irc.left + 3;
81 		int y = 0;
82 		while (x < rc.right) {
83 			surface->LineTo(x - 1, irc.top + y);
84 			y = 1 - y;
85 			surface->LineTo(x, irc.top + y);
86 			x += 3;
87 		}
88 		surface->LineTo(irc.right, irc.top + y);	// Finish the line
89 	} else if (sacDraw.style == INDIC_TT) {
90 		surface->MoveTo(irc.left, ymid);
91 		int x = irc.left + 5;
92 		while (x < rc.right) {
93 			surface->LineTo(x, ymid);
94 			surface->MoveTo(x-3, ymid);
95 			surface->LineTo(x-3, ymid+2);
96 			x++;
97 			surface->MoveTo(x, ymid);
98 			x += 5;
99 		}
100 		surface->LineTo(irc.right, ymid);	// Finish the line
101 		if (x - 3 <= rc.right) {
102 			surface->MoveTo(x-3, ymid);
103 			surface->LineTo(x-3, ymid+2);
104 		}
105 	} else if (sacDraw.style == INDIC_DIAGONAL) {
106 		int x = irc.left;
107 		while (x < rc.right) {
108 			surface->MoveTo(x, irc.top + 2);
109 			int endX = x+3;
110 			int endY = irc.top - 1;
111 			if (endX > rc.right) {
112 				endY += endX - irc.right;
113 				endX = irc.right;
114 			}
115 			surface->LineTo(endX, endY);
116 			x += 4;
117 		}
118 	} else if (sacDraw.style == INDIC_STRIKE) {
119 		surface->MoveTo(irc.left, irc.top - 4);
120 		surface->LineTo(irc.right, irc.top - 4);
121 	} else if ((sacDraw.style == INDIC_HIDDEN) || (sacDraw.style == INDIC_TEXTFORE)) {
122 		// Draw nothing
123 	} else if (sacDraw.style == INDIC_BOX) {
124 		surface->MoveTo(irc.left, ymid + 1);
125 		surface->LineTo(irc.right, ymid + 1);
126 		const int lineTop = static_cast<int>(rcLine.top) + 1;
127 		surface->LineTo(irc.right, lineTop);
128 		surface->LineTo(irc.left, lineTop);
129 		surface->LineTo(irc.left, ymid + 1);
130 	} else if (sacDraw.style == INDIC_ROUNDBOX ||
131 		sacDraw.style == INDIC_STRAIGHTBOX ||
132 		sacDraw.style == INDIC_FULLBOX) {
133 		PRectangle rcBox = rcLine;
134 		if (sacDraw.style != INDIC_FULLBOX)
135 			rcBox.top = rcLine.top + 1;
136 		rcBox.left = rc.left;
137 		rcBox.right = rc.right;
138 		surface->AlphaRectangle(rcBox, (sacDraw.style == INDIC_ROUNDBOX) ? 1 : 0,
139 			sacDraw.fore, fillAlpha, sacDraw.fore, outlineAlpha, 0);
140 	} else if (sacDraw.style == INDIC_GRADIENT ||
141 		sacDraw.style == INDIC_GRADIENTCENTRE) {
142 		PRectangle rcBox = rc;
143 		rcBox.top = rcLine.top + 1;
144 		rcBox.bottom = rcLine.bottom;
145 		const Surface::GradientOptions options = Surface::GradientOptions::topToBottom;
146 		const ColourAlpha start(sacNormal.fore, fillAlpha);
147 		const ColourAlpha end(sacNormal.fore, 0);
148 		std::vector<ColourStop> stops;
149 		switch (sacDraw.style) {
150 		case INDIC_GRADIENT:
151 			stops.push_back(ColourStop(0.0, start));
152 			stops.push_back(ColourStop(1.0, end));
153 			break;
154 		case INDIC_GRADIENTCENTRE:
155 			stops.push_back(ColourStop(0.0, end));
156 			stops.push_back(ColourStop(0.5, start));
157 			stops.push_back(ColourStop(1.0, end));
158 			break;
159 		}
160 		surface->GradientRectangle(rcBox, stops, options);
161 	} else if (sacDraw.style == INDIC_DOTBOX) {
162 		PRectangle rcBox = PixelGridAlign(rc);
163 		rcBox.top = rcLine.top + 1;
164 		rcBox.bottom = rcLine.bottom;
165 		IntegerRectangle ircBox(rcBox);
166 		// Cap width at 4000 to avoid large allocations when mistakes made
167 		const int width = std::min(ircBox.Width(), 4000);
168 		RGBAImage image(width, ircBox.Height(), 1.0, nullptr);
169 		// Draw horizontal lines top and bottom
170 		for (int x=0; x<width; x++) {
171 			for (int y = 0; y<ircBox.Height(); y += ircBox.Height() - 1) {
172 				image.SetPixel(x, y, sacDraw.fore, ((x + y) % 2) ? outlineAlpha : fillAlpha);
173 			}
174 		}
175 		// Draw vertical lines left and right
176 		for (int y = 1; y<ircBox.Height(); y++) {
177 			for (int x=0; x<width; x += width-1) {
178 				image.SetPixel(x, y, sacDraw.fore, ((x + y) % 2) ? outlineAlpha : fillAlpha);
179 			}
180 		}
181 		surface->DrawRGBAImage(rcBox, image.GetWidth(), image.GetHeight(), image.Pixels());
182 	} else if (sacDraw.style == INDIC_DASH) {
183 		int x = irc.left;
184 		while (x < rc.right) {
185 			surface->MoveTo(x, ymid);
186 			surface->LineTo(std::min(x + 4, irc.right), ymid);
187 			x += 7;
188 		}
189 	} else if (sacDraw.style == INDIC_DOTS) {
190 		int x = irc.left;
191 		while (x < irc.right) {
192 			const PRectangle rcDot = PRectangle::FromInts(x, ymid, x + 1, ymid + 1);
193 			surface->FillRectangle(rcDot, sacDraw.fore);
194 			x += 2;
195 		}
196 	} else if (sacDraw.style == INDIC_COMPOSITIONTHICK) {
197 		const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom);
198 		surface->FillRectangle(rcComposition, sacDraw.fore);
199 	} else if (sacDraw.style == INDIC_COMPOSITIONTHIN) {
200 		const PRectangle rcComposition(rc.left+1, rcLine.bottom-2, rc.right-1, rcLine.bottom-1);
201 		surface->FillRectangle(rcComposition, sacDraw.fore);
202 	} else if (sacDraw.style == INDIC_POINT || sacDraw.style == INDIC_POINTCHARACTER) {
203 		if (rcCharacter.Width() >= 0.1) {
204 			const XYPOSITION pixelHeight = floor(rc.Height() - 1.0f);	// 1 pixel onto next line if multiphase
205 			const XYPOSITION x = (sacDraw.style == INDIC_POINT) ? (rcCharacter.left) : ((rcCharacter.right + rcCharacter.left) / 2);
206 			const XYPOSITION ix = round(x);
207 			const XYPOSITION iy = floor(rc.top + 1.0f);
208 			Point pts[] = {
209 				Point(ix - pixelHeight, iy + pixelHeight),	// Left
210 				Point(ix + pixelHeight, iy + pixelHeight),	// Right
211 				Point(ix, iy)								// Top
212 			};
213 			surface->Polygon(pts, ELEMENTS(pts), sacDraw.fore, sacDraw.fore);
214 		}
215 	} else {	// Either INDIC_PLAIN or unknown
216 		surface->MoveTo(irc.left, ymid);
217 		surface->LineTo(irc.right, ymid);
218 	}
219 }
220 
SetFlags(int attributes_)221 void Indicator::SetFlags(int attributes_) {
222 	attributes = attributes_;
223 }
224