Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

TextNote.Width Calculate Minimum

16 REPLIES 16
SOLVED
Reply
Message 1 of 17
atiefenbach
3400 Views, 16 Replies

TextNote.Width Calculate Minimum

When creating a new TextNote I'm given the option to give the TextNote a LineWidth Property.  I understand this is the 1:1 scale of the width and the real Width would be that number * the scale factor of the View.  So here's the code I'm using to create the TextNote:

TextNoteType Bold = doc.GetElement(new ElementId(1212838)) as TextNoteType;
			
using (Transaction t = new Transaction(doc, "Create TextNotes"))
	{
	t.Start();
		TextNote txNote = doc.Create.NewTextNote(doc.ActiveView,new XYZ(0,0,0), XYZ.BasisX, XYZ.BasisY,
			0.06, TextAlignFlags.TEF_ALIGN_LEFT | TextAlignFlags.TEF_ALIGN_BOTTOM, "TEST BOLD");
		txNote.TextNoteType = Bold;
	t.Commit();
	}

 

This works fine so long as the String doesn't get longer.  If I need to change the length of the string (which I am) I'd need to give it a new LineWidth, or set the Width Property after I change the TextNoteType.  This is important because I need to create a new TextNote after this bolded String and I want to make sure it's not too far from the last letter of the bolded string I just created.

 

So, is there a way to calculate the minimum Width of a TextNote without it wrapping?

Anthony Tiefenbach
BIM Manager
HuntonBrady Architects
16 REPLIES 16
Message 2 of 17
Scott_Wilson
in reply to: atiefenbach

When I used to do text rendering in game engines, I made use of windows GDI to measure the size of text strings for a particular font. Maybe you could combine this kind of technique with other known information of the text note and view scale to get what you need?

 

I did a quick google and found what looks like the .net way of doing this.

 

http://msdn.microsoft.com/en-us/library/9bt8ty58(v=vs.110).aspx

 

 

Message 3 of 17
atiefenbach
in reply to: atiefenbach

Thanks for the suggestion, that worked out perfect!  I had to do some manupulation / calculations to try and figure out how tall my font in pixels and how to convert the width from pixels to inches, but I got it close enough to do a sentence (and I only need a few words) so code below works pretty good.

 

TextNoteType Bold = doc.GetElement(new ElementId(1212838)) as TextNoteType;  //Arial 3/32" Bold
Font ArialBoldFont = new Font("Arial", 9, FontStyle.Bold);
			
using (Transaction t = new Transaction(doc, "Create TextNote"))
	{
	t.Start();
		string s = "TEST BOLD";
		Size txtBox = TextRenderer.MeasureText(s, ArialBoldFont);
		double newWidth = ((double)txtBox.Width / 86) / 12;
		TextNote txNote;
		txNote = doc.Create.NewTextNote(doc.ActiveView,
			new XYZ(0,0,0), XYZ.BasisX, XYZ.BasisY, 0.1,
			TextAlignFlags.TEF_ALIGN_LEFT | TextAlignFlags.TEF_ALIGN_BOTTOM, s);
		txNote.TextNoteType = ArialBold;
		txNote.Width = newWidth;
	t.Commit();
	}

 

Anthony Tiefenbach
BIM Manager
HuntonBrady Architects
Message 4 of 17
Scott_Wilson
in reply to: atiefenbach

Nicely done. I'm glad it worked.
Message 5 of 17
jeremytammik
in reply to: Scott_Wilson

Hi guys,

 

Thank you Scott for the nice solution.

 

I do not understand how you get from the 3/32" font size defined in the Revit text type to the number 9 that you specify as the em-size in points of the new .NET font.

 

Also, vice versa, MeasureText returns the size in pixels. Why do you divide this by 86? Would that mean 86 pixels per inch? Does that depend on the Revit scale? If it is pixels, it also depends on the device, doesn't it? And the division by 12 transforms that to feet, of course.

 

Can you explain, please?

 

Thank you!

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 6 of 17
jeremytammik
in reply to: Scott_Wilson

Hi again guys,

 

I tried to make use of your solution above and it did not seem to work for me.

 

I looked at ways to improve it, making it work in my context, and would like to hear your opinion on the following expanded experiment:

 

It performs the following steps:

 

  • Pick a text insertion point.
  • Grab the first text note type encountered.
  • Convert the text note type nominal size to points, i.e. 1/72".
  • Create the appropriate .NET font.
  • Measure the text size in pixels in this font.
  • Determine the current display horizontal dots per inch resolution.
  • Use that to convert the size in pixels to inches.
  • Determine the Revit view scale.
  • Scale the width in inches according to that.
  • Add a factor to account for imprecision, currently the experimentally determined factor 1.4.

 

That seems to work for me and produces the following result:

 

text_width_1.png

 

Here is the current version of the external command source code producing that result:

 

  [Transaction( TransactionMode.Manual )]
  class CmdNewTextNote : IExternalCommand
  {
    [DllImport( "user32.dll" )]
    private static extern IntPtr GetDC( IntPtr hwnd );

    [DllImport( "user32.dll" )]
    private static extern Int32 ReleaseDC( IntPtr hwnd );

    /// <summary>
    /// Determine the current display 
    /// horizontal dots per inch.
    /// </summary>
    static float DpiX
    {
      get
      {
        Single xDpi, yDpi;

        IntPtr dc = GetDC( IntPtr.Zero );

        using( Graphics g = Graphics.FromHdc( dc ) )
        {
          xDpi = g.DpiX;
          yDpi = g.DpiY;
        }

        if( ReleaseDC( IntPtr.Zero ) != 0 )
        {
          // GetLastError and handle...
        }
        return xDpi;
      }
    }

    public Result Execute(
      ExternalCommandData commandData,
      ref string message,
      ElementSet elements )
    {
      UIApplication uiapp = commandData.Application;
      UIDocument uidoc = uiapp.ActiveUIDocument;
      Document doc = uidoc.Document;
      View view = doc.ActiveView;

      XYZ p;

      try
      {
        p = uidoc.Selection.PickPoint(
          "Please pick text insertion point" );
      }
      catch( Autodesk.Revit.Exceptions.OperationCanceledException )
      {
        return Result.Cancelled;
      }

      //TextNoteType boldTextType = doc.GetElement(
      //  new ElementId( 1212838 ) ) as TextNoteType; // Arial 3/32" Bold

      // 1 inch = 72 points
      // 3/32" = 72*3/32 points = 

      TextNoteType textType
        = new FilteredElementCollector( doc )
          .OfClass( typeof( TextNoteType ) )
          .FirstElement() as TextNoteType;

      Debug.Print( textType.Name );

      // 6 mm Arial happens to be the first text type found
      // 6 mm = 6 / 25.4 inch = 72 * 6 / 25.4 points = 17 pt

      Font font = new Font( "Arial", 17, FontStyle.Bold );

      using( Transaction t = new Transaction( doc ) )
      {
        t.Start( "Create TextNote" );

        string s = "The quick brown fox jumped over the lazy dog";

        Size txtBox = System.Windows.Forms.TextRenderer
          .MeasureText( s, font );

        double w_inch = txtBox.Width / DpiX;
        double v_scale = view.Scale; // ratio of true model size to paper size

        Debug.Print(
          "Text box width in pixels {0} = {1} inch, scale {2}",
          txtBox.Width, w_inch, v_scale );

        //double newWidth
        //  = ( (double) txtBox.Width / 86 ) / 12;

        double newWidth = w_inch / 12;

        newWidth = newWidth * v_scale;

        newWidth *= 1.4;

        TextNote txNote = doc.Create.NewTextNote(
          doc.ActiveView, p, XYZ.BasisX, XYZ.BasisY,
          0.1, TextAlignFlags.TEF_ALIGN_LEFT
          | TextAlignFlags.TEF_ALIGN_BOTTOM, s );

        txNote.TextNoteType = textType;
        txNote.Width = newWidth;

        t.Commit();
      }
      return Result.Succeeded;
    }
  }

 

I would love to hear your comments on this.

 

I am sure it includes at least lots of room for improvement, and potentially serious errors.

 

I do wonder how your simple solution presented above could work as well as you say it does.

 

Does it really?

 

Thank you!

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 7 of 17
atiefenbach
in reply to: jeremytammik

Jeremy,

 

Fortunately, I have the luxury of only having to deal with one specific font (Arial), one specific height (3/32"), one view scale (1:1), and very short phrases, so my code does work with the crazy math.  I used 9 for the TextRenderer because I was going to use 1/8" text (which, at 72DPI, is 9em), but then changed to 3/32", I just forgot to change the 9 to 6.75, which might be why I had to use 86 for my DPI.  Once I got it to work I was just like, "got it, don't change anything".  🙂

 

I did update my code to 6.75em and changed the division to be by 72.27 DPI, and it still gets pretty close to accurate, see attached PNG.

 

If you can improve the code it would be great, because I do think it would be a great help (at least until we can override characters in a TextBox to be bold like we can with the GUI).

 

I realize the DPI could be different on each user's screen, but wouldn't you be able to always use the same 72DPI to calculate the width?  The width of the TextNote doesn't rely on the DPI of the user's screen, so I feel like the DPI division should be a constant.  Also, the Width of the TextNote will include the BuiltInParameter.LEADER_OFFSET_SHEET (times 2), but the TextRenderer wouldn't.  You'll have to take that into account for the Width too.

Anthony Tiefenbach
BIM Manager
HuntonBrady Architects
Message 8 of 17
atiefenbach
in reply to: atiefenbach


@atiefenbach wrote:

I realize the DPI could be different on each user's screen, but wouldn't you be able to always use the same 72DPI to calculate the width?  The width of the TextNote doesn't rely on the DPI of the user's screen, so I feel like the DPI division should be a constant.


 

So I just tested this theory and you're right, the screen DPI and resolution defintely effects the TextRenderer's width.

 

Maybe you could use the txtBox.Height vs. txNote.BoundingBox Height to determine the height variation and use that as some sort of scale factor for the width?

Anthony Tiefenbach
BIM Manager
HuntonBrady Architects
Message 9 of 17
Scott_Wilson
in reply to: atiefenbach

I had a bit of a play with Jeremy's code to see what I could discover. I generalised it further to read in the size of the text style, width factor and border offset and then calculate the rest on the fly without hard-coding any values. I think the magic scaling factor of 1.4 is actually due to Revit treating fonts as 96 DPI (which I believe is the standard for Windows these days) instead of 72 (1.3333 factor). If I calculate the point size at 96 dpi I am able to discard the final scaling step and still get similar results.

I also played around a little with other common typographic units such as http://www.translatorscafe.com/cafe/EN/units-converter/typography/7-4/pixel_(X)-millimeter/ but nothing else could explain the ~1.4 figure (I also considered that 1.4 is quite close to sqrt(2) which is a scaling factor used for ISO paper sizes, but that didn't really make sense).

 

The text measure method from Windows.Forms returns the size in whole pixels and therefore will be rounded. I have noticed that the variance between actual shortest line length and the calculated value is inconsistent between text sizes (sometimes the calculated value is larger, sometimes it is smaller and sometimes almost spot on), this is probably related to this integer rounding. The Graphics.MeasureString() method that I originally suggested returns the width in fractional pixels without rounding, so is more consistant, but the value returned is always too large and still fluctuates a little depending on text size.

This is I believe caused by non-standard font sizes casuing the text rendering system to perform its own rounding while scaling the font for drawing (and calculating string widths). I've found that if I round my calculated point size up to the nearest 8th of a point (0.125) then multiply that by 10 (then of course dividing the resulting text width by 10) to gain more resolution the end result is much more consistent.

 

I've tested several sizes ranging from 1mm up to 12mm and found that a few sizes just don't behave very well (3mm and 5mm for example) and will calculate a length just a little too short while the rest are fine. To compensate for this I then added 2.5 points to my scaled up font size and it then worked for all sizes.

 

It's a pretty good length approximation that is usually just a little larger than required. There are still a few sizes that don't behave though, so there's more analysis required to pin down a proper generalised solution. I think the inconsistency comes from Revit dealing with font size point rounding and scaling in a different way to the methods we are using, sometimes the rounding is in synch, other times it will diverge. It would be handy to know the actual method Revit uses so that it could be mimmicked.

 

Also, be sure to apply the text type's Width Factor and then add twice the border offset (BuiltInParameter.LEADER_OFFSET_SHEET).

Message 10 of 17
jeremytammik
in reply to: Scott_Wilson

Dear Scott and Atiefenbach,

 

Wow, thank you very much indeed for your valuable additional research.

 

That is more than I could have hoped for.

 

It sounds like this provides the basis for a pretty complete solution.

 

I implemented this partially as follows:

 

  [Transaction( TransactionMode.Manual )]
  class CmdNewTextNote : IExternalCommand
  {
    [DllImport( "user32.dll" )]
    private static extern IntPtr GetDC( IntPtr hwnd );

    [DllImport( "user32.dll" )]
    private static extern Int32 ReleaseDC( IntPtr hwnd );

    /// <summary>
    /// Determine the current display
    /// horizontal dots per inch.
    /// </summary>
    static float DpiX
    {
      get
      {
        Single xDpi, yDpi;

        IntPtr dc = GetDC( IntPtr.Zero );

        using( Graphics g = Graphics.FromHdc( dc ) )
        {
          xDpi = g.DpiX;
          yDpi = g.DpiY;
        }

        if( ReleaseDC( IntPtr.Zero ) != 0 )
        {
          // GetLastError and handle...
        }
        return xDpi;
      }
    }

    public Result Execute(
      ExternalCommandData commandData,
      ref string message,
      ElementSet elements )
    {
      UIApplication uiapp = commandData.Application;
      UIDocument uidoc = uiapp.ActiveUIDocument;
      Document doc = uidoc.Document;
      View view = doc.ActiveView;

      XYZ p;

      try
      {
        p = uidoc.Selection.PickPoint(
          "Please pick text insertion point" );
      }
      catch( Autodesk.Revit.Exceptions.OperationCanceledException )
      {
        return Result.Cancelled;
      }

      TextNoteType textType
        = new FilteredElementCollector( doc )
          .OfClass( typeof( TextNoteType ) )
          .FirstElement() as TextNoteType;

      Debug.Print( "TextNoteType.Name = " + textType.Name );

      // 6 mm Arial happens to be the first text type found
      // 6 mm = 6 / 25.4 inch = 72 * 6 / 25.4 points = 17 pt.
      // Nowadays, Windows does not assume that a point is
      // 1/72", but moved to 1/96" instead.

      float text_type_height_mm = 6;

      float mm_per_inch = 25.4f;

      float points_per_inch = 96; // not 72

      float em_size = points_per_inch
        * ( text_type_height_mm / mm_per_inch );

      em_size += 2.5f;

      Font font = new Font( "Arial", em_size,
        FontStyle.Regular );

      using( Transaction t = new Transaction( doc ) )
      {
        t.Start( "Create TextNote" );

        string s = "The quick brown fox jumps over the lazy dog";

        Size txtBox = System.Windows.Forms.TextRenderer
          .MeasureText( s, font );

        double w_inch = txtBox.Width / DpiX;
        double v_scale = view.Scale; // ratio of true model size to paper size

        Debug.Print(
          "Text box width in pixels {0} = {1} inch, "
          + "view scale = {2}",
          txtBox.Width, w_inch, v_scale );

        double newWidth = w_inch / 12;

        TextNote txNote = doc.Create.NewTextNote(
          doc.ActiveView, p, XYZ.BasisX, XYZ.BasisY,
          newWidth, TextAlignFlags.TEF_ALIGN_LEFT
          | TextAlignFlags.TEF_ALIGN_BOTTOM, s );

        txNote.TextNoteType = textType;

        Debug.Print(
          "NewTextNote lineWidth {0} times view scale "
          + "{1} = {2} generated TextNote.Width {3}",
          Util.RealString( newWidth ),
          Util.RealString( v_scale ),
          Util.RealString( newWidth * v_scale ),
          Util.RealString( txNote.Width ) );

        // This fails.

        //Debug.Assert(
        //  Util.IsEqual( newWidth * v_scale, txNote.Width ),
        //  "expected the NewTextNote lineWidth "
        //  + "argument to determine the resulting "
        //  + "text note width" );

        txNote.Width = newWidth * v_scale;

        t.Commit();
      }
      return Result.Succeeded;
    }
  }

 

Does that match your intentions and explanation, Scott?

 

I do not see where to obtain the "text type's Width Factor" that you mention. Where is that stored?

 

Furthermore, what element hosts the BuiltInParameter.LEADER_OFFSET_SHEET?

 

It sounds like you tested this for varous different fonts and text string samples.

 

Have you written some kind of test framework iterating over the available text note types, matching them with .NET fonts and trying them out with different strings?

 

In order to use this approach in a serious production environemnt, I would try to set up and use such a system and a corresponding unit test to verify that the chosen adjustments really do work correctly in all cases encountered.

 

Thank you!

 

Best regards,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 11 of 17
Scott_Wilson
in reply to: jeremytammik

That looks pretty close Jeremy. One extra thing my solution does is scale the font point size up by 10x then divides the resulting string width by 10 afterwards so that we can get sub-pixel accuracy out of the MeasureText method. I do this upscaling before adding the 2.5points (magic number unfortunately...).

 

The command I developed is based upon your code, but iterates through all available textnote types calculating the font metrics and placing a note for each just below the preceding one. It works fine for each note type in my testing template (ranging from 1 through to 12mm), none of them wrap, but some sizes do have a little more line length than I would like. I'll clean it up a little to remove the references to my framework and post it up.

 

BuiltInParameter.LEADER_OFFSET_SHEET is the Leader/Border offset parameter found in the TextNoteType element. The font height and width factor can also be found there. One thing my code doesn't do just yet is handle font styles such as bold and itallics, It'll only take 5min to throw that in before I post it up though.

 

 

 

 

Message 12 of 17
Scott_Wilson
in reply to: Scott_Wilson

Ok after a little more testing I've decided that using TextRenderer.MeasureText() is not the way to go. The results are too inconsistent and require too much hand-waving to normalise. I believe that TextRenderer.MeasureText() uses standard GDI to calculate the text dimensions while Graphics.MeasureString() makes use of GDI+. I would think that Revit would itself be using GDI+, so it would make sense to use the same system for our calculations.

 

I stripped away all of the scaling and magic numbers and used Graphics.MeasureString(). I found that although the line lengths do end up a little larger than you would like, the results are much more stable (although it isn't perfect, I was able to cause a line wrap with an Arial bold, italic, underlined 16.5mm font). I also stripped out the User32 Interop stuff as it is quite easy to get at the same data using managed code.

 

Anyway here is the full external command class for you to play with, I won't go as far as to say it is a complete solution as there are still a few instances where it will cause a line wrap. I just think that Revit is doing some odd things internally with regard to calculation of line wrapping and we'll never know for sure until it is one day (hopefully) exposed to the API.

 

 

[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
	public class TextNoteTest : IExternalCommand
	{	
		static float GetDpiX()
		{
			Single xDpi, yDpi;

			using(Graphics g = Graphics.FromHwnd(IntPtr.Zero))
			{
				xDpi = g.DpiX;
				yDpi = g.DpiY;
			}
			
			return xDpi;
		}
		
		static Double GetStringWidth(String text, Font font)
		{
			Double textWidth = 0.0;

			using(Graphics g = Graphics.FromHwnd(IntPtr.Zero))
			{
				textWidth = g.MeasureString(text, font).Width;
			}

			return textWidth;
		}
				
		public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData, ref String message, ElementSet elements)
		{
			Result commandResult = Result.Succeeded;

			try
			{
				UIDocument uiDoc = commandData.Application.ActiveUIDocument;
				Document dbDoc = uiDoc.Document;
								
				View view = uiDoc.ActiveGraphicalView;

				XYZ pLoc = XYZ.Zero;

				try
				{
					pLoc = uiDoc.Selection.PickPoint("Please pick text insertion point");
				}
				catch(Autodesk.Revit.Exceptions.OperationCanceledException e)
				{
					Debug.WriteLine("Operation cancelled.");
					message = "Operation cancelled.";

					return Result.Succeeded;
				}

				List<TextNoteType> noteTypeList = new FilteredElementCollector(dbDoc).OfClass(typeof(TextNoteType)).Cast<TextNoteType>().ToList();

				// Sort note types into ascending text size
				noteTypeList.Sort((a, b) => a.get_Parameter(BuiltInParameter.TEXT_SIZE).AsDouble().CompareTo(b.get_Parameter(BuiltInParameter.TEXT_SIZE).AsDouble()));

				foreach(TextNoteType textType in noteTypeList)
				{
					Debug.WriteLine(textType.Name);
										
					Parameter paramTextFont = textType.get_Parameter(BuiltInParameter.TEXT_FONT);
					Parameter paramTextSize = textType.get_Parameter(BuiltInParameter.TEXT_SIZE);
					Parameter paramBorderSize = textType.get_Parameter(BuiltInParameter.LEADER_OFFSET_SHEET);

					Parameter paramTextBold = textType.get_Parameter(BuiltInParameter.TEXT_STYLE_BOLD);
					Parameter paramTextItalic = textType.get_Parameter(BuiltInParameter.TEXT_STYLE_ITALIC);
					Parameter paramTextUnderline = textType.get_Parameter(BuiltInParameter.TEXT_STYLE_UNDERLINE);
					Parameter paramTextWidthScale = textType.get_Parameter(BuiltInParameter.TEXT_WIDTH_SCALE);
					
					String fontName = paramTextFont.AsString();
					Double textHeight = paramTextSize.AsDouble();
					Boolean textBold = paramTextBold.AsInteger() == 1 ? true : false;
					Boolean textItalic = paramTextItalic.AsInteger() == 1 ? true : false;
					Boolean textUnderline = paramTextUnderline.AsInteger() == 1 ? true : false;
					Double textBorder = paramBorderSize.AsDouble();

					Double textWidthScale = paramTextWidthScale.AsDouble();

					FontStyle textStyle = FontStyle.Regular;

					if(textBold)
					{
						textStyle |= FontStyle.Bold;
					}

					if(textItalic)
					{
						textStyle |= FontStyle.Italic;
					}
					
					if(textUnderline)
					{
						textStyle |= FontStyle.Underline;
					}
					
					float fontHeightInch = (float)textHeight * 12.0f;
					float displayDpiX = GetDpiX();

					float fontDpi = 96.0f;
					float pointSize = (float)(textHeight * 12.0 * fontDpi);
					
					Font font = new Font(fontName, pointSize, textStyle);

					int viewScale = view.Scale;

					using(Transaction t = new Transaction(dbDoc))
					{
						t.Start("Test TextNote lineWidth calculation");

						string textString = textType.Name + " (" + fontName + " " + (textHeight * 304.8).ToString("0.##") + "mm, " + textStyle.ToString() + ", " + (textWidthScale * 100.0).ToString("0.##") + "%): The quick brown fox jumps over the lazy dog.";

						Double stringWidthPx = GetStringWidth(textString, font);
						
						double stringWidthIn = stringWidthPx / displayDpiX;

						Debug.WriteLine("String Width in pixels: " + stringWidthPx.ToString("F3"));
						Debug.WriteLine((stringWidthIn * 25.4 * viewScale).ToString("F3") + " mm at 1:" + viewScale.ToString());

						double stringWidthFt = stringWidthIn / 12.0;

						double lineWidth = ((stringWidthFt * textWidthScale) + (textBorder * 2.0)) * viewScale;

						TextNote textNote = dbDoc.Create.NewTextNote(view, pLoc, XYZ.BasisX, XYZ.BasisY, 0.001, TextAlignFlags.TEF_ALIGN_LEFT | TextAlignFlags.TEF_ALIGN_TOP, textString);

						textNote.TextNoteType = textType;
						textNote.Width = lineWidth;

						t.Commit();
					}

					pLoc += view.UpDirection.Multiply((textHeight + (5.0 / 304.8)) * viewScale).Negate(); // place next text note below this one with 5mm gap
				}

			}

			catch(Autodesk.Revit.Exceptions.ExternalApplicationException e)
			{
				message = e.Message;
				Debug.WriteLine("Exception Encountered (Application)\n" + e.Message + "\nStack Trace: " + e.StackTrace);

				commandResult = Result.Failed;
			}
			catch(Exception e)
			{
				message = e.Message;
				Debug.WriteLine("Exception Encountered (General)\n" + e.Message + "\nStack Trace: " + e.StackTrace);

				commandResult = Result.Failed;
			}

			return commandResult;
		}
	}

 

 

 

 

Message 13 of 17
jeremytammik
in reply to: Scott_Wilson

Dear Scott,

 

Your new implementation based on Graphics.MeasureString instead of TextRenderer.MeasureText works perfectly for me:

 

text_width_4_iterate.png

 

I added this to The Building Coder samples as CmdNewTextNote and published on GitHub:

 

https://github.com/jeremytammik/the_building_coder_samples

 

Your current code is tagged as release 2015.0.114.2:

 

https://github.com/jeremytammik/the_building_coder_samples/releases/tag/2015.0.114.2

 

Thank you!

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 14 of 17
Scott_Wilson
in reply to: jeremytammik

Excellent, I'm glad you got good results. But test it out with various combinations of textnote styles with width factors like 0.75 and you'll see that it does sometimes fail at larger text heights (> 16mm). But overall I'm happy with it and will be adding it to my framework as a basis for several commands I've been meaning to add for awhile now.

Message 15 of 17
jeremytammik
in reply to: Scott_Wilson

Hi Scott,

 

Great, thank you.

 

I published a summary of this interesting discussion on The Building Coder now:

http://thebuildingcoder.typepad.com/blog/2014/10/new-text-note-and-text-width-calculation.html

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 16 of 17
atiefenbach
in reply to: Scott_Wilson

Scott and Jeremy,

 

Thank you so much for this discussion.  I thought the solution would have been really quick and I was just missing something.  It's so great to have more experienced people on here to help people like me.  This code will allow me to do a string of non-bold text, then bold text, then non-bold text on the same line and have it spaced properly.

 

Thanks again!!

Anthony Tiefenbach
BIM Manager
HuntonBrady Architects
Message 17 of 17
drdanielfc
in reply to: atiefenbach

Sorry to revive an old discussion, but I wanted to clear some things up so that future readers of this thread aren't mislead.

 

First of all, the Revit DPI is 72, not 96. I confirmed this experimentally as follows:

 

  1. Inserting an image of width 332px into a revit sheet of scale 48.
  2. The resulting image width was 18.4444444444', so the conversion factor is (332*48)/18.4444444444 = 864
  3. Dividing this result by 12in/ft gives us 72DPI.

 

Regarding the original task at hand, placing 2 strings next to eachother, I took a slightly different approach by calculating the *exact* width of the string (in contrast to the minimum width of the note, which is notably larger than just the text), setting the note width to 110% of that (so it doesn't wrap), but then placing the next note such that it is right at the end of the exact width of the string. Thus, there is some note element overlap but the text itself lines up perfectly without an awkwardly large space afterward, as is the case with the current solution.

 

Here is the code I used to calculate the exact string width:

        private const float REVIT_FONT_DPI = 72.0f;  // NOT 96.0f;

public static double stringWidth(string textString, TextNoteType textType, bool includeBorder, int viewScale) { return stringWidthInternal(textString, textType, includeBorder, viewScale); } private static double stringWidthInternal(string textString, TextNoteType textType, bool includeBorder, int viewScale) { double stringWidthPx = systemStringWidth(textString, fontFrom(textType)); double stringWidthIn = stringWidthPx / REVIT_FONT_DPI; double stringWidthFt = stringWidthIn / 12.0; Parameter paramTextWidthScale = textType.get_Parameter(BuiltInParameter.TEXT_WIDTH_SCALE); Parameter paramBorderSize = textType.get_Parameter(BuiltInParameter.LEADER_OFFSET_SHEET); double textBorder = paramBorderSize.AsDouble(); double textWidthScale = paramTextWidthScale.AsDouble(); if (!includeBorder) textBorder = 0; double lineWidth = ((stringWidthFt * textWidthScale) + (textBorder * 2.0)) * viewScale; return lineWidth; } private static double systemStringWidth(string text, Font font) { using (Graphics g = Graphics.FromHwnd(IntPtr.Zero)) { g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixel; StringFormat format = new System.Drawing.StringFormat(); format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces; RectangleF rect = new RectangleF(0, 0, float.MaxValue / 2.0f, float.MaxValue / 2.0f); CharacterRange[] ranges = { new CharacterRange(0, text.Length) }; Region[] regions = new Region[1]; format.SetMeasurableCharacterRanges(ranges); regions = g.MeasureCharacterRanges(text, font, rect, format); if (regions.Length < 1) return 0; rect = regions[0].GetBounds(g); return rect.Width; } }

 I would, however, like to thank you all for getting me on the right track.

 

Untitled.png

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Rail Community