#region MIT License /*Copyright (c) 2012 Robert Rouhani SharpFont based on Tao.FreeType, Copyright (c) 2003-2007 Tao Framework Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ #endregion using System; using System.Runtime.InteropServices; using SharpFont.Internal; namespace SharpFont { /// /// This structure is used to describe an outline to the scan-line converter. /// /// /// The B/W rasterizer only checks bit 2 in the ‘tags’ array for the first point of each contour. The drop-out mode /// as given with , , and /// in ‘flags’ is then overridden. /// public sealed class Outline : IDisposable { #region Fields private bool disposed; private bool duplicate; private IntPtr reference; private OutlineRec rec; private Library parentLibrary; private Memory parentMemory; #endregion #region Constructor /// /// Initializes a new instance of the class. /// /// /// The reason why this function takes a ‘library’ parameter is simply to use the library's memory allocator. /// /// /// A handle to the library object from where the outline is allocated. Note however that the new outline will /// not necessarily be freed, when destroying the library, by . /// /// The maximum number of points within the outline. /// The maximum number of contours within the outline. [CLSCompliant(false)] public Outline(Library library, uint pointsCount, int contoursCount) { IntPtr reference; Error err = FT.FT_Outline_New(library.Reference, pointsCount, contoursCount, out reference); if (err != Error.Ok) throw new FreeTypeException(err); parentLibrary = library; parentLibrary.AddChildOutline(this); } /// /// Initializes a new instance of the class. /// /// A handle to the memory object from where the outline is allocated. /// The maximum number of points within the outline. /// The maximum number of contours within the outline. [CLSCompliant(false)] public Outline(Memory memory, uint pointsCount, int contoursCount) { IntPtr reference; Error err = FT.FT_Outline_New_Internal(memory.Reference, pointsCount, contoursCount, out reference); if (err != Error.Ok) throw new FreeTypeException(err); parentMemory = memory; //TODO Should Memory be disposable as well? } internal Outline(IntPtr reference, Library parent) { Reference = reference; parentLibrary = parent; parentLibrary.AddChildOutline(this); } internal Outline(OutlineRec outlineInt) { this.rec = outlineInt; duplicate = true; } /// /// Finalizes an instance of the class. /// ~Outline() { Dispose(false); } #endregion #region Properties /// /// Gets a value indicating whether the has been disposed. /// public bool IsDisposed { get { return disposed; } } /// /// Gets the number of contours in the outline. /// public short ContoursCount { get { if (disposed) throw new ObjectDisposedException("ContoursCount", "Cannot access a disposed object."); return rec.n_contours; } } /// /// Gets the number of points in the outline. /// public short PointsCount { get { if (disposed) throw new ObjectDisposedException("PointsCount", "Cannot access a disposed object."); return rec.n_points; } } /// /// Gets a pointer to an array of ‘PointsCount’ elements, giving the outline's point /// coordinates. /// public FTVector[] Points { get { if (disposed) throw new ObjectDisposedException("Points", "Cannot access a disposed object."); int count = PointsCount; if (count == 0) return null; FTVector[] points = new FTVector[count]; IntPtr array = rec.points; for (int i = 0; i < count; i++) { points[i] = new FTVector(new IntPtr(array.ToInt64() + IntPtr.Size * i)); } return points; } } /// /// Gets a pointer to an array of ‘PointsCount’ chars, giving each outline point's type. /// /// If bit 0 is unset, the point is ‘off’ the curve, i.e., a Bézier control point, while it is ‘on’ if set. /// /// Bit 1 is meaningful for ‘off’ points only. If set, it indicates a third-order Bézier arc control point; and /// a second-order control point if unset. /// /// If bit 2 is set, bits 5-7 contain the drop-out mode (as defined in the OpenType specification; the value is /// the same as the argument to the SCANMODE instruction). /// /// Bits 3 and 4 are reserved for internal purposes. /// public byte[] Tags { get { if (disposed) throw new ObjectDisposedException("Tags", "Cannot access a disposed object."); int count = PointsCount; if (count == 0) return null; byte[] tags = new byte[count]; IntPtr array = rec.tags; for (int i = 0; i < count; i++) { tags[i] = Marshal.ReadByte(array, IntPtr.Size * i); } return tags; } } /// /// Gets an array of ‘ContoursCount’ shorts, giving the end point of each contour within the outline. For /// example, the first contour is defined by the points ‘0’ to ‘Contours[0]’, the second one is defined by the /// points ‘Contours[0]+1’ to ‘Contours[1]’, etc. /// public short[] Contours { get { if (disposed) throw new ObjectDisposedException("Contours", "Cannot access a disposed object."); int count = ContoursCount; if (count == 0) return null; short[] contours = new short[count]; IntPtr array = rec.contours; for (int i = 0; i < count; i++) { contours[i] = Marshal.ReadInt16(array, IntPtr.Size * i); } return contours; } } /// /// Gets a set of bit flags used to characterize the outline and give hints to the scan-converter and hinter on /// how to convert/grid-fit it. /// /// public OutlineFlags Flags { get { if (disposed) throw new ObjectDisposedException("Flags", "Cannot access a disposed object."); return rec.flags; } } internal IntPtr Reference { get { if (disposed) throw new ObjectDisposedException("Reference", "Cannot access a disposed object."); return reference; } set { if (disposed) throw new ObjectDisposedException("Reference", "Cannot access a disposed object."); reference = value; rec = PInvokeHelper.PtrToStructure(reference); } } #endregion #region Methods #region Outline Processing /// /// Copy an outline into another one. Both objects must have the same sizes (number of points & number of /// contours) when this function is called. /// /// A handle to the target outline. public void Copy(Outline target) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); if (target == null) throw new ArgumentNullException("target"); IntPtr targetRef = target.Reference; Error err = FT.FT_Outline_Copy(reference, ref targetRef); target.Reference = reference; if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Apply a simple translation to the points of an outline. /// /// The horizontal offset. /// The vertical offset. public void Translate(int offsetX, int offsetY) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); FT.FT_Outline_Translate(reference, offsetX, offsetY); } /// /// Apply a simple 2x2 matrix to all of an outline's points. Useful for applying rotations, slanting, flipping, /// etc. /// /// /// You can use if you need to translate the outline's points. /// /// A pointer to the transformation matrix. public void Transform(FTMatrix matrix) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); FT.FT_Outline_Transform(reference, ref matrix); } /// /// Embolden an outline. The new outline will be at most 4 times ‘strength’ pixels wider and higher. You may /// think of the left and bottom borders as unchanged. /// /// Negative ‘strength’ values to reduce the outline thickness are possible also. /// /// /// The used algorithm to increase or decrease the thickness of the glyph doesn't change the number of points; /// this means that certain situations like acute angles or intersections are sometimes handled incorrectly. /// /// If you need ‘better’ metrics values you should call or . /// /// /// FT_Load_Glyph( face, index, FT_LOAD_DEFAULT ); /// if ( face->slot->format == FT_GLYPH_FORMAT_OUTLINE ) /// FT_Outline_Embolden( &face->slot->outline, strength ); /// /// How strong the glyph is emboldened. Expressed in 26.6 pixel format. public void Embolden(int strength) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); Error err = FT.FT_Outline_Embolden(reference, strength); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Embolden an outline. The new outline will be ‘xstrength’ pixels wider and ‘ystrength’ pixels higher. /// Otherwise, it is similar to , which uses the same strength in both directions. /// /// /// How strong the glyph is emboldened in the X direction. Expressed in 26.6 pixel format. /// /// /// How strong the glyph is emboldened in the Y direction. Expressed in 26.6 pixel format. /// public void EmboldenXY(int strengthX, int strengthY) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); Error err = FT.FT_Outline_EmboldenXY(reference, strengthX, strengthY); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Reverse the drawing direction of an outline. This is used to ensure consistent fill conventions for /// mirrored glyphs. /// /// /// This function toggles the bit flag in the outline's ‘flags’ field. /// /// It shouldn't be used by a normal client application, unless it knows what it is doing. /// public void Reverse() { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); FT.FT_Outline_Reverse(reference); } /// /// Check the contents of an outline descriptor. /// public void Check() { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); Error err = FT.FT_Outline_Check(reference); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Compute the exact bounding box of an outline. This is slower than computing the control box. However, it /// uses an advanced algorithm which returns very quickly when the two boxes coincide. Otherwise, the outline /// Bézier arcs are traversed to extract their extrema. /// /// /// If the font is tricky and the glyph has been loaded with , the resulting /// BBox is meaningless. To get reasonable values for the BBox it is necessary to load the glyph at a large /// ppem value (so that the hinting instructions can properly shift and scale the subglyphs), then extracting /// the BBox which can be eventually converted back to font units. /// /// The outline's exact bounding box. public BBox GetBBox() { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); IntPtr bboxRef; Error err = FT.FT_Outline_Get_BBox(reference, out bboxRef); if (err != Error.Ok) throw new FreeTypeException(err); return new BBox(bboxRef); } /// /// Walk over an outline's structure to decompose it into individual segments and Bézier arcs. This function /// also emits ‘move to’ operations to indicate the start of new contours in the outline. /// /// /// A table of ‘emitters’, i.e., function pointers called during decomposition to indicate path operations. /// /// /// A typeless pointer which is passed to each emitter during the decomposition. It can be used to store the /// state during the decomposition. /// public void Decompose(OutlineFuncs funcInterface, IntPtr user) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); if (funcInterface == null) throw new ArgumentNullException("funcInterface"); //TODO cleanup/move to the outlinefuncs class IntPtr funcInterfaceRef = Marshal.AllocHGlobal(OutlineFuncsRec.SizeInBytes); Marshal.WriteIntPtr(funcInterfaceRef, Marshal.GetFunctionPointerForDelegate(funcInterface.MoveFunction)); Marshal.WriteIntPtr(funcInterfaceRef, (int)Marshal.OffsetOf(typeof(OutlineFuncsRec), "line_to"), Marshal.GetFunctionPointerForDelegate(funcInterface.LineFuction)); Marshal.WriteIntPtr(funcInterfaceRef, (int)Marshal.OffsetOf(typeof(OutlineFuncsRec), "conic_to"), Marshal.GetFunctionPointerForDelegate(funcInterface.ConicFunction)); Marshal.WriteIntPtr(funcInterfaceRef, (int)Marshal.OffsetOf(typeof(OutlineFuncsRec), "cubic_to"), Marshal.GetFunctionPointerForDelegate(funcInterface.CubicFunction)); Marshal.WriteInt32(funcInterfaceRef, (int)Marshal.OffsetOf(typeof(OutlineFuncsRec), "shift"), funcInterface.Shift); Marshal.WriteInt32(funcInterfaceRef, (int)Marshal.OffsetOf(typeof(OutlineFuncsRec), "delta"), funcInterface.Delta); Error err = FT.FT_Outline_Decompose(reference, funcInterfaceRef, user); Marshal.FreeHGlobal(funcInterfaceRef); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Return an outline's ‘control box’. The control box encloses all the outline's points, including Bézier /// control points. Though it coincides with the exact bounding box for most glyphs, it can be slightly larger /// in some situations (like when rotating an outline which contains Bézier outside arcs). /// /// Computing the control box is very fast, while getting the bounding box can take much more time as it needs /// to walk over all segments and arcs in the outline. To get the latter, you can use the ‘ftbbox’ component /// which is dedicated to this single task. /// /// See for a discussion of tricky fonts. /// The outline's control box. public BBox GetCBox() { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); IntPtr cboxRef; FT.FT_Outline_Get_CBox(reference, out cboxRef); return new BBox(cboxRef); } /// /// Render an outline within a bitmap. The outline's image is simply OR-ed to the target bitmap. /// /// /// This function does NOT CREATE the bitmap, it only renders an outline image within the one you pass to it! /// Consequently, the various fields in ‘abitmap’ should be set accordingly. /// /// It will use the raster corresponding to the default glyph format. /// /// The value of the ‘num_grays’ field in ‘abitmap’ is ignored. If you select the gray-level rasterizer, and /// you want less than 256 gray levels, you have to use directly. /// /// A pointer to the target bitmap descriptor. public void GetBitmap(FTBitmap bitmap) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); Error err = FT.FT_Outline_Get_Bitmap(parentLibrary.Reference, reference, bitmap.Reference); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Render an outline within a bitmap. The outline's image is simply OR-ed to the target bitmap. /// /// /// This function does NOT CREATE the bitmap, it only renders an outline image within the one you pass to it! /// Consequently, the various fields in ‘abitmap’ should be set accordingly. /// /// It will use the raster corresponding to the default glyph format. /// /// The value of the ‘num_grays’ field in ‘abitmap’ is ignored. If you select the gray-level rasterizer, and /// you want less than 256 gray levels, you have to use directly. /// /// A handle to a FreeType library object. /// A pointer to the target bitmap descriptor. public void GetBitmap(Library library, FTBitmap bitmap) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); if (library == null) throw new ArgumentNullException("library"); if (bitmap == null) throw new ArgumentNullException("bitmap"); Error err = FT.FT_Outline_Get_Bitmap(library.Reference, reference, bitmap.Reference); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Render an outline within a bitmap using the current scan-convert. This function uses an /// structure as an argument, allowing advanced features like direct composition, /// translucency, etc. /// /// /// You should know what you are doing and how works to use this function. /// /// The field ‘params.source’ will be set to ‘outline’ before the scan converter is called, which means that /// the value you give to it is actually ignored. /// /// The gray-level rasterizer always uses 256 gray levels. If you want less gray levels, you have to provide /// your own span callback. See the value of the ‘flags’ field in the /// structure for more details. /// /// /// A pointer to an structure used to describe the rendering operation. /// public void Render(RasterParams parameters) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); if (parameters == null) throw new ArgumentNullException("parameters"); Error err = FT.FT_Outline_Render(parentLibrary.Reference, reference, parameters.Reference); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// Render an outline within a bitmap using the current scan-convert. This function uses an /// structure as an argument, allowing advanced features like direct composition, /// translucency, etc. /// /// /// You should know what you are doing and how works to use this function. /// /// The field ‘params.source’ will be set to ‘outline’ before the scan converter is called, which means that /// the value you give to it is actually ignored. /// /// The gray-level rasterizer always uses 256 gray levels. If you want less gray levels, you have to provide /// your own span callback. See the value of the ‘flags’ field in the /// structure for more details. /// /// A handle to a FreeType library object. /// /// A pointer to an structure used to describe the rendering operation. /// public void Render(Library library, RasterParams parameters) { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); if (library == null) throw new ArgumentNullException("library"); if (parameters == null) throw new ArgumentNullException("parameters"); Error err = FT.FT_Outline_Render(library.Reference, reference, parameters.Reference); if (err != Error.Ok) throw new FreeTypeException(err); } /// /// This function analyzes a glyph outline and tries to compute its fill orientation (see /// ). This is done by computing the direction of each global horizontal and/or /// vertical extrema within the outline. /// /// Note that this will return for empty outlines. /// /// The orientation. public Orientation GetOrientation() { if (disposed) throw new ObjectDisposedException("Outline", "Cannot access a disposed object."); return FT.FT_Outline_Get_Orientation(reference); } #endregion #region Glyph Stroker /// /// Retrieve the value corresponding to the ‘inside’ borders of a given outline. /// /// The border index. for empty or invalid outlines. public StrokerBorder GetInsideBorder() { return FT.FT_Outline_GetInsideBorder(Reference); } /// /// Retrieve the value corresponding to the ‘outside’ borders of a given outline. /// /// The border index. for empty or invalid outlines. public StrokerBorder GetOutsideBorder() { return FT.FT_Outline_GetOutsideBorder(Reference); } #endregion /// /// Disposes an instance of the class. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!disposed) { disposed = true; if (!duplicate) { Error err; if (parentLibrary != null) err = FT.FT_Outline_Done(parentLibrary.Reference, reference); else err = FT.FT_Outline_Done_Internal(parentMemory.Reference, reference); if (err != Error.Ok) throw new FreeTypeException(err); // removes itself from the parent Library, with a check to prevent this from happening when Library is // being disposed (Library disposes all it's children with a foreach loop, this causes an // InvalidOperationException for modifying a collection during enumeration) if (!parentLibrary.IsDisposed) parentLibrary.RemoveChildOutline(this); } reference = IntPtr.Zero; rec = default(OutlineRec); } } #endregion } }