Unity GUI combo box with search - unity3d

I need a list which is a lot of list (enum list). And right now the list is using the pop up (combo box), but the normal combo box is not really help because too many item inside it. It kind of frustrating when try to select item that is far away at bottom. The list are full on the screen when openned.
When openning a combo box and type a letter it will jump only the first letter, when I press second letter the list will be jump to another first letter start. So example, I want to select DIAMOND -> I press D and it will go to the list with D start. And when I press I, it will jump to item that start with I instead of DI.
Is there any component of GUI to have the search?

Unity haven't components for search like your.
But you can try something like this (SearchEnumLabel function):
using System;
using System.Globalization;
using System.Linq;
using UnityEditor;
public enum States
{
ABCDEF,
ACBDEF,
AdEXG,
bErDSa
}
[CustomEditor(typeof(ObjectControllerTester))]
[CanEditMultipleObjects]
public class ObjectControllerTesterEditor : Editor
{
States _selected;
public override void OnInspectorGUI ()
{
_selected = SearchEnumLabel("My search enum", _selected);
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField("[Debug]Current selected: "+_selected);
}
private String _searchEnumText = "";
private bool _isSearchEnumLabelInSearch = false;
private T SearchEnumLabel<T>(String label, T state) where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
{
EditorGUILayout.LabelField("T must be an enumerated type");
return state;
}
var states = Enum.GetValues(typeof (T)).Cast<object>().Select(o => o.ToString()).ToArray();
if (string.IsNullOrEmpty(_searchEnumText) && states.Length > 0) _searchEnumText = state.ToString(CultureInfo.InvariantCulture);
var text = EditorGUILayout.TextField(label, _searchEnumText);
if (text != _searchEnumText || _isSearchEnumLabelInSearch)
{
_searchEnumText = text;
var mach = states.Select((v,i)=>new {value = v, index = i}).FirstOrDefault(a => a.value.ToLower().StartsWith(text.ToLower()));
var targetState = state;
if (mach != null) targetState = (T) Enum.GetValues(typeof (T)).GetValue(mach.index);
EditorGUILayout.LabelField("Select closest: "+targetState);
Repaint();
state = targetState;
_isSearchEnumLabelInSearch = !string.Equals(_searchEnumText, targetState.ToString(CultureInfo.InvariantCulture), StringComparison.CurrentCultureIgnoreCase);
}
return state;
}
}
This script will show something like this:
Default view
After input some data
=== UPDATE ===
More complex variant with fast select buttons
using System;
using System.Globalization;
using System.Linq;
using UnityEditor;
using UnityEngine;
public enum States
{
ABCDEF,
ACBDEF,
AdEXG,
bErDSa,
sEOjsfl,
SdDiaso,
POsdjaow,
PSADJsd,
Oasdo,
IOQWEnds
}
[CustomEditor(typeof(ObjectControllerTester))]
[CanEditMultipleObjects]
public class ObjectControllerTesterEditor : Editor
{
States _selected;
public override void OnInspectorGUI()
{
_selected = SearchEnumLabel("My search enum", _selected);
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField("[Debug]Current selected: " + _selected);
}
private String _searchEnumText = "";
private bool _isSearchEnumLabelInSearch = false;
private T SearchEnumLabel<T>(String label, T state) where T : struct, IConvertible
{
if (!typeof(T).IsEnum)
{
EditorGUILayout.LabelField("T must be an enumerated type");
return state;
}
var states = Enum.GetValues(typeof(T)).Cast<object>().Select(o => o.ToString()).ToArray();
if (string.IsNullOrEmpty(_searchEnumText) && states.Length > 0) _searchEnumText = state.ToString(CultureInfo.InvariantCulture);
var text = EditorGUILayout.TextField(label, _searchEnumText);
if (text != _searchEnumText || _isSearchEnumLabelInSearch)
{
_searchEnumText = text;
var mach = states.Select((v, i) => new { value = v, index = i }).Where(a => a.value.ToLower().StartsWith(text.ToLower())).ToList();
var targetState = state;
if (mach.Any())
{
// many of results
targetState = (T)Enum.GetValues(typeof(T)).GetValue(mach[0].index);
EditorGUILayout.LabelField("Select closested: " + targetState);
Repaint();
var selected = GUILayout.SelectionGrid(-1, mach.Select(v => v.value).ToArray(), 4);
if (selected != -1)
{
targetState = (T)Enum.GetValues(typeof(T)).GetValue(mach[selected].index);
_searchEnumText = targetState.ToString(CultureInfo.InvariantCulture);
_isSearchEnumLabelInSearch = false;
GUI.FocusControl("FocusAway");
Repaint();
}
}
state = targetState;
_isSearchEnumLabelInSearch = !string.Equals(_searchEnumText, targetState.ToString(CultureInfo.InvariantCulture), StringComparison.CurrentCultureIgnoreCase);
}
return state;
}
}
Tap button to select target enum

Here is my solution, you can replace 'EnumNBEvent' as you like.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Newborn.Pb.Event;
using System;
using System.Reflection;
using Google.Protobuf.Reflection;
using System.Text.RegularExpressions;
[CustomPropertyDrawer(typeof(EnumNBEvent), false)]
public class FSMEventDrawer : PropertyDrawer {
struct EnumStringValuePair : IComparable<EnumStringValuePair>
{
public string strValue;
public int intValue;
public int CompareTo(EnumStringValuePair another)
{
if (intValue < another.intValue)
return -1;
else if (intValue > another.intValue)
return 1;
return 0;
}
}
Dictionary<int, string> filters = new Dictionary<int, string>();
//string filter = string.Empty;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
int y = (int)position.position.y;
if (!filters.ContainsKey(y))
filters[y] = string.Empty;
EditorGUI.BeginProperty(position, label, property);
EditorGUI.LabelField(new Rect(position.x, position.y, 100, 20), property.name);
filters[y] = EditorGUI.TextField(new Rect(position.x + 100, position.y, 60, 15), filters[y]);
List<EnumStringValuePair> enumList = GetEnumList(filters[y]);
List<string> enumStrList = new List<string>(enumList.Count);
for (int i = 0; i < enumList.Count; ++i)
{
enumStrList.Add(enumList[i].strValue);
}
int selectedIndex = 0;
for (int i = 0; i < enumList.Count; ++i)
{
if (enumList[i].intValue == property.enumValueIndex)
{
selectedIndex = i;
break;
}
}
selectedIndex = EditorGUI.Popup(new Rect(position.x + 170, position.y, 200, 20), selectedIndex, enumStrList.ToArray());
if (enumList.Count > selectedIndex)
{
property.enumValueIndex = enumList[selectedIndex].intValue;
}
EditorGUI.EndProperty();
}
private List<EnumStringValuePair> GetEnumList(string filter)
{
List<EnumStringValuePair> allList = new List<EnumStringValuePair>();
Array enumValues = Enum.GetValues(typeof(EnumNBEvent));
for (int i = 0; i < enumValues.Length; ++i)
{
EnumStringValuePair pair = new EnumStringValuePair();
pair.strValue = enumValues.GetValue(i).ToString();
pair.intValue = (int)enumValues.GetValue(i);
allList.Add(pair);
}
List<EnumStringValuePair> ret = new List<EnumStringValuePair>();
Regex regex = new Regex(filter.ToLower());
for (int i = 0; i < allList.Count; ++i)
{
if (regex.IsMatch(allList[i].strValue.ToLower()))
{
ret.Add(allList[i]);
}
}
return ret;
}
}

Related

Item in ScrollView is not seen in Scene, but it shows in hierarchy

I am trying to display a list, generated dinamically. I created a prefab with the things I need in it (a TextView, 3 TMP_InputFields and 2 Buttons.)
To manage the different list items, I created a script (SkillManager, since the items represents skill the player can choose), which I attached to the prefab.
Then, I add every item (currently I am adding only one for testing purposes) to a List, iterate that list, and add the prefab to the Content of a ScrollView:
for(int i = 0; i < listaSkills.Count; i++)
{
GameObject listItem = Instantiate(SkillPrefab) as GameObject;
listItem.GetComponent<SkillManager>().skill = listaSkills[i];
//listItem.transform.SetParent(SkillsContent.transform, false);
listItem.transform.parent = SkillsContent.transform;
}
When I run this, no item is seen in the ScrollView, but I can see the SkillItem added to the hierarchy:
If I move to Scene tab after playing, I see a square with red lines crossing it:
Why is my item not displaying? Why the red cross? How can I populate my ScrollView?
EDIT:
This is the code of SkillManager, the script added to SkillPrefab:
public class SkillManager : MonoBehaviour
{
public TMP_InputField toSpend;
public TMP_InputField rangos;
public TMP_InputField modificadores;
public TMP_InputField total;
public Button plusButton;
public Button minusButton;
public TMP_Text nombre;
public Skill skill;
private int modificador;
private int pointsToSpend;
private int totalPoints;
// Start is called before the first frame update
void Start()
{
print("Start");
if(total!=null)
total.text = "0";
if(modificadores!=null)
modificadores.text = "0";
if (toSpend != null)
{
toSpend.GetComponent<TMP_InputField>().text = GetSkillPoints();
totalPoints = int.Parse(total.GetComponent<TMP_InputField>().text);
pointsToSpend = int.Parse(toSpend.GetComponent<TMP_InputField>().text);
}
else
{
GameObject GameObjectToSpend = GameObject.FindGameObjectWithTag("tospend");
toSpend = GameObjectToSpend.GetComponent<TMP_InputField>();
if (toSpend == null)
{
print("Sigue siendo nulo");
}
else
{
toSpend.text= GetSkillPoints();
//totalPoints = int.Parse(total.GetComponent<TMP_InputField>().text);
if(total!=null)
totalPoints = int.Parse(total.text);
if(toSpend!=null)
pointsToSpend = int.Parse(toSpend.text);
}
}
if (skill != null)
{
modificador = GetModificador(skill);
string sModificador = modificadores.GetComponent<TMP_InputField>().text;
int iModificador = int.Parse(sModificador);
modificadores.GetComponent<TMP_InputField>().text = iModificador.ToString();
}
if (plusButton != null)
{
plusButton.onClick.AddListener(PlusButtonClicked);
minusButton.onClick.AddListener(MinusButtonClicked);
}
}
private string GetSkillPoints()
{
return "1";
}
public void MinusButtonClicked()
{
string ranks = rangos.GetComponent<TMP_InputField>().text;
int ranksInt = int.Parse(ranks);
if (ranksInt > 0)
{
int newRank = ranksInt - 1;
pointsToSpend += 1;
rangos.GetComponent<TMP_InputField>().text = newRank.ToString();
toSpend.GetComponent<TMP_InputField>().text = pointsToSpend.ToString();
total.GetComponent<TMP_InputField>().text = (newRank + modificador).ToString();
skill.Puntos = newRank;
}
}
public void PlusButtonClicked()
{
string ranks=rangos.GetComponent<TMP_InputField>().text;
int ranksInt = int.Parse(ranks);
Character character = Almacen.instance.Character;
int level = character.CharacterLevel;
if (ranksInt < level && pointsToSpend > 0)
{
int newRank = ranksInt + 1;
rangos.GetComponent<TMP_InputField>().text = newRank.ToString();
pointsToSpend -= 1;
toSpend.GetComponent<TMP_InputField>().text = pointsToSpend.ToString();
total.GetComponent<TMP_InputField>().text = (newRank + modificador).ToString();
skill.Puntos = newRank;
}
}
private int GetModificador(Skill skill)
{
int retorno=0;
if (skill.Clasea)
{
retorno += 3;
}
else
{
retorno += 0;
}
retorno += GetModificadorCaracteristica();
return retorno;
}
private int GetModificadorCaracteristica()
{
Utils utils = new Utils();
int retorno = 0;
int characteristic=0;
switch (skill.Caracteristica)
{
case "Fue":
characteristic = Almacen.instance.Character.EffectiveStr;
break;
case "Des":
characteristic = Almacen.instance.Character.EffectiveDex;
break;
case "Con":
characteristic = Almacen.instance.Character.EffectiveCon;
break;
case "Int":
characteristic = Almacen.instance.Character.EffectiveInt;
break;
case "Sab":
characteristic = Almacen.instance.Character.EffectiveWis;
break;
case "Car":
characteristic = Almacen.instance.Character.EffectiveCha;
break;
}
retorno = utils.GetCharModifier(characteristic);
return retorno;
}
}
it looks like you instantiate the object as a GameObject. but this will not be seen in the canvas because it isn't a UI component. you may want to add a sprite or image to the component and instantiate that into the Canvas. it will look something like this:
public class SkillPrefab
{
//put all your variables here!!!
public Sprite skillSprite;
}
public class YourClassName : MonoBehaviour
{
[SerializeField]
public List<SkillPrefab> skills = new List<SkillPrefab>();
private void Update()
{
Sprite listItem = Instantiate(skills[0].skillSprite); //the index is the skill you want to spawn in the list.
}
}
this does take into account that you have made your skills into a list of skills that you can acces.

How to choose a color randomly from the list?

Unity C#
I made a list. The color changes after 5 seconds. I defined the color from which the list begins ("_currentIndex = 0"). The first color should always be the one I defined at the beginning. What I have means that each color on the list is selected one by one. After the last color everything comes back to the beginning.
I would like the first color to always be the same, but each subsequent one was chosen randomly from the list. It must be an infinite loop.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColorCycler : MonoBehaviour
{
public Color[] Colors;
public float Speed = 5;
int _currentIndex;
Camera _cam;
bool _shouldChange = false;
private int randomColors;
void Start()
{
_cam = GetComponent<Camera>();
_currentIndex = 0;
SetColor(Colors[_currentIndex]);
}
public void SetColor(Color color)
{
_cam.backgroundColor = color;
}
public void Cycle()
{
_shouldChange = true;
}
void Update()
{
if (_shouldChange)
{
var startColor = _cam.backgroundColor;
//start from color with number
var endColor = Colors[0];
if (_currentIndex + 1 < Colors.Length)
{
endColor = Colors[_currentIndex + 1];
}
var newColor = Color.Lerp(startColor, endColor, Time.deltaTime * Speed);
SetColor(newColor);
if (newColor == endColor)
{
_shouldChange = false;
if (_currentIndex + 1 < Colors.Length)
{
_currentIndex++;
}
else
{
_currentIndex = 0;
}
}
}
}
}
If you just want a random element from a list then just choose a random index.
Random random = new Random();
int randomIndex = random.Next(Colors.Count);
color = Colors[randomIndex];

Unity 5 Inventory system not working

Hello programmers all around the world. I have made myself an inventory system for my game. Only problem is that when I click on item and then drag it to and empty slot it doesn't move and I kinda don't see the error which I am having and I have tried to debug it but without success any help? Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class Inventory : MonoBehaviour {
private RectTransform inventoryRect;
private float inventoryWidth;
private float inventoryHeight;
public int slots;
public int rows;
public float slotPaddingLeft;
public float slotPaddingTop;
public float slotSize;
public GameObject slotPrefab;
private static Slot from;
private static Slot to;
private List<GameObject> allslots;
public GameObject iconPrefab;
private static GameObject hoverObject;
private static int emptySlots;
public Canvas canvas;
private float hoverYOffset;
private bool isPressed;
public EventSystem eventSystem;
public static int EmptySlots{
get{ return emptySlots;}
set{ emptySlots = value;}
}
// Use this for initialization
void Start () {
CreateLayout ();
canvas.enabled = false;
isPressed = false;
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown (KeyCode.I)) {
if (Input.GetKeyDown (KeyCode.I)) {
canvas.enabled = false;
}
canvas.enabled = true;
}
if (Input.GetMouseButtonUp (0)) {
if (!eventSystem.IsPointerOverGameObject (-1) && from != null) {
from.GetComponent<Image> ().color = Color.white;
from.ClearSlot ();
Destroy (GameObject.Find ("Hover"));
to = null;
from = null;
hoverObject = null;
}
}
if (hoverObject != null) {
Vector2 position;
RectTransformUtility.ScreenPointToLocalPointInRectangle (canvas.transform as RectTransform, Input.mousePosition, canvas.worldCamera, out position);
position.Set (position.x, position.y - hoverYOffset);
hoverObject.transform.position = canvas.transform.TransformPoint (position);
}
}
private void CreateLayout(){
allslots = new List<GameObject> ();
hoverYOffset = slotSize * 0.01f;
emptySlots = slots;
inventoryWidth = (slots / rows) * (slotSize + slotPaddingLeft) + slotPaddingLeft;
inventoryHeight = rows * (slotSize + slotPaddingTop) + slotPaddingTop;
inventoryRect = GetComponent<RectTransform> ();
inventoryRect.SetSizeWithCurrentAnchors (RectTransform.Axis.Horizontal, inventoryWidth);
inventoryRect.SetSizeWithCurrentAnchors (RectTransform.Axis.Vertical, inventoryHeight);
int colums = slots / rows;
for (int y = 0; y < rows; y++) {
for (int x = 0; x < colums; x++) {
GameObject newSlot = (GameObject)Instantiate (slotPrefab);
RectTransform slotRect = newSlot.GetComponent<RectTransform> ();
newSlot.name = "Slot";
newSlot.transform.SetParent (this.transform.parent);
slotRect.localPosition = inventoryRect.localPosition + new Vector3 (slotPaddingLeft * (x + 1) + (slotSize * x), -slotPaddingTop * (y + 1) - (slotSize * y));
slotRect.SetSizeWithCurrentAnchors (RectTransform.Axis.Horizontal, slotSize);
slotRect.SetSizeWithCurrentAnchors (RectTransform.Axis.Vertical, slotSize);
allslots.Add (newSlot);
}
}
}
public bool AddItem(Item item){
if (item.maxSize == 1) {
PlaceEmpty (item);
return true;
}
else {
foreach (GameObject slot in allslots) {
Slot temporary = slot.GetComponent<Slot> ();
if (!temporary.IsEmpty) {
if (temporary.CurrentItem.type == item.type && temporary.IsAvailable) {
temporary.AddItem (item);
return true;
}
}
}
if (emptySlots > 0) {
PlaceEmpty (item);
}
}
return false;
}
private bool PlaceEmpty(Item item){
if (emptySlots > 0) {
foreach (GameObject slot in allslots) {
Slot temporary = slot.GetComponent<Slot> ();
if (temporary.IsEmpty) {
temporary.AddItem (item);
emptySlots--;
return true;
}
}
}
return false;
}
public void MoveItem(GameObject clicked){
if (from == null) {
if (!clicked.GetComponent<Slot> ().IsEmpty) {
from = clicked.GetComponent<Slot> ();
from.GetComponent<Image> ().color = Color.gray;
hoverObject = (GameObject)Instantiate (iconPrefab);
hoverObject.GetComponent<Image> ().sprite = clicked.GetComponent<Image> ().sprite;
hoverObject.name = "Hover";
RectTransform hoverTransform = hoverObject.GetComponent<RectTransform> ();
RectTransform clickedTransform = clicked.GetComponent<RectTransform> ();
hoverTransform.SetSizeWithCurrentAnchors (RectTransform.Axis.Horizontal, clickedTransform.sizeDelta.x);
hoverTransform.SetSizeWithCurrentAnchors (RectTransform.Axis.Vertical, clickedTransform.sizeDelta.y);
hoverObject.transform.SetParent (GameObject.Find ("Canvas").transform, true);
hoverObject.transform.localScale = from.gameObject.transform.localScale;
}
}
else if (to = null) {
to = clicked.GetComponent<Slot> ();
Destroy (GameObject.Find ("Hover"));
}
if (to != null && from != null) {
Stack<Item> tmpTo = new Stack<Item> (to.Items);
to.AddItems (from.Items);
if (tmpTo.Count == 0) {
from.ClearSlot ();
}
else {
from.AddItems (tmpTo);
}
from.GetComponent<Image> ().color = Color.white;
to = null;
from = null;
hoverObject = null;
}
}
}
The method which is causing the problem is the MoveItem() sadly it is not a nullreference or nullpointer and I simply am out of ideas been strugling with it for a couple of days... Any advice on how to fix this would be helpfull and much welcomed indeed. Thanks in advance!
I haven't taken a long look at your code but right away I saw this issue:
else if (to = null) {
to = clicked.GetComponent<Slot> ();
Destroy (GameObject.Find ("Hover"));
}
This is causing the end location to be set to null. To fix this, change to double equals like so:
else if (to == null) {
to = clicked.GetComponent<Slot> ();
Destroy (GameObject.Find ("Hover"));
}
If this does not solve your problem, let me know and I'll look at your code harder.

Foreach loop not working in android for custom object

I am trying to store the count of number of times an item from a list view is clicked in a custom array list but in my code the quantity of only the first clicked item is increasing and if i click on any other item then it gets added to the list view instead of increasing the quantity. Please tell me if you know any other method to get the count. Thanks
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Flowers item = (Flowers) parent.getAdapter().getItem(position);
if(item == null)
{
Toast.makeText(getBaseContext(),"item is null",Toast.LENGTH_LONG).show();
}else {
selectedItems items = new selectedItems();
items.setName(item.getName());
items.setCategory(item.getCategory());
Counter counter = new Counter();
if(selectedItemsArrayList.isEmpty())
{
counter.CartItemPosition = 0;
counter.MenuItemPosition = position;
counter.quantity = 1;
items.setQuantity(counter.quantity);
count.add(counter);
selectedItemsArrayList.add(items);
}
else
{
for(Counter c : count)
{
if(c.MenuItemPosition == position)
{
c.quantity = c.quantity + 1;
int i = c.CartItemPosition;
selectedItemsArrayList.get(i).setQuantity(c.quantity);
break;
}
else
{
Counter obj = new Counter();
obj.MenuItemPosition = position;
obj.CartItemPosition++;
obj.quantity=1;
count.add(obj);
items.setQuantity(obj.quantity);
selectedItemsArrayList.add(items);
return;
}
}
}
}
}
});
and my Counter class is
public class Counter {
public int CartItemPosition;
public int MenuItemPosition;
public int quantity;}

Draw a GraphicsPath in a PDF

I am trying to create a PDF based on some vector art I have in my C# application. I have two major problems when I try to map the points and types from a GraphicsPath.
Some paths are just plain missing.
When a sub path is an internal boundary I need to indicate that somehow. ie, the circle in the letter d is filled in.
I'm referencing iTextSharp 5.5.2 on NuGet. I'm only using AddString here because I want an easy way to demonstrate creating a complex path in this example. For my need I won't be using a path to place text in the PDF.
using iTextSharp.text;
using iTextSharp.text.pdf;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PdfGen
{
class StackQuestion
{
public static void Main()
{
string filename = #"d:\itext.pdf";
using (var doc = new Document())
using (var fs = new FileStream(filename, FileMode.Create))
{
var writer = PdfWriter.GetInstance(doc, fs);
doc.Open();
writer.DirectContent.SetColorFill(BaseColor.BLACK);
var path = new GraphicsPath(FillMode.Winding);
path.AddString("Hello World", FontFamily.GenericSerif,
(int)FontStyle.Regular, 25f, RectangleF.Empty,
StringFormat.GenericDefault);
AddPath(path, writer.DirectContent);
doc.Close();
}
System.Diagnostics.Process.Start(filename);
}
static void AddPath(GraphicsPath path, PdfContentByte to)
{
var view = to.PdfDocument.PageSize;
path.FillMode = System.Drawing.Drawing2D.FillMode.Winding;
var d = path.PathData;
for (int i = 0; i < d.Points.Length && i < d.Types.Length; i++)
{
var t = (PathPointType)d.Types[i];
var p = Fix(d.Points[i], view);
if (Match(t, PathPointType.Bezier))
{
var p2 = Fix(d.Points[++i], view);
if (d.Types.Length > i + 1 &&
Match((PathPointType)d.Types[i + 1],
PathPointType.Bezier3))
{
var p3 = Fix(d.Points[++i], view);
to.CurveTo(p.X, p.Y, p2.X, p2.Y, p3.X, p3.Y);
}
else
{
to.CurveTo(p.X, p.Y, p2.X, p2.Y);
}
}
if (Match(t, PathPointType.Line))
{
to.LineTo(p.X, p.Y);
}
if (Match(t, PathPointType.CloseSubpath))
{
to.ClosePath();
to.EoFill();
}
if (t == PathPointType.Start)
{
to.NewPath();
to.MoveTo(p.X, p.Y);
}
}
}
static bool Match(PathPointType type, PathPointType match)
{
return (type & match) == match;
}
static System.Drawing.PointF Fix(System.Drawing.PointF pt,
iTextSharp.text.Rectangle view)
{
return new System.Drawing.PointF(pt.X, view.Height - pt.Y);
}
}
}
I'm posting an answer to myself in case anyone else is in need of a simple function to plot out a GraphicsPath in iTextSharp. I had two problems with my sample code in the question:
as mkl pointed out I was trying to fill too often
I failed to notice that PathPointType.Line is a valid mask in PathPointType.Bezier so the code placing a line back to the origin after a curve.
Updatd Code:
using iTextSharp.text;
using iTextSharp.text.pdf;
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
namespace PdfGen
{
class StackQuestion
{
public static void Main()
{
string filename = #"d:\itext.pdf";
using (var doc = new Document())
using (var fs = new FileStream(filename, FileMode.Create))
{
var writer = PdfWriter.GetInstance(doc, fs);
doc.Open();
writer.DirectContent.SetColorFill(BaseColor.BLACK);
var path = new GraphicsPath(FillMode.Winding);
path.AddString("Hello World", FontFamily.GenericSansSerif,
(int)FontStyle.Regular, 90f, PointF.Empty,
StringFormat.GenericDefault);
AddPath(path, writer.DirectContent);
writer.DirectContent.EoFill();
doc.Close();
}
System.Diagnostics.Process.Start(filename);
}
static void AddPath(GraphicsPath path, PdfContentByte to)
{
var view = to.PdfDocument.PageSize;
var d = path.PathData;
to.NewPath();
PointF? start = null;
for (int i = 0; i < d.Points.Length && i < d.Types.Length; i++)
{
var t = (PathPointType)d.Types[i];
var p = Fix(d.Points[i], view);
if (Match(t, PathPointType.Bezier))
{
var p2 = Fix(d.Points[++i], view);
if (d.Types.Length > i + 1 &&
Match((PathPointType)d.Types[i + 1],
PathPointType.Bezier))
{
var p3 = Fix(d.Points[++i], view);
to.CurveTo(p.X, p.Y, p2.X, p2.Y, p3.X, p3.Y);
}
else
{
to.CurveTo(p.X, p.Y, p2.X, p2.Y);
}
}
else if (Match(t, PathPointType.Line))
{
to.LineTo(p.X, p.Y);
}
if (Match(t, PathPointType.CloseSubpath))
{
if (start != null)
to.LineTo(start.Value.X, start.Value.Y);
start = null;
to.ClosePath();
}
if (t == PathPointType.Start)
{
if (start != null)
to.LineTo(start.Value.X, start.Value.Y);
start = p;
to.MoveTo(p.X, p.Y);
}
}
}
static bool Match(PathPointType type, PathPointType match)
{
return (type & match) == match;
}
static System.Drawing.PointF Fix(System.Drawing.PointF pt,
iTextSharp.text.Rectangle view)
{
return new System.Drawing.PointF(pt.X, view.Height - pt.Y);
}
}
}