PageSourceSearch

https://canvaspaint.org/src/tools.js

js canvaspaint.org collected 2026-10-02 04:41:59 UTC 19,900 bytes, 708 lines download raw bytes

1
2tools = [{
3	name: "Free-Form Select",
4	description: "Selects a free-form part of the picture to move, copy, or edit.",
5	cursor: ["precise", [16, 16], "crosshair"],
6	// passive: @TODO,
7	
8	// The vertices of the polygon
9	points: [],
10	
11	// The boundaries of the polygon
12	x_min: +Infinity,
13	x_max: -Infinity,
14	y_min: +Infinity,
15	y_max: -Infinity,
16	
17	pointerdown: function(){
18		var tool = this;
19		tool.x_min = pointer.x;
20		tool.x_max = pointer.x+1;
21		tool.y_min = pointer.y;
22		tool.y_max = pointer.y+1;
23		tool.points = [];
24		
25		// End prior selection, drawing it to the canvas
26		deselect();
27		// Checkpoint so we can roll back inverty brush
28		// XXX: Shouldn't use the undo stack for this at all!
29		// TODO: Create an OnCanvasObject for the inverty brush, and make selection a passive action
30		undoable();
31
32		// The inverty brush is continuous in space which means
33		// paint(ctx, x, y) will be called for each pixel the pointer moves
34		// and we only need to record individual pointer events to make the polygon
35		var onpointermove = function(e){
36			var pointer = e2c(e);
37			// Constrain the pointer to the canvas
38			pointer.x = Math.min(canvas.width, pointer.x);
39			pointer.x = Math.max(0, pointer.x);
40			pointer.y = Math.min(canvas.height, pointer.y);
41			pointer.y = Math.max(0, pointer.y);
42			// Add the point
43			tool.points.push(pointer);
44			// Update the boundaries of the polygon
45			tool.x_min = Math.min(pointer.x, tool.x_min);
46			tool.x_max = Math.max(pointer.x, tool.x_max);
47			tool.y_min = Math.min(pointer.y, tool.y_min);
48			tool.y_max = Math.max(pointer.y, tool.y_max);
49		};
50		$G.on("pointermove", onpointermove);
51		$G.one("pointerup", function(){
52			$G.off("pointermove", onpointermove);
53		});
54	},
55	continuous: "space",
56	paint: function(ctx, x, y){
57		
58		// Constrain the inverty paint brush position to the canvas
59		x = Math.min(canvas.width, x);
60		x = Math.max(0, x);
61		y = Math.min(canvas.height, y);
62		y = Math.max(0, y);
63		
64		// Find the dimensions on the canvas of the tiny square to invert
65		var inverty_size = 2;
66		var rect_x = ~~(x - inverty_size/2);
67		var rect_y = ~~(y - inverty_size/2);
68		var rect_w = inverty_size;
69		var rect_h = inverty_size;
70		
71		var ctx_src = undos[undos.length-1].getContext("2d");//psh
72		
73		// Make two tiny ImageData objects,
74		var id_dest = ctx.getImageData(rect_x, rect_y, rect_w, rect_h);
75		var id_src = ctx_src.getImageData(rect_x, rect_y, rect_w, rect_h);
76		
77		for(var i=0, l=id_dest.data.length; i<l; i+=4){
78			id_dest.data[i+0] = 255 - id_src.data[i+0];
79			id_dest.data[i+1] = 255 - id_src.data[i+1];
80			id_dest.data[i+2] = 255 - id_src.data[i+2];
81			id_dest.data[i+3] = 255;
82			// @TODO maybe: invert based on id_src.data[i+3] and the background
83		}
84		
85		ctx.putImageData(id_dest, rect_x, rect_y);
86		
87	},
88	pointerup: function(){
89		// Revert the inverty brush paint
90		ctx.copy(undos[undos.length-1]);
91		
92		var contents_within_polygon = copy_contents_within_polygon(
93			canvas,
94			this.points,
95			this.x_min,
96			this.y_min,
97			this.x_max,
98			this.y_max
99		);
100		
101		selection = new Selection(
102			this.x_min,
103			this.y_min,
104			this.x_max - this.x_min,
105			this.y_max - this.y_min
106		);
107		selection.instantiate(contents_within_polygon);
108		selection.cut_out_background();
109	},
110	$options: $choose_transparency
111}, {
112	name: "Select",
113	description: "Selects a rectangular part of the picture to move, copy, or edit.",
114	cursor: ["precise", [16, 16], "crosshair"],
115	passive: true,
116	drag_start_x: 0,
117	drag_start_y: 0,
118	pointerdown: function(){
119		this.drag_start_x = pointer.x;
120		this.drag_start_y = pointer.y;
121		if(selection){
122			selection.draw();
123			selection.destroy();
124			selection = null;
125		}
126		var pointer_has_moved = false;
127		$G.one("pointermove", function(){
128			pointer_has_moved = true;
129		});
130		$G.one("pointerup", function(){
131			if(!pointer_has_moved && selection){
132				selection.draw();//?
133				selection.destroy();
134				selection = null;
135			}
136		});
137		selection = new Selection(pointer.x, pointer.y, 1, 1);
138	},
139	paint: function(){
140		if(!selection){ return; }
141		var x1 = Math.max(0, Math.min(this.drag_start_x, pointer.x));
142		var y1 = Math.max(0, Math.min(this.drag_start_y, pointer.y));
143		var x2 = Math.min(canvas.width, Math.max(this.drag_start_x, pointer.x));
144		var y2 = Math.min(canvas.height, Math.max(this.drag_start_y, pointer.y));
145		selection.x = x1;
146		selection.y = y1;
147		selection.width = Math.max(1, x2 - x1);
148		selection.height = Math.max(1, y2 - y1);
149		selection.position();
150	},
151	pointerup: function(){
152		if(!selection){ return; }
153		
154		if(ctrl){
155			selection.crop();
156			selection.destroy();
157			selection = null;
158		}else{
159			selection.instantiate();
160		}
161	},
162	cancel: function(){
163		if(!selection){return;}
164		selection.destroy();
165		selection = null;
166	},
167	$options: $choose_transparency
168}, {
169	name: "Eraser/Color Eraser",
170	description: "Erases a portion of the picture, using the selected eraser shape.",
171	cursor: ["precise", [16, 16], "crosshair"],
172	// @TODO: draw square on canvas as cursor
173	continuous: "space",
174	paint: function(ctx, x, y){
175		
176		var rect_x = ~~(x - eraser_size/2);
177		var rect_y = ~~(y - eraser_size/2);
178		var rect_w = eraser_size;
179		var rect_h = eraser_size;
180		
181		if(button === 0){
182			// Eraser
183			if(transparency){
184				ctx.clearRect(rect_x, rect_y, rect_w, rect_h);
185			}else{
186				ctx.fillStyle = colors.background;
187				ctx.fillRect(rect_x, rect_y, rect_w, rect_h);
188			}
189		}else{
190			// Color Eraser
191			// Right click with the eraser to selectively replace
192			// the selected foreground color with the selected background color
193			
194			var fg_rgba = get_rgba_from_color(colors.foreground);
195			var bg_rgba = get_rgba_from_color(colors.background);
196			
197			var id = ctx.getImageData(rect_x, rect_y, rect_w, rect_h);
198			
199			for(var i=0, l=id.data.length; i<l; i+=4){
200				if(
201					id.data[i+0] === fg_rgba[0] &&
202					id.data[i+1] === fg_rgba[1] &&
203					id.data[i+2] === fg_rgba[2] &&
204					id.data[i+3] === fg_rgba[3]
205				){
206					id.data[i+0] = bg_rgba[0];
207					id.data[i+1] = bg_rgba[1];
208					id.data[i+2] = bg_rgba[2];
209					id.data[i+3] = bg_rgba[3];
210				}
211			}
212			
213			ctx.putImageData(id, rect_x, rect_y);
214		}
215	},
216	$options: $choose_eraser_size
217}, {
218	name: "Fill With Color",
219	description: "Fills an area with the selected drawing color.",
220	cursor: ["fill-bucket", [8, 22], "crosshair"],
221	pointerdown: function(ctx, x, y){
222		
223		// Get the rgba values of the selected fill color
224		var rgba = get_rgba_from_color(fill_color);
225		
226		if(shift){
227			// Perform a global (non-contiguous) fill operation, AKA color replacement
228			draw_noncontiguous_fill(ctx, x, y, rgba[0], rgba[1], rgba[2], rgba[3]);
229		} else {
230			// Perform a normal fill operation
231			draw_fill(ctx, x, y, rgba[0], rgba[1], rgba[2], rgba[3]);
232		}
233	}
234}, {
235	name: "Pick Color",
236	description: "Picks up a color from the picture for drawing.",
237	cursor: ["eye-dropper", [9, 22], "crosshair"],
238	deselect: true,
239	passive: true,
240	
241	current_color: "",
242	display_current_color: function(){
243		this.$options.css({
244			background: this.current_color
245		});
246	},
247	pointerdown: function(){
248		var _this = this;
249		$G.one("pointerup", function(){
250			_this.$options.css({
251				background: ""
252			});
253		});
254	},
255	paint: function(ctx, x, y){
256		if(x >= 0 && y >= 0 && x < canvas.width && y < canvas.height){
257			var id = ctx.getImageData(~~x, ~~y, 1, 1);
258			var r = id.data[0];
259			var g = id.data[1];
260			var b = id.data[2];
261			var a = id.data[3];
262			this.current_color = "rgba("+r+","+g+","+b+","+a/255+")";
263		}else{
264			this.current_color = "white";
265		}
266		this.display_current_color();
267	},
268	pointerup: function(){
269		colors[fill_color_k] = this.current_color;
270		$G.trigger("option-changed");
271	},
272	$options: $(E("div"))
273}, {
274	name: "Magnifier",
275	description: "Changes the magnification.",
276	cursor: ["magnifier", [16, 16], "zoom-in"],
277	// @TODO: use zoom-in/zoom-out as default,
278	// even though the custom cursor image is less descriptive
279	deselect: true,
280	passive: true,
281	// @TODO: choose and preview viewport with rectangular cursor
282	pointerdown: function(){
283		if(magnification > 1){
284			reset_magnification();
285		}else{
286			set_magnification(this.$options.enlarged_magnification);
287		}
288	},
289	$options: $choose_magnification
290}, {
291	name: "Pencil",
292	description: "Draws a free-form line one pixel wide.",
293	cursor: ["pencil", [13, 23], "crosshair"],
294	continuous: "space",
295	stroke_only: true,
296	pencil_canvas: Canvas(),
297	paint: function(ctx, x, y){
298		// XXX: WET (Write Everything Twice) / DAMP (Duplicate Anything Moderately Pastable) (I'm coining that)
299		// TODO: DRY (Don't Repeat Yourself) / DEHYDRATE (Delete Everything Hindering Yourself Drastically Reducing Aqueous Text Evil) (I'm coining that too)
300		var csz = get_brush_canvas_size(pencil_size, "circle");
301		if(
302			this.rendered_shape !== "circle" ||
303			this.rendered_color !== stroke_color ||
304			this.rendered_size !== pencil_size
305		){
306			this.pencil_canvas.width = csz;
307			this.pencil_canvas.height = csz;
308			// don't need to do this.pencil_canvas.ctx.disable_image_smoothing() currently because images aren't drawn to the brush
309
310			this.pencil_canvas.ctx.fillStyle = this.pencil_canvas.ctx.strokeStyle = stroke_color;
311			render_brush(this.pencil_canvas.ctx, "circle", pencil_size);
312			
313			this.rendered_color = stroke_color;
314			this.rendered_size = pencil_size;
315			this.rendered_shape = "circle";
316		}
317		ctx.drawImage(this.pencil_canvas, Math.ceil(x-csz/2), Math.ceil(y-csz/2));
318	}
319}, {
320	name: "Brush",
321	description: "Draws using a brush with the selected shape and size.",
322	cursor: ["precise-dotted", [16, 16], "crosshair"],
323	continuous: "space",
324	rendered_color: "",
325	rendered_size: 0,
326	rendered_shape: "",
327	paint: function(ctx, x, y){
328		var csz = get_brush_canvas_size(brush_size, brush_shape);
329		if(
330			this.rendered_shape !== brush_shape ||
331			this.rendered_color !== stroke_color ||
332			this.rendered_size !== brush_size
333		){
334			brush_canvas.width = csz;
335			brush_canvas.height = csz;
336			// don't need to do brush_ctx.disable_image_smoothing() currently because images aren't drawn to the brush
337
338			brush_ctx.fillStyle = brush_ctx.strokeStyle = stroke_color;
339			render_brush(brush_ctx, brush_shape, brush_size);
340			
341			this.rendered_color = stroke_color;
342			this.rendered_size = brush_size;
343			this.rendered_shape = brush_shape;
344		}
345		ctx.drawImage(brush_canvas, Math.ceil(x-csz/2), Math.ceil(y-csz/2));
346	},
347	$options: $choose_brush
348}, {
349	name: "Airbrush",
350	description: "Draws using an airbrush of the selected size.",
351	cursor: ["airbrush", [7, 22], "crosshair"],
352	continuous: "time",
353	paint: function(ctx, x, y){
354		var r = airbrush_size / 2;
355		for(var i = 0; i < 6 + r/5; i++){
356			var rx = (Math.random()*2-1) * r;
357			var ry = (Math.random()*2-1) * r;
358			var d = rx*rx + ry*ry;
359			if(d <= r * r){
360				ctx.fillRect(x + ~~rx, y + ~~ry, 1, 1);
361			}
362		}
363	},
364	$options: $choose_airbrush_size
365}, {
366	name: "Text",
367	description: "Inserts text into the picture.",
368	cursor: ["precise", [16, 16], "crosshair"],
369	passive: true,
370	activate: function(){
371		setTimeout(FontDetective.preload, 10);
372	},
373	drag_start_x: 0,
374	drag_start_y: 0,
375	pointerdown: function(){
376		this.drag_start_x = pointer.x;
377		this.drag_start_y = pointer.y;
378		if(textbox){
379			textbox.draw();
380			textbox.destroy();
381		}
382		var pointer_has_moved = false;
383		$G.one("pointermove", function(){
384			pointer_has_moved = true;
385		});
386		$G.one("pointerup", function(){
387			if(!pointer_has_moved && textbox){
388				textbox.draw();
389				textbox.destroy();
390				textbox = null;
391			}
392		});
393		textbox = new TextBox(pointer.x, pointer.y, 1, 1);
394	},
395	paint: function(){
396		if(!textbox){ return; }
397		var x1 = Math.max(0, Math.min(this.drag_start_x, pointer.x));
398		var y1 = Math.max(0, Math.min(this.drag_start_y, pointer.y));
399		var x2 = Math.min(canvas.width, Math.max(this.drag_start_x, pointer.x));
400		var y2 = Math.min(canvas.height, Math.max(this.drag_start_y, pointer.y));
401		textbox.x = x1;
402		textbox.y = y1;
403		textbox.width = Math.max(1, x2 - x1);
404		textbox.height = Math.max(1, y2 - y1);
405		textbox.position();
406	},
407	pointerup: function(){
408		if(!textbox){ return; }
409		textbox.instantiate();
410	},
411	cancel: function(){
412		if(!textbox){ return; }
413		textbox.destroy();
414		textbox = null;
415	},
416	$options: $choose_transparency
417}, {
418	name: "Line",
419	description: "Draws a straight line with the selected line width.",
420	cursor: ["precise", [16, 16], "crosshair"],
421	stroke_only: true,
422	shape: function(ctx, x, y, w, h){
423		update_brush_for_drawing_lines(stroke_size);
424		draw_line(ctx, x, y, x+w, y+h, stroke_size);
425	},
426	$options: $choose_stroke_size
427}, {
428	name: "Curve",
429	description: "Draws a curved line with the selected line width.",
430	cursor: ["precise", [16, 16], "crosshair"],
431	stroke_only: true,
432	points: [],
433	passive: function(){
434		// Actions are passive if you've already started using the tool,
435		// but the first action should be undoable / cancelable
436		return this.points.length > 0;
437	},
438	pointerup: function(ctx, x, y){
439		if(this.points.length >= 4){
440			this.points = [];
441		}
442	},
443	pointerdown: function(ctx, x, y){
444		if(this.points.length < 1){
445			// This would be so much better in CoffeeScript
446			var thine = this;
447			undoable(function(){
448				thine.points.push({x: x, y: y});
449				// second point so first action draws a line
450				thine.points.push({x: x, y: y});
451			});
452		}else{
453			this.points.push({x: x, y: y});
454		}
455	},
456	paint: function(ctx, x, y){
457		if(this.points.length < 1){ return; }
458		
459		var i = this.points.length - 1;
460		this.points[i].x = x;
461		this.points[i].y = y;
462		
463		update_brush_for_drawing_lines(stroke_size);
464		
465		if(this.points.length === 4){
466			draw_bezier_curve(
467				ctx,
468				this.points[0].x, this.points[0].y,
469				this.points[2].x, this.points[2].y,
470				this.points[3].x, this.points[3].y,
471				this.points[1].x, this.points[1].y,
472				stroke_size
473			);
474		}else if(this.points.length === 3){
475			draw_quadratic_curve(
476				ctx,
477				this.points[0].x, this.points[0].y,
478				this.points[2].x, this.points[2].y,
479				this.points[1].x, this.points[1].y,
480				stroke_size
481			);
482		}else{
483			draw_line(
484				ctx,
485				this.points[0].x, this.points[0].y,
486				this.points[1].x, this.points[1].y,
487				stroke_size
488			);
489		}
490	},
491	cancel: function(){
492		this.points = [];
493	},
494	end: function(){
495		this.points = [];
496	},
497	shape: function(){true},
498	$options: $choose_stroke_size
499}, {
500	name: "Rectangle",
501	description: "Draws a rectangle with the selected fill style.",
502	cursor: ["precise", [16, 16], "crosshair"],
503	shape: function(ctx, x, y, w, h){
504		if(w < 0){ x += w; w = -w; }
505		if(h < 0){ y += h; h = -h; }
506		
507		if(this.$options.fill){
508			ctx.fillRect(x, y, w, h);
509		}
510		if(this.$options.stroke){
511			if(w < stroke_size * 2 || h < stroke_size * 2){
512				ctx.save();
513				ctx.fillStyle = ctx.strokeStyle;
514				ctx.fillRect(x, y, w, h);
515				ctx.restore();
516			}else{
517				// TODO: shouldn't that be ~~(stroke_size / 2)?
518				ctx.strokeRect(x + stroke_size / 2, y + stroke_size / 2, w - stroke_size, h - stroke_size);
519			}
520		}
521	},
522	$options: $ChooseShapeStyle()
523}, {
524	name: "Polygon",
525	description: "Draws a polygon with the selected fill style.",
526	cursor: ["precise", [16, 16], "crosshair"],
527	
528	// Record the last click for double-clicking
529	// A double click happens on pointerdown of a second click
530	// (within a cylindrical volume in 2d space + 1d time)
531	last_click_pointerdown: {x: -Infinity, y: -Infinity, time: -Infinity},
532	last_click_pointerup: {x: -Infinity, y: -Infinity, time: -Infinity},
533	
534	// The vertices of the polygon
535	points: [],
536	
537	passive: function(){
538		// actions are passive if you've already started using the tool
539		// but the first action should be undoable
540		return this.points.length > 0;
541		// In other words, it's supposed to be one undoable action
542	},
543	pointerup: function(ctx, x, y){
544		if(this.points.length < 1){ return; }
545		
546		var i = this.points.length - 1;
547		this.points[i].x = x;
548		this.points[i].y = y;
549		var dx = this.points[i].x - this.points[0].x;
550		var dy = this.points[i].y - this.points[0].y;
551		var d = Math.sqrt(dx*dx + dy*dy);
552		if(d < stroke_size * 5.1010101){ // arbitrary 101 (TODO: find correct value (or formula))
553			this.complete(ctx, x, y);
554		}
555		
556		this.last_click_pointerup = {x: x, y: y, time: +(new Date)};
557	},
558	pointerdown: function(ctx, x, y){
559		var tool = this;
560		
561		if(tool.points.length < 1){
562			// @TODO: stop needing this:
563			tool.canvas_base = canvas;
564			
565			undoable(function(){
566				// @TODO: stop needing this:
567				tool.canvas_base = undos[undos.length-1];
568				
569				// Add the first point of the polygon
570				tool.points.push({x: x, y: y});
571				// Add a second point so first action draws a line
572				tool.points.push({x: x, y: y});
573			});
574		}else{
575			var lx = tool.last_click_pointerdown.x;
576			var ly = tool.last_click_pointerdown.y;
577			var lt = tool.last_click_pointerdown.time;
578			var dx = x - lx;
579			var dy = y - ly;
580			var dt = +(new Date) - lt;
581			var d = Math.sqrt(dx*dx + dy*dy);
582			if(d < 4.1010101 && dt < 250){ // arbitrary 101 (TODO: find correct value (or formula))
583				tool.complete(ctx, x, y);
584				// Release the pointer to prevent tool.paint()
585				// being called and clearing the canvas
586				$canvas.trigger("pointerup");
587			}else{
588				// Add the point
589				tool.points.push({x: x, y: y});
590			}
591		}
592		tool.last_click_pointerdown = {x: x, y: y, time: +new Date};
593	},
594	paint: function(ctx, x, y){
595		if(this.points.length < 1){ return; }
596		
597		// Clear the canvas to the previous image to get
598		// rid of lines drawn while constructing the shape
599		// @TODO: stop needing this
600		ctx.copy(this.canvas_base);
601		
602		var i = this.points.length - 1;
603		this.points[i].x = x;
604		this.points[i].y = y;
605		
606		ctx.strokeStyle = stroke_color;
607		draw_line_strip(
608			ctx,
609			this.points
610		);
611	},
612	complete: function(ctx, x, y){
613		if(this.points.length < 1){ return; }
614		
615		// Clear the canvas to the previous image to get
616		// rid of lines drawn while constructing the shape
617		// @TODO: stop needing this
618		ctx.copy(this.canvas_base);
619		
620		ctx.fillStyle = fill_color;
621		ctx.strokeStyle = stroke_color;
622
623		draw_polygon(
624			ctx,
625			this.points,
626			this.$options.stroke,
627			this.$options.fill
628		);
629		
630		this.reset();
631	},
632	cancel: function(){
633		this.reset();
634	},
635	end: function(){
636		this.reset();
637	},
638	reset: function(){
639		this.points = [];
640		this.last_click_pointerdown = {x: -Infinity, y: -Infinity, time: -Infinity};
641		this.last_click_pointerup = {x: -Infinity, y: -Infinity, time: -Infinity};
642	},
643	shape_colors: true,
644	$options: $ChooseShapeStyle()
645}, {
646	name: "Ellipse",
647	description: "Draws an ellipse with the selected fill style.",
648	cursor: ["precise", [16, 16], "crosshair"],
649	shape: function(ctx, x, y, w, h){
650		if(w < 0){ x += w; w = -w; }
651		if(h < 0){ y += h; h = -h; }
652
653		if(w < stroke_size || h < stroke_size){
654			ctx.fillStyle = ctx.strokeStyle;
655			draw_ellipse(ctx, x, y, w, h, false, true);
656		}else{
657			draw_ellipse(
658				ctx,
659				x + ~~(stroke_size / 2),
660				y + ~~(stroke_size / 2),
661				w - stroke_size,
662				h - stroke_size,
663				this.$options.stroke,
664				this.$options.fill
665			);
666		}
667	},
668	$options: $ChooseShapeStyle()
669}, {
670	name: "Rounded Rectangle",
671	description: "Draws a rounded rectangle with the selected fill style.",
672	cursor: ["precise", [16, 16], "crosshair"],
673	shape: function(ctx, x, y, w, h){
674		if(w < 0){ x += w; w = -w; }
675		if(h < 0){ y += h; h = -h; }
676
677		if(w < stroke_size || h < stroke_size){
678			ctx.fillStyle = ctx.strokeStyle;
679			var radius = Math.min(8, w/2, h/2);
680			// var radius_x = Math.min(8, w/2);
681			// var radius_y = Math.min(8, h/2);
682			draw_rounded_rectangle(
683				ctx,
684				x, y, w, h,
685				radius, radius,
686				// radius_x, radius_y,
687				false,
688				true
689			);
690		}else{
691			var radius = Math.min(8, (w - stroke_size)/2, (h - stroke_size)/2);
692			// var radius_x = Math.min(8, (w - stroke_size)/2);
693			// var radius_y = Math.min(8, (h - stroke_size)/2);
694			draw_rounded_rectangle(
695				ctx,
696				x + ~~(stroke_size / 2),
697				y + ~~(stroke_size / 2),
698				w - stroke_size,
699				h - stroke_size,
700				radius, radius,
701				// radius_x, radius_y,
702				this.$options.stroke,
703				this.$options.fill
704			);
705		}
706	},
707	$options: $ChooseShapeStyle()
708}];

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.