com/libs/hxswfml [en]

All languages

Differences between version #13827 and #13830

1,674c1
< hxformat example:
< Creating an swf compiler with hxformat ( [[http://code.google.com/p/hxformat/]] )
< 
< <code haxe>
< package;
< import format.swf.Data;
< import format.swf.Reader;
< import format.swf.Writer;
< 
< import format.mp3.Data;
< import format.mp3.Reader;
< import format.mp3.Writer;
< 
< import haxe.io.Bytes;
< import haxe.io.BytesInput;
< import haxe.io.BytesOutput;
< 
< import neko.Lib;
< import neko.io.File;
< 
< /**
< * ...
< * @author jan j.
< */
< 
< class XswfML
< {
< 	public static function main() 
< 	{
< 		var args:Array<String> = neko.Sys.args();
< 		if (args.length!=2)
< 		{
< 			Lib.print("Usage: hXswfML in.xml out.swf");
< 		}
< 		else
< 		{	
< 			if(neko.FileSystem.exists(args[0]))
< 			{
< 				new HXswfML(args[0], args[1]);
< 			}
< 			else
< 			{
< 				throw '!' + args[0] + ' does not exist!';
< 			}
< 		}
< 	}
< }
< 
< class HXswfML
< {
< 	private var fileIn:String;
< 	private var currentTagNode:Xml;
< 	private var swfBytesOutput:BytesOutput;
< 	private var swfWriter:format.swf.Writer;
< 	
< 	public function new(fileIn:String, fileOut:String)
< 	{
< 		//get xml
< 		this.fileIn = fileIn;
< 		var xml:Xml = Xml.parse(File.getContent(fileIn));
< 		
< 		//create SWF
< 		swfBytesOutput = new BytesOutput();
< 		swfWriter = new format.swf.Writer(swfBytesOutput);
< 		createSWF(xml);
< 
< 		//save SWF
< 		var file = File.write(fileOut,true);
< 		file.write(swfBytesOutput.getBytes());
< 		file.close();
< 	}
< 	private function createSWF(xml:Xml):Void
< 	{
< 		var swfNode:Xml = xml.firstElement();
< 		for(tagNode in swfNode.elements())
< 		{
< 			currentTagNode = tagNode;
< 			switch(currentTagNode.nodeName.toLowerCase())
< 			{
< 				case 'header' :  swfWriter.writeHeader(header());
< 				case 'fileattributes' : swfWriter.writeTag(fileAttributes());
< 				case 'setbackgroundcolor' : swfWriter.writeTag(setBackgroundColor());
< 				case 'scriptlimits' : swfWriter.writeTag(scriptLimits());
< 
< 				case 'definebitsjpeg2' : swfWriter.writeTag(defineBitsJPEG2());
< 				case 'defineshape' : swfWriter.writeTag(defineShape());
< 				case 'definesprite' : swfWriter.writeTag(defineSprite());
< 				case 'definebinarydata' : swfWriter.writeTag(defineBinaryData());
< 				case 'definesound' : swfWriter.writeTag(defineSound());
< 				case 'definefont' : swfWriter.writeTag(defineFont());
< 				case 'defineabc' : swfWriter.writeTag(defineABC());
< 				//case 'definevideo'  : swfWriter.writeTag(defineVideo());//TODO (port from FLVParser (AS3).)
< 					
< 				case 'placeobject' : swfWriter.writeTag(placeObject2());
< 				case 'removeobject' : swfWriter.writeTag(removeObject2());
< 				case 'startsound' : swfWriter.writeTag(startSound());
< 					
< 				case 'symbolclass' : swfWriter.writeTag(symbolClass());
< 					
< 				case "framelabel" : swfWriter.writeTag(frameLabel());
< 				case 'showframe' : swfWriter.writeTag(showFrame());
< 				case 'endframe' : swfWriter.writeTag(endFrame());
< 				
< 				default : throw 'Unsupported swf tag : '+ currentTagNode.nodeName;
< 			}
< 		}
< 		swfWriter.writeEnd();
< 	}
< 	
< 	//FILE TAGS
< 	private function header()
< 	{
< 		return {version:_getInt('version',10), compressed:_getBool('compressed',true), width:_getInt('width',800), height:_getInt('height',600), fps:_getInt('fps',30), nframes:_getInt('frameCount',1)};
< 	}
< 	private function fileAttributes()
< 	{
< 		return TSandBox ({
< 							useDirectBlit:_getBool('useDirectBlit',false), 
< 							useGPU:_getBool('useGPU',false), 
< 							hasMetaData:_getBool('hasMetaData',false), 
< 							actionscript3:_getBool('actionscript3',true), 
< 							useNetWork:_getBool('useNetwork',false)
< 							});
< 	}
< 	private function setBackgroundColor()
< 	{
< 		return TBackgroundColor(_getInt('color',0xffffff));
< 	}
< 	private function scriptLimits()
< 	{
< 		var maxRecursion = _getInt('maxRecursionDepth',256);
< 		var timeoutSeconds = _getInt('scriptTimeoutSeconds',15);
< 		return TScriptLimits(maxRecursion,timeoutSeconds);
< 	}
< 	// DEFINE TAGS
< 	private function defineBitsJPEG2()
< 	{
< 		var id = _getInt('id');
< 		var file = _getString('file');
< 		var bytes = neko.io.File.getBytes(file);
< 		return TBitsJPEG(id, JDJPEG2(bytes));
< 	}
< 	private function defineShape()
< 	{
< 		var id = _getInt('id');
< 		var width = _getInt('width');
< 		var height = _getInt('height');
< 		var bounds;
< 		var shapeWithStyle;
< 		if(currentTagNode.exists('bitmapId'))
< 		{
< 			var bitmapId = _getInt('bitmapId');
< 			bounds = {left :0*20, right : width*20, top:0*20,  bottom : height*20}
< 			shapeWithStyle = {
< 								fillStyles:[FSBitmap(1, {scale:{x:0.0, y:0.0}, rotate:{rs0:0.0, rs1:0.0}, translate:{x:0, y:0}}, false, false)],
< 								lineStyles:[], 
< 								shapeRecords:[	SHRChange({	moveTo:{dx:width*20,dy:0*20}, 
< 															fillStyle0:{idx:1}, 
< 															fillStyle1:null, 
< 															lineStyle:null, 
< 															newStyles:null}), 
< 												SHREdge(0, height*20),
< 												SHREdge(-width*20, 0), 
< 												SHREdge(0, -height*20), 
< 												SHREdge(width*20, 0), 
< 												SHREnd ]
< 							}
< 		}
< 		else
< 		{
< 			var width = _getInt('width');
< 			var height = _getInt('height');
< 			var fillColor = _getInt('fillColor',0x000000);
< 			var lineColor = _getInt('lineColor',0x000000);
< 			var lineWidth = _getInt('lineWidth',1);
< 			bounds = {left:0*20, right:width*20, top:0*20, bottom:height*20};
< 			
< 			var fillStyles = [FSSolid({r:(fillColor & 0xff0000) >> 16, g:(fillColor & 0x00ff00) >> 8, b:(fillColor & 0x0000ff)})];
< 			var lineStyles = [{	width:lineWidth*20, data:LSRGB({	r:(lineColor & 0xff0000) >> 16, g:(lineColor & 0x00ff00) >> 8, b:lineColor & 0x0000ff})}];
< 			
< 			shapeWithStyle = {fillStyles:fillStyles,				
< 								lineStyles:lineStyles,
< 								shapeRecords:[	SHRChange({moveTo:{dx:width*20,dy:0*20},
< 															fillStyle0:{idx:1}, 
< 															fillStyle1:null, 
< 															lineStyle:{idx:1}, 
< 															newStyles:null}), 
< 												SHREdge(0, height*20), 
< 												SHREdge(-width*20, 0), 
< 												SHREdge(0, -height*20), 
< 												SHREdge(width*20, 0), 
< 												SHREnd 
< 												]
< 							}	
< 		}
< 		return TShape(id, SHDShape1(bounds, shapeWithStyle));
< 	}
< 	private function defineSprite()
< 	{
< 		var id = _getInt('id');
< 		var frames = _getInt('frameCount',1);
< 		var tags : Array<SWFTag>=new Array();
< 		for(tagNode in currentTagNode.elements())
< 		{
< 			currentTagNode = tagNode;
< 			switch(tagNode.nodeName.toLowerCase())
< 			{
< 				case "placeobject":tags.push(placeObject2());
< 				case "removeobject":tags.push(removeObject2());
< 				case "startsound":tags.push(startSound());
< 				
< 				case "framelabel":tags.push(frameLabel());
< 				case 'showframe': tags.push(showFrame());
< 				case "endframe":tags.push(endFrame());
< 				
< 				default: throw 'Unsupported tag:' + currentTagNode.nodeName  + ' found inside DefineSprite with id: '+ id;
< 			}
< 		}
< 		return TClip(id, frames, tags);
< 	}
< 	private function defineSound()
< 	{
< 		var file = _getString('file');
< 		var mp3FileBytes = neko.io.File.read(file, true);
< 		var mp3Reader = new format.mp3.Reader(mp3FileBytes);
< 
< 		var mp3 = mp3Reader.read();
< 		var mp3Frames:Array<MP3Frame> = mp3.frames;
< 		var mp3Header:MP3Header =mp3Frames[0].header;
< 		
< 		var dataBytesOutput = new BytesOutput();
< 		var mp3Writer = new format.mp3.Writer(dataBytesOutput);
< 		mp3Writer.write(mp3, false);
< 
< 		return TSound( {sid:_getInt('id'),
< 						format : SFMP3,
< 						rate : switch(mp3Header.samplingRate) 
< 								{
< 									case SR_11025: SR11k;
< 									case SR_22050: SR22k;
< 									case SR_44100: SR44k;
< 									default: throw 'Unsupported MP3 SoundRate' + mp3Header.samplingRate + ' in '+ fileIn;
< 								},
< 						is16bit : true,
< 						isStereo : switch(mp3Header.channelMode) 
< 								{
< 									case Stereo: true;
< 									case JointStereo: true;
< 									case DualChannel: true;
< 									case Mono:false;
< 								},
< 						samples : haxe.Int32.ofInt(mp3.sampleCount),
< 						data : SDMp3(0, dataBytesOutput.getBytes())
< 					});
< 	}
< 	private function defineBinaryData()
< 	{
< 		var id = _getInt('id');
< 		var file = _getString('file');
< 		var bytes = neko.io.File.getBytes(file);
< 		return TBinaryData(id, bytes);
< 	}
< 	private function defineFont()
< 	{
< 		var _id = _getInt('id');
< 		var file = _getString('file');
< 		
< 		var swf:Bytes = neko.io.File.getBytes(file);
< 		var swfBytesInput = new BytesInput(swf);
< 		var swfReader = new format.swf.Reader(swfBytesInput);
< 		var header = swfReader.readHeader();
< 		var tags:Array<SWFTag> = swfReader.readTagList();
< 		swfBytesInput.close();
< 		
< 		for (tag in tags)
< 		{
< 			switch (tag)
< 			{
< 				case TFont(id, data): 
< 				return TFont(_id, data);
< 				
< 			default://do nothing
< 			}
< 		}
< 		return throw 'No Font definitions were found inside swf: ' + file;
< 	}
< 	private function defineABC()
< 	{
< 		var remap = _getString('remap', "");
< 		var file = _getString('file');
< 		var swf:Bytes = neko.io.File.getBytes(file);
< 		var swfBytesInput = new BytesInput(swf);
< 		var swfReader = new format.swf.Reader(swfBytesInput);
< 		var header = swfReader.readHeader();
< 		var tags:Array<SWFTag> = swfReader.readTagList();
< 		swfBytesInput.close();
< 		
< 		var _lookupStrings = ["Boot", "Lib", "Type"];//expand? custom classnames?
< 		for (tag in tags)
< 		{
< 			switch (tag)
< 			{
< 				case TActionScript3(data,ctx): 
< 					if(remap=="")
< 					{
< 						return TActionScript3(data, ctx);
< 					}
< 					else
< 					{
< 						var abcReader = new format.abc.Reader(new haxe.io.BytesInput(data));
< 						var abcFile = abcReader.read();
< 						var cpoolStrings = abcFile.strings;
< 						Lib.println('\nChanged following class names in: ' + file + '\n');
< 						Lib.println("-------------------");
< 						for (i in 0...cpoolStrings.length)
< 						{
< 							for ( s in _lookupStrings)
< 							{
< 								var regex =  new EReg('\\b' + s+'\\b', '');
< 								var str = cpoolStrings[i];
< 								if (regex.match(str))
< 								{
< 									Lib.println('<-'+cpoolStrings[i]);
< 									cpoolStrings[i] = regex.replace(str, s + remap);
< 									Lib.println('->'+cpoolStrings[i]);
< 									Lib.println("-------------------");
< 								}
< 							}
< 						}
< 
< 						var abcOutput = new haxe.io.BytesOutput();
< 						format.abc.Writer.write(abcOutput,abcFile);
< 						var abcBytes = abcOutput.getBytes();
< 						return TActionScript3(abcBytes);
< 					}
< 					
< 				default://do nothing
< 			}
< 		}
< 		return throw 'No ABC files were found inside swf: ' + file;
< 	}
< 	
< 	//CONTROL TAGS
< 	private function placeObject2()
< 	{
< 		var id:Int = _getInt('id');
< 		var depth:Int = _getInt('depth');
< 		var x:Int = _getInt('x',0);
< 		var y:Int = _getInt('y',0);
< 		var scaleX:Float = _getFloat('scaleX',1.0);
< 		var scaleY:Float = _getFloat('scaleY',1.0);
< 		var rs0 :Float = _getFloat('rotate0',0.0);
< 		var rs1 :Float = _getFloat('rotate1',0.0);
< 		
< 		var _placeObject:PlaceObject = new PlaceObject();
< 		_placeObject.depth = depth;
< 		_placeObject.move = false;
< 		_placeObject.cid = id;
< 		_placeObject.matrix={scale:{x:scaleX, y:scaleY}, rotate:{rs0:rs0, rs1:rs1}, translate:{x:x*20, y:y*20}};
< 		_placeObject.color=null;
< 		_placeObject.ratio=null;
< 		_placeObject.instanceName = null;
< 		_placeObject.clipDepth=null;
< 		_placeObject.events=null;
< 		_placeObject.filters=null;
< 		_placeObject.blendMode=null;
< 		_placeObject.bitmapCache = false;
< 		
< 		return TPlaceObject2(_placeObject);
< 	}
< 	private function removeObject2()
< 	{
< 		return TRemoveObject2(_getInt('depth'));
< 	}
< 	private function startSound()
< 	{
< 		var id:Int = _getInt('id');
< 		var stop:Bool = _getBool('stop', false);
< 		var loopCount = _getInt('loopCount', 0);
< 		var hasLoops = loopCount==0?false:true;
< 		return TStartSound(id, {syncStop:stop, hasLoops:hasLoops, loopCount:loopCount});
< 	}
< 	private function symbolClass()
< 	{
< 		var cid = _getInt('id');
< 		var className = _getString('class');
< 		var symbols: Array<SymData> = [{cid:cid, className:className}];
< 		return TSymbolClass(symbols);
< 	}
< 	
< 	//FRAME TAGS:
< 	private function frameLabel()
< 	{
< 		var label = _getString('name');
< 		var anchor = _getBool('anchor', false);
< 		return TFrameLabel(label, anchor);
< 	}
< 	private function showFrame()
< 	{
< 		return TShowFrame;
< 	}
< 	private function endFrame()
< 	{
< 		return TEnd;
< 	}	
< 
< 	//helpers for reading xml attributes and converting to correct datatype and/or default value
< 	private function _getInt(att:String, ?defaultValue:Int):Int
< 	{
< 		return currentTagNode.exists(att)? Std.parseInt(currentTagNode.get(att)) : defaultValue;
< 	}
< 	private function _getBool(att:String, ?defaultValue:Bool):Bool
< 	{
< 		return currentTagNode.exists(att)? (currentTagNode.get(att) == 'true' ? true:false) : defaultValue;
< 	}
< 	private function _getFloat(att:String,?defaultValue:Float):Float
< 	{
< 		return currentTagNode.exists(att)? Std.parseFloat(currentTagNode.get(att)) : defaultValue;
< 	}
< 	private function _getString(att:String,?defaultValue:String ):String
< 	{
< 		return currentTagNode.exists(att)? currentTagNode.get(att) : defaultValue;
< 	}
< }
< </code>
< Compile with:
< <code haxe>
< XswfML
< -main XswfML
< -neko hXswfML.n
< </code>
< 
< Example Movie.hx:
< <code haxe>
< package;
< import flash.display.Bitmap;
< import flash.display.MovieClip;
< import flash.display.Sprite;
< import flash.display.Loader;
< import flash.display.PixelSnapping;
< import flash.events.MouseEvent;
< import flash.events.Event;
< import flash.media.Sound;
< import flash.utils.ByteArray;
< import flash.text.Font;
< import flash.text.TextField;
< import flash.text.TextFormat;
< 
< 
< class MyBitmap extends Bitmap{}
< class MyMovieClip extends MovieClip{}
< class MySoundMP3 extends Sound{}
< class MyFontTTF extends Font{}
< class MyVideo extends MovieClip{}
< class MyByteArrayTXT extends ByteArray{}
< class MyByteArraySWF extends ByteArray{}
< 
< class Movie extends Sprite
< {
< 	public static function main()
< 	{
< 		flash.Lib.current.addChild(new Movie());
< 	}
< 	public function new()
< 	{
< 		super();
< 		
< 		//BITMAP
< 		var bmp = new MyBitmap(null,PixelSnapping.AUTO,true);
< 		bmp.x=400;
< 		addChild(bmp);
< 		
< 		
< 		//MOVIECLIP
< 		var mc = new MyMovieClip();
< 		mc.buttonMode=true;
< 		mc.y=200;
< 		addChild(mc);
< 		
< 		//BYTEARRAY (swf)
< 		var loader:Loader=new Loader();
< 		loader.loadBytes(new MyByteArraySWF());
< 		loader.x = 300;
< 		loader.y = 200;
< 		addChild(loader);
< 		
< 		//FONT
< 		var font = new MyFontTTF();
< 		//Font.registerFont(MyFontTTF);
< 		
< 		//BYTEARRAY (txt)
< 		var textFormat = new TextFormat();
< 		textFormat.font = font.fontName;
< 		var tf:TextField=new TextField();
< 		tf.defaultTextFormat= textFormat;
< 		tf.embedFonts=true;
< 		tf.text = '\n'+Std.string(new MyByteArrayTXT());
< 		tf.border=true;
< 		tf.x=tf.width=300;
< 		tf.y=tf.height=300;
< 		addChild(tf);
< 		
< 		//AUDIO:MP3		
< 		var snd = new MySoundMP3();
< 		snd.play();
< 
< 		trace('trace test from Haxe');
< 	}
< }
< </code>
< Compile with:
< <code haxe>
< Movie
< -main Movie
< --flash-use-stage
< -swf9 ../assets/Movie.swf
< -swf-version 10
< </code>
< xample xml:
< <code haxe>
< <?xml version="1.0" ?>
< <swf>
< 	<Header width="800" height="600" fps="30" version="10" compressed="true" frameCount="1" />
< 
< 	<FileAttributes actionscript3="true" useNetwork="false" useDirectBlit="false" useGPU="false" hasMetaData="false" />
< 
< 	<SetBackgroundColor color="0xaaaaaa" />
< 
< 	<ScriptLimits maxRecursionDepth="256" scriptTimeoutSeconds="15" />
< 
< 	<DefineBitsJPEG2 id="1" file="./assets/haxe.jpg" />
< 
< 	<DefineShape id="2" bitmapId="1" width="298" height="99"/>
< 
< 	<PlaceObject id="2" depth="1" x="0" y="0" scaleX="1.0" scaleY="1.0"/>
< 
< 	<DefineShape id="3" fillColor="0xff0000" lineWidth="1" lineColor = "0x000000" width="100" height="100" />
< 	
< 	<DefineSprite id="4" frameCount="1">
< 		<PlaceObject id="3" depth="2" x="0" y="0" scaleX="1.0" scaleY="1.0" />
< 		<ShowFrame />
< 		<EndFrame />
< 		</DefineSprite>
< 	
< 	<PlaceObject id="4" depth="3" x="0" y="0" scaleX="1.0" scaleY="1.0"/>
< 
< 	<DefineBinaryData id="5" file="./assets/data.txt" />
< 
< 	<DefineBinaryData id="6" file="./assets/test.swf" />
< 
< 	<DefineSound id="7" file="./assets/sound.mp3" />
< 
< 	<!--<StartSound id="7" stop="false" loopCount="0" />-->
< 
< 	<DefineFont id="8" file="./assets/zerohour.swf" />
< 
< 	<SymbolClass id="1" class="MyBitmap" />
< 	<SymbolClass id="4" class="MyMovieClip" />
< 	<SymbolClass id="5" class="MyByteArrayTXT" />
< 	<SymbolClass id="6" class="MyByteArraySWF" />
< 	<SymbolClass id="7" class="MySoundMP3" />
< 	<SymbolClass id="8" class="MyFontTTF" />
< 
< 	<DefineABC file="./assets/Movie.swf" />
< 	<SymbolClass id="0" class="flash.Boot" />
< 
< 	<FrameLabel name="frame1" anchor="false" />
< 	<ShowFrame/>
< </swf>
< </code>
< Compile xml to swf with:
< <code haxe>
< neko hXswfML.n swf.xml Movie.swf
< </code>
< 
< 
< Adding a preloader...
< Example Preloader.hx:
< <code haxe>
< package;
< 
< import flash.display.Loader;
< import flash.display.Sprite;
< import flash.events.Event;
< import flash.text.TextField;
< import flash.system.LoaderContext;
< import flash.system.ApplicationDomain;
< import flash.utils.ByteArray;
< 
< import flash.Lib;
< 
< class MyMovie extends ByteArray{}
< 
< class Preloader extends Sprite
< {
< 	var tf:TextField;
< 	var loader:Loader;
< 	var ctx : LoaderContext;
< 
< 	public static function main()
< 	{
< 		Lib.current.addChild(new Preloader());
< 	}
< 	public function new()
< 	{
< 		super();
< 		tf=new TextField();
< 		tf.border=true;
< 		tf.x=200;
< 		tf.y=200;
< 		tf.width=100;
< 		tf.height=20;
< 		addChild(tf);
< 		addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
< 
< 		loader=new Loader();
< 		ctx = new LoaderContext(false, new ApplicationDomain(), null);
< 	}
< 	private function onAddedToStage(event:Event):Void
< 	{
< 		removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
< 		addEventListener(Event.ENTER_FRAME, onEnterFrame);
< 	}
< 	private function onEnterFrame(event:Event):Void
< 	{
< 		var percent = Math.floor((root.loaderInfo.bytesLoaded / root.loaderInfo.bytesTotal)*100);
< 		tf.text= percent +' %';
< 		if(percent==100)
< 		{
< 			removeChild(tf);
< 			removeEventListener(Event.ENTER_FRAME, onEnterFrame);
< 			loader.loadBytes(new MyMovie(), ctx);
< 			addChild(loader);
< 		}
< 	}
< }
< </code>
< Compile with:
< <code haxe>
< Preloader
< -main Preloader
< -swf9 ../assets/Preloader.swf
< </code>
< 
< Example xml:
< <code haxe>
< <?xml version="1.0" ?>
< <swf>
< 	<Header width="800" height="600" fps="30" version="10" compressed="true" frameCount="1" />
< 
< 	<FileAttributes actionscript3="true" useNetwork="false" useDirectBlit="false" useGPU="false" hasMetaData="false" />
< 
< 	<SetBackgroundColor color="0xaaaaaa" />
< 
< 	<ScriptLimits maxRecursionDepth="256" scriptTimeoutSeconds="15" />
< 
< 	<DefineABC file="./assets/Preloader.swf"/>
< 	<SymbolClass id="0" class="flash.Boot"/>
< 
< 	<ShowFrame/>
< 
< 	<DefineBinaryData id="1" file="./assets/Movie.swf" />
< 	<SymbolClass id="1" class="MyMovie" />
< 
< 	<ShowFrame/>
< </swf>
< </code>
< Compile with:
< <code haxe>
< neko hXswfML.n swf.xml index.swf
< </code>
< 
< Download source: [[http://adnez.testingserver.nl/hxswfml/hxswfml_source/]]
< Download examples: [[http://adnez.testingserver.nl/hxswfml/hxswfml_examples/]]
\ No newline at end of file
---
> [[http://code.google.com/p/hxswfml/]]
\ No newline at end of file

	
Ver Date User Action
#13830 2012-04-20 17:32:56 adnez View | Diff
#13829 2012-04-20 17:21:24 adnez Deleted
#13827 2012-04-20 17:15:54 JLM View | Diff

Previous | Next