Length of a string in pixels - actionscript-3

I'm populating a dropDownList with arrayCollection of strings. I want the width of the drop down list control to match with the size (in pixels) of the longest string in the array collection. The problem I'm facing is: the font width of the strings in the collection are different e.g. 'W' looks wider than 'l'. So I estimated the width of a character to be 8 pixels but that's not pretty neat. If a string that has many 'W' and 'M' is encountered the estimation is wrong. So I want precise pixel width of strings. How can i get the exact length of a string in pixels??
My solution that estimates all character to be 8 pixels wide is given below:
public function populateDropDownList():void{
var array:Array = new Array("o","two","three four five six seven eight wwww");
var sampleArrayCollection:ArrayCollection = new ArrayCollection(array);
var customDropDownList:DropDownList = new DropDownList();
customDropDownList.dataProvider=sampleArrayCollection;
customDropDownList.prompt="Select ...";
customDropDownList.labelField="Answer Options:";
//calculating the max width
var componentWidth=10; //default
for each(var answerText in array){
Alert.show("Txt size: "+ answerText.length + " pixels: " + answerText.length*9);
if(answerText.length * 8 > componentWidth){
componentWidth=answerText.length * 8;
}
}
customDropDownList.width=componentWidth;
answers.addChild(customDropDownList);
}
Any idea or solution is highly valued.
Thanks

To get a more accurate measurement, you can populate a TextField with the string, then measure the width of that TextField's text.
Code:
function measureString(str:String, format:TextFormat):Rectangle {
var textField:TextField = new TextField();
textField.defaultTextFormat = format;
textField.text = str;
return new Rectangle(0, 0, textField.textWidth, textField.textHeight);
}
Usage:
var format:TextFormat = new TextFormat();
format.font = "Times New Roman";
format.size = 16;
var strings:Array = [ "a", "giraffe", "foo", "!" ];
var calculatedWidth:Number = 50; // Set this to minimum width to start with
for each (var str:String in strings) {
var stringWidth:Number = measureString(str, format).width;
if (stringWidth > calculatedWidth) {
calculatedWidth = stringWidth;
}
}
trace(calculatedWidth);

I don't have edit priv on other people's post's so I'm posting this as a separate answer but credit should go to Cameron if this works:
function measureString(str:String, format:TextFormat):Rectangle {
var textField:TextField = new TextField();
textField.autoSize = TextFieldAutoSize.LEFT;
textField.defaultTextFormat = format;
textField.text = str;
return new Rectangle(0, 0, textField.textWidth, textField.textHeight);
}
If I see that it does and his is edited I'd delete this one for cleanliness.
Sorry for the garbage post initially was trying to answer the question just did so erroneously... anyhow tested this one and it appears to work. I did this in Flex but you should be able to just use the AS3 part no problem I just wrapped up the textfield in a UIComponent to get it on stage but using the autosize seems to work fine:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.core.UIComponent;
protected function textinput1_changeHandler(event:TextOperationEvent):void
{
// TODO Auto-generated method stub
var rect:Rectangle = measureString(event.target.text);
holderBox.graphics.clear();
holderBox.graphics.beginFill(0xFF0000);
holderBox.graphics.drawRect(rect.x,rect.y,rect.width,rect.height);
}
private function measureString(str:String):Rectangle {
var textField:TextField = new TextField();
textField.autoSize = TextFieldAutoSize.LEFT;
textField.text = str;
var uiComponent:UIComponent = new UIComponent();
uiComponent.addChild(textField);
holderBox.addChild(uiComponent);
return new Rectangle(0, 0, textField.textWidth, textField.textHeight);
}
]]>
</fx:Script>
<mx:VBox>
<s:TextInput text="" change="textinput1_changeHandler(event)"/>
<mx:Box id="holderBox"/>
</mx:VBox>
</s:Application>

This is a more polished version of some of the above code. Accounting for linebreaks (html break and \n) and nullifying the created Textfield object with some other optimizations. Hope this is helpful.
function measureString(str:String, font:String="Times New Roman", size:Number=12):Rectangle
{
var textField:TextField = new TextField();
textField.defaultTextFormat = new TextFormat( font, size );
textField.border = true;
textField.multiline = true;
textField.autoSize = TextFieldAutoSize.LEFT;
textField.htmlText = str;
// Grab with and height before nullifying Textfield.
var w:Number = textField.textWidth;
var h:Number = textField.textHeight;
//addChild( textField );// This will add the Textfield to the stage so you can visibly see it.
//if( contains( textField ) ) removeChild( textField );// If it exists onstage, remove it.
textField = null;//nullify it to make it available for garbage collection.
return new Rectangle(0, 0, w, h);
}
var str:String = "Jeremy is a good boy.<br>He also has a red bike. \nSometimes Jeremy rides his bike to the store to buy bread for his family.<br>He likes wholewheat.";
trace( measureString( str, "Times New Roman", 25 ).width );
If you prefer this in a class, check it out in my GIT framework:
https://github.com/charlesclements/as3-tools/blob/master/net/charlesclements/util/text/TextUtil.as
AS3-tools:https://github.com/charlesclements/as3-tools
Also, our Flash/JS brother Jack Doyle # GreenSock has some handy stuff to do with manipulating text. Well worth it to check it out:http://www.greensock.com/splittext/

Here is a routine I wrote for Java. I guess you can convert it to Javascript or whatever.
It is not exact, but gives an approximate answer. It assumes that a space is about 200 wide, and M and W is about 1000 wide. Obviously you'll need to scale that based on your particular font size.
static int picaSize(String s)
{
// the following characters are sorted by width in Arial font
String lookup = " .:,;'^`!|jl/\\i-()JfIt[]?{}sr*a\"ce_gFzLxkP+0123456789<=>~qvy$SbduEphonTBCXY#VRKZN%GUAHD#OQ&wmMW";
int result = 0;
for (int i = 0; i < s.length(); ++i)
{
int c = lookup.indexOf(s.charAt(i));
result += (c < 0 ? 60 : c) * 7 + 200;
}
return result;
}

Related

Loading images using the URLRequest

recently started to learn ActionScript 3 and already have questions.
question remains the same: I'm uploading a picture using the object Loader.load (URLRequest). Loaded and displayed a picture normally. But it is impossible to read the values of attributes of the image height and width, instead issued zero. That is, do this:
var loader:Loader=new Loader();
var urlR:URLRequest=new URLRequest("Image.jpg");
public function main()
{
loader.load(urlR);
var h:Number = loader.height;// here instead of the width of the image of h is set to 0
// And if you do like this:
DrawText(loader.height.toString(10), 50, 50); // Function which draws the text as defined below
// 256 is displayed, as necessary
}
private function DrawText(text:String, x:int, y:int):void
{
var txt:TextField = new TextField();
txt.autoSize = TextFieldAutoSize.LEFT;
txt.background = true;
txt.border = true;
txt.backgroundColor = 0xff000000;
var tFor:TextFormat = new TextFormat();
tFor.font = "Charlemagne Std";
tFor.color = 0xff00ff00;
tFor.size = 20;
txt.x = x;
txt.y = y;
txt.text = text;
txt.setTextFormat(tFor);
addChild(txt);
}
Maybe attribute values must be obtained through the special features, but in the book K.Muka "ActionScript 3 for fash" says that it is necessary to do so. Please help me to solve this. Thanks in advance.
Well it's simple.
Flash is focused on the Internet, hence such problems.
If you wrote loader.load (urlR); it does not mean loaded. Accordingly, prior to the event confirming the end of loading, in loadare Null
if, instead of certain functions would be more code that would perhaps tripped your approach.
Yeah plus still highly dependent on the size of the file that you read.
Well, in general it all lyrics. Listen event on loader.contentLoaderInfo.addEventListener (Event.INIT, _onEvent), onEvent and read properties.
You need to wait for your image to load to be able to get values out of it.
Attach an eventListener to your URLLoader.
var urlR:URLRequest = new URLRequest("Image.jpg");
loader.load(urlR);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loader_complete);
function loader_complete(e:Event): void {
// Here you can get the height and width values etc.
var target_mc:Loader = evt.currentTarget.loader as Loader;
// target_mc.height , target_mc.width
}
Loader

Remove randomly generated sprites with Mouse.Event

I am making a game for my class where different chemical elements are generated as sprites at the top of the screen and then fall down. Different types are made and I want students to mouse over specific types depending on where they are in the game.
My question is how to write the function to remove them when they are correctly selected? I've tried a lot of different ways but am having a lot of trouble. An example of the code that I wrote to make each element is below and then I have a separate function to move down all of the sprites created.
var spriteArray:Array = new Array();
var halogenArray:Array = new Array("F", "Cl", "Br", "I");
var rndnum:Number = Math.random();
//Halogens
if (rndnum < 0.05)
{
var halo:Sprite = new Sprite();
halo.graphics.beginFill(0x00FF00, 1);
halo.graphics.drawCircle(7.5, 7.5, 15);
halo.graphics.endFill();
halo.addEventListener(MouseEvent.MOUSE_OVER, removeElement);
halo.x = Math.random()*500 + 50;
halo.y = -18;
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
textField.height = 30;
textField.x = (15 - textField.textWidth)/2; // center it horizontally
textField.y = (15 - textField.textHeight)/2; // center it vertically
halo.addChild(textField);
spriteArray.push(halo);
addChild(halo);
}
At what point are you struggling?
I am assuming it is in determining the types of the halogens.
In your remove function I assume you have the desired type already figured out, you would then compare it to
element.getChildAt(0).text
and you would get the element by either looping across every element in the spriteArray, or using the mouseEvent's target
My suggestion is to use a halogen Class to contain the grapics & textfield, and a vector to hold the objects. It would then be easier to get the type rather than searching the anonymous children of the sprite.
I believe you are looking for something like this:
//give your textfields a name, it isn't totally necessary as we can do getChildAt(0)
//but it's more readable, and if you decide to add more children before you
//add the text field, then this will still work
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
...
textField.name = "haloTx"; //for tracking later
//assuming you have some variable set to the correct answer
var correctAnswer:String = "F";
function removeElement( e:MouseEvent ):void {
var element:TextField = ( e.target as Sprite ).getChildByName( "haloTx" );
//if we have the correct element, remove from parent and list
if ( element && element.text == correctAnswer ) {
var index:int = spriteArray.indexOf( e.target as Sprite );
removeChild( spriteArray.splice( index, 1 )[0] );
}
}
Although #VBCPP is right, doing that in a separate class is definitely the best way organizationally. Which might look something like:
class ElementSprite extends Sprite {
public var textField:TextField;
//pass shapeArgs as string, so say a circle at x=7.5, y=7.5, and radius=15 -- shapeArgs = "7.5, 7.5, 15"
public function ElementSprite( element:String, drawShape:String="Circle", shapeArgs:String="7.5, 7.5, 15", fillColor:uint=0x00FF00 ) {
//set textfield properties etc. or textFormat
textField = new TextField();
textField.text = element;
addChild( textField );
//if you passed some arguments to draw our shape
if ( shapeArgs != "" ) {
graphics.beginFill( fillColor );
graphics[ "draw" + drawShape ].apply( this, shapeArgs.split( "," ) );
}
}
public function get currentElement():String { return textField.text }
}
Then you would use it like so in your if statement if (rndnum < 0.05):
var elementSprite:ElementSprite = new ElementSprite( "A" );
//elementSprite.x = set your x;
//elementSprite.y = set your y;
addChild(elementSprite);
That would be replacing all your current code in that if statement. This is all a working example, if you have an questions feel free to comment.

Aligning textfields close together produces inaccurate spacing appearance

I've added Arial as an embedded font to my font format, which I've added to numerous textfields that must be aligned accurately next to each other to create the appearance of spaces. They are aligned approximately 1 - 2 pixels next to each other. The problem is that they look inaccurately spaced when viewed at the normal stage size, but when I zoom in the text looks fine.
I've tried messing around and adding advanced antialiasing, but nothing is working. Perhaps Arial doesn't work at small sizes? I know not setting a font it produces the results I want, however it's not the font I want to use; it must be using Times?
Any ideas?
var myFont = new Font1();
format1 = new TextFormat();
format1.color = 0x000000;
format1.size = 16;
format1.font = myFont.fontName;
EDIT:
Essentially I'm splitting up each character into its own textfield, so I can manipulate each character and animate it. But to do so I need to space the characters as though it was one textfield.
private var bmd:BitmapData; // bitmapdata to draw textField in;
private function getExactMetrics(textField:TextField):Object
{
// copy textField to bitmap
bmd = new BitmapData(textField.width,textField.height,true,0x000000);
bmd.draw(textField);
// loop through pixels of bitmap data and store the x location of pixels found.
var foundx:Array = [];
for (var nx:int = 0; nx < bmd.width;nx++)
{
for (var ny:int = 0; ny < bmd.height; ny++)
{
var px:uint = bmd.getPixel32(nx, ny);
if (px != 0)
{
foundx.push(nx);
break;
}
}
}
// get the values for the metrics
var startX:int = foundx[0]; // first pixel found representing the x start of the text in pixels
var endX:int = foundx[foundx.length-1]; t
var realWidth:Number = endX - startX;
// clear the array with found x positions
foundx = [];
// wrap values in object
var metrics:Object = { };
metrics.width = realWidth;
metrics.x = startX;
// clear bitmapdata
bmd = null;
return metrics; // retrurn metric object;
}
}
Remember that fonts have kerning which means that letters are not just put one next to each other, but spacing depends on settings of the font. For example in word 'To' 'o' is slightly below the 'top beam' of letter 'T'.
If you just put letter next to each other it will look bad, you have to consider kerning.
TextField class has a getCharBoundaries method which can be used to get accurate char position. Not sure if it will work with htmlText, though.
To make it simpler you could use a third party library. There is a great SplitTextField from Greensock (but it's not free) or Textanim (open source).
Fonts such as Verdana might have greater readability at smaller sizes; however, it sounds like you are experiencing text metrics issues.
In this example, TextLineMetrics are used to accurately measure each text field.
Result:
Code:
package
{
import flash.display.Sprite;
import flash.text.AntiAliasType;
import flash.text.Font;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.text.TextLineMetrics;
[SWF(percentWidth = 100, percentHeight = 100, backgroundColor = 0xefefef, frameRate = 30)]
public class X extends Sprite
{
public function X()
{
super();
// collection of words
var words:Array = [ "These", "are", "the", "words", "to", "be", "placed" ];
// define a text format
var textFormat:TextFormat = new TextFormat();
textFormat.font = "Arial";
textFormat.bold = false;
textFormat.size = 8;
textFormat.color = 0x0;
textFormat.align = TextFormatAlign.LEFT;
// destination x coordinate for next word text field
var dx:Number = 0;
// for every word
for each (var word:String in words)
{
// create a text field
var textField:TextField = new TextField();
textField.setTextFormat(textFormat);
textField.defaultTextFormat = textFormat;
textField.autoSize = TextFieldAutoSize.LEFT;
textField.antiAliasType = AntiAliasType.ADVANCED;
// set text and place the text field
textField.text = word;
textField.x = dx;
textField.y = 20;
addChild(textField);
// measure the text field for accurate width
var textLineMetrics:TextLineMetrics = textField.getLineMetrics(0);
dx += textLineMetrics.width + 2;
}
}
}
}

AS3 multiple textfields made easy

I am working on a Results page for my game as well as upgrade page and looking for an easy way to do many textfields. I have a format for my text that takes care of font, colour, and size, but looking for an easy way to do the width and height of textfields to increase all at the same time.
I have been informed about a "with" keyword that may work but do not understand how to implement this within my program and essentially want to shorten my results class if possible.
Thank you,
The best way would be to create a custom function for generating textfield.
The example can be found in the livedocs itself.
So something like the following should suffice :
private function createCustomTextField(x:Number, y:Number, width:Number, height:Number):TextField {
var result:TextField = new TextField();
result.x = x;
result.y = y;
result.width = width;
result.height = height;
return result;
}
You may also set a default value to each attribute in the function.
private function createCustomTextField ( x:Number= <Default Value>, ...
Use it to add a textfield inside the container form.
var container:Sprite = new Sprite(); // New form container
container.addChild(createCustomTextField (20,20,50,50)); // Text Filed 1
container.addChild(createCustomTextField (20,50,50,50)); // Text Filed 2
addChild(container); // Add to current class
You may want to modify the function to accept a name so that each variable can be accessed later.
As far as I am aware, you can't use a "with" keyword to target multiple objects. Here's the documentation for it: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/statements.html#with
What I've done in the past is just make an array of all the targets, and then write a loop to apply properties to each:
var textFormat:TextFormat = generateMyCustomTextFormat();
var textField1:TextField = new TextField();
var textField2:TextField = new TextField();
//...
var textField3:TextField = new TextField();
var targets:Array = [textField1, textField2, textField3];
for(var i:int=0; i<targets.length; i++)
{
targets[i].defaultTextFormat = textFormat;
targets[i].width = 250;
//...
}

In Flex, how do I create, populate, and display a bitmap?

Starting from a totally empty MXML application:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" >
</mx:Application>
What is the script that will:
create a bitmap (should this be a BitmapData object?)
programatically populate it with data (e.g. draw a circle) (is there any more efficient way to do this than repeatedly calling BitmapData.SetPixel?)
render it within the application (e.g. centered in the middle) (how DO I get a bitmap onto the screen? Online Flash samples show calling myBitmap=new Bitmap(myBitmapData) and then myBitmap.AddChild, but this throws an error in Flex).
??
NOTE: The goal is not just to get a circle on the screen, it's to populate a Bitmap (or BitmapData? Or ???) object using SetPixel (or ???) and then get its contents onto the screen, as one would need to do in an image processing or visual effects application.
You have a couple of options. The first is to examine the Degrafa framework which has some really incredible drawing tools, otherwise you need to add a script block and use the AS3 drawing api in a function (in this case probably called on creationComplete:
private function handleCreationComplete():void
{
var component:UIComponent = new UIComponent(); //needed to add to the flex display list
var myCircle:Sprite = new Sprite();
myCircle.graphics.beginFill(0xFFFFFF,1)
myCircle.graphics.drawCircle(100,100,50);
component.addChild(myCircle);
this.addChild(component);
}
This won't center the circle, but you can figure that bit out.
you can use this function to actually get the bitmap out of the above UIComponent:
private function getBitmapData( target : UIComponent ) : BitmapData
{
var bd : BitmapData = new BitmapData( target.width, target.height );
var m : Matrix = new Matrix();
bd.draw( target, m );
return bd;
}
from here.
Here's a complete, working example I was able to create based on Joel Hooks' suggestions:
MXML FILE (ards.mxml):
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
creationComplete="onCreationComplete()">
<mx:Script source="ards_script.as" />
</mx:Application>
SCRIPT FILE (ards_script.as):
import mx.core.UIComponent;
private function onCreationComplete():void
{
// create a UIComponent and add it to the Flex display list
var component:UIComponent = new UIComponent();
component.width = this.width;
component.height = this.height;
component.x = 0;
component.y = 0;
this.addChild(component);
// create a BitmapData object (basically an array of pixels) the same size as the screen
var bd : BitmapData = new BitmapData( component.width, component.height );
// draw a green circle in the bitmap data
var xCenter:int = component.width/2;
var yCenter:int = component.height/2;
var r:int = Math.min(xCenter,yCenter);
var rSquare:int = r*r;
var color:Number = 0x00ff00;
for( var i:int=0; i<component.width; i++ )
{
for( var j:int=0; j<component.width; j++ )
{
var dX:int = i - xCenter;
var dY:int = j - yCenter;
var q:int = dX*dX + dY*dY;
if( q < rSquare )
{
bd.setPixel( i, j, color );
}
}
}
// create a bitmap based on the data, and add it as a child to the UIComponent to be displayed
// (if you try to add it directly to the Application, you get a runtime error)
var bt:Bitmap = new Bitmap(bd);
component.addChild(bt);
}