Click here to Skip to main content
Click here to Skip to main content
Articles » Languages » C# » PInvoke » Downloads
 
Add your own
alternative version

How To Convert PDF to Image Using Ghostscript API

, 28 Mar 2010 CPOL
How to use Ghostscript library to create an image (or images) from a PDF file
ConvertPDF_Source_1.0.4.zip
ConvertPDF
ConvertPDF
bin
Debug
ConvertPDF.exe
ConvertPDF.vshost.exe
graphics.ico
Properties
Settings.settings
search.ico
Thumbs.db
ConvertPDF.suo
convertpdf_source_1.0.zip
ConvertPDF.exe
ConvertPDF.pdb
ConvertPDF.vshost.exe
obj
Debug
ConvertPDF.csproj.GenerateResource.Cache
ConvertPDF.exe
ConvertPDF.MainForm.resources
ConvertPDF.pdb
ConvertPDF.Properties.Resources.resources
Refactor
TempPE
Properties.Resources.Designer.cs.dll
Settings.settings
search.ico
ConvertPDF.suo
ConvertPDF_Source_1.1.1.zip
ConvertPDF.exe
ConvertPDF.vshost.exe
graphics.ico
Settings.settings
search.ico
Thumbs.db
ConvertPDF.suo
ConvertPDF_Source_1.1.2.zip
ConvertPDF.exe
ConvertPDF.vshost.exe
graphics.ico
Settings.settings
search.ico
Thumbs.db
ConvertPDF.suo
ConvertPDF_Source_1.1.2b.zip
ConvertPDF.exe
ConvertPDF.vshost.exe
graphics.ico
Settings.settings
search.ico
Thumbs.db
ConvertPDF.suo
ConvertPDF_Source_1.1.3.zip
graphics.ico
Settings.settings
search.ico
Thumbs.db
ConvertPDF.suo
ConvertPDF_source_1.2.zip
.cvsignore
.svn
all-wcprops
entries
prop-base
graphics.ico.svn-base
search.ico.svn-base
Thumbs.db.svn-base
props
text-base
ConvertPDF.csproj.svn-base
graphics.ico.svn-base
MainForm.cs.svn-base
MainForm.Designer.cs.svn-base
MainForm.resx.svn-base
PDFConvert.cs.svn-base
Program.cs.svn-base
search.ico.svn-base
Thumbs.db.svn-base
tmp
prop-base
props
text-base
graphics.ico
Properties
.svn
all-wcprops
entries
prop-base
props
text-base
AssemblyInfo.cs.svn-base
Resources.Designer.cs.svn-base
Resources.resx.svn-base
Settings.Designer.cs.svn-base
Settings.settings.svn-base
tmp
prop-base
props
text-base
Settings.settings
search.ico
Thumbs.db
PdfPageTools
Library
PdfSharp.dll
Properties
PdfToImage
.cvsignore
.svn
all-wcprops
entries
prop-base
props
text-base
GhostScript.cs.svn-base
PDFConvert.cs.svn-base
PdfToImage.csproj.svn-base
tmp
prop-base
props
text-base
Properties
.svn
all-wcprops
entries
prop-base
props
text-base
AssemblyInfo.cs.svn-base
tmp
prop-base
props
text-base
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.IO;

namespace ConvertPDF
{
    /// <summary>
    /// Create by : TaGoH
    /// URL of the last version: http://www.codeproject.com/KB/cs/GhostScriptUseWithCSharp.aspx
    /// Description:
    /// Class to convert a pdf to an image using GhostScript DLL
    /// A big Credit for this code go to:Rangel Avulso
    /// I mainly create a better interface and refactor it to made it ready to use!
    /// </summary>
    /// <see cref="http://www.codeproject.com/KB/cs/GhostScriptUseWithCSharp.aspx"/>
    /// <seealso cref="http://www.hrangel.com.br/index.php/2006/12/04/converter-pdf-para-imagem-jpeg-em-c/"/>
    public class PDFConvert
    {
        #region Windows Import
        /// <summary>Needed to copy memory from one location to another, used to fill the struct</summary>
        /// <param name="Destination"></param>
        /// <param name="Source"></param>
        /// <param name="Length"></param>
        [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
        static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
        #endregion
        #region GhostScript Import

        /// <summary>Create a new instance of Ghostscript. This instance is passed to most other gsapi functions. The caller_handle will be provided to callback functions.
        ///  At this stage, Ghostscript supports only one instance. </summary>
        /// <param name="pinstance"></param>
        /// <param name="caller_handle"></param>
        /// <returns></returns>
        [DllImport("gsdll32.dll", EntryPoint="gsapi_new_instance")]
        private static extern int gsapi_new_instance (out IntPtr pinstance, IntPtr caller_handle);

        /// <summary>This is the important function that will perform the conversion</summary>
        /// <param name="instance"></param>
        /// <param name="argc"></param>
        /// <param name="argv"></param>
        /// <returns></returns>
        [DllImport("gsdll32.dll", EntryPoint="gsapi_init_with_args")]
        private static extern int gsapi_init_with_args (IntPtr instance, int argc, IntPtr argv);
        /// <summary>
        /// Exit the interpreter. This must be called on shutdown if gsapi_init_with_args() has been called, and just before gsapi_delete_instance(). 
        /// </summary>
        /// <param name="instance"></param>
        /// <returns></returns>
        [DllImport("gsdll32.dll", EntryPoint="gsapi_exit")]
        private static extern int gsapi_exit (IntPtr instance);

        /// <summary>
        /// Destroy an instance of Ghostscript. Before you call this, Ghostscript must have finished. If Ghostscript has been initialised, you must call gsapi_exit before gsapi_delete_instance. 
        /// </summary>
        /// <param name="instance"></param>
        [DllImport("gsdll32.dll", EntryPoint="gsapi_delete_instance")]
        private static extern void gsapi_delete_instance (IntPtr instance);
        /// <summary>Get info about the version of Ghostscript i'm using</summary>
        /// <param name="pGSRevisionInfo"></param>
        /// <param name="intLen"></param>
        /// <returns></returns>
        [DllImport("gsdll32.dll", EntryPoint="gsapi_revision")]
        private static extern int gsapi_revision (ref GS_Revision pGSRevisionInfo , int intLen );
        /// <summary>Use a different I/O</summary>
        /// <param name="lngGSInstance"></param>
        /// <param name="gsdll_stdin">Function that menage the Standard INPUT</param>
        /// <param name="gsdll_stdout">Function that menage the Standard OUTPUT</param>
        /// <param name="gsdll_stderr">Function that menage the Standard ERROR output</param>
        /// <returns></returns>
        [DllImport("gsdll32.dll", EntryPoint = "gsapi_set_stdio")]
        private static extern int gsapi_set_stdio(IntPtr lngGSInstance, StdioCallBack gsdll_stdin, StdioCallBack gsdll_stdout, StdioCallBack gsdll_stderr);

        #endregion
        #region Const
        const int e_Quit=-101;
        const int e_NeedInput = -106;
        #endregion
        #region Variables
        private string _sDeviceFormat;
        private string _sParametersUsed;

        private int _iWidth;
        private int _iHeight;
        private int _iResolutionX;
        private int _iResolutionY;
        private int _iJPEGQuality;
        /// <summary>The first page to convert in image</summary>
        private int _iFirstPageToConvert = -1;
        /// <summary>The last page to conver in an image</summary>
        private int _iLastPageToConvert = -1;
        private Boolean _bFitPage;
        private Boolean _bThrowOnlyException = false;
        private bool _bRedirectIO = false;
        private IntPtr _objHandle;

        private System.Diagnostics.Process myProcess;
        public StringBuilder output;
        //public string output;
        //private List<byte> outputBytes;
        //public string error;
        #endregion
        #region Proprieties
        /// <summary>
        /// What format to use to convert
        /// is suggested to use png256 instead of jpeg for document!
        /// they are smaller and better suited!
        /// </summary>
        /// <see cref="http://pages.cs.wisc.edu/~ghost/doc/cvs/Devices.htm"/>
        public string OutputFormat
        {
            get { return _sDeviceFormat; }
            set { _sDeviceFormat = value; }
        }

        public string ParametersUsed
        {
            get { return _sParametersUsed; }
            set { _sParametersUsed = value; }
        }

        public int Width
        {
            get { return _iWidth; }
            set { _iWidth = value; }
        }

        public int Height
        {
            get { return _iHeight; }
            set { _iHeight = value; }
        }

        public int ResolutionX
        {
            get { return _iResolutionX; }
            set { _iResolutionX = value; }
        }

        public int ResolutionY
        {
            get { return _iResolutionY; }
            set { _iResolutionY = value; }
        }

        public Boolean FitPage
        {
            get { return _bFitPage; }
            set { _bFitPage = value; }
        }
        /// <summary>Quality of compression of JPG</summary>
        public int JPEGQuality
        {
            get { return _iJPEGQuality; }
            set { _iJPEGQuality = value; }
        }
        /// <summary>The first page to convert in image</summary>
        public int FirstPageToConvert
        {
            get { return _iFirstPageToConvert; }
            set { _iFirstPageToConvert = value; }
        }
        /// <summary>The last page to conver in an image</summary>
        public int LastPageToConvert
        {
            get { return _iLastPageToConvert; }
            set { _iLastPageToConvert = value; }
        }
        /// <summary>Set to True if u want the program to never display Messagebox
        /// but otherwise throw exception</summary>
        public Boolean ThrowOnlyException
        {
            get { return _bThrowOnlyException; }
            set { _bThrowOnlyException = value; }
        }
        /// <summary>If i should redirect the Output of Ghostscript library somewhere</summary>
        public bool RedirectIO
        {
            get { return _bRedirectIO; }
            set { _bRedirectIO = value; }
        }
        #endregion
        #region Init
        public PDFConvert(IntPtr objHandle)
        {
            _objHandle = objHandle;
        }

        public PDFConvert()
        {
            _objHandle = IntPtr.Zero;
        }
        #endregion

        #region Convert
        /// <summary>Convert a single file!</summary>
        /// <param name="inputFile">The file PDf to convert</param>
        /// <param name="outputFile">The image file that will be created</param>
        /// <remarks>You must pass all the parameter for the conversion
        /// as Proprieties of this class</remarks>
        /// <returns>True if the conversion succed!</returns>
        public bool Convert(string inputFile, string outputFile)
        {
            return Convert(inputFile, outputFile, _bThrowOnlyException);
        }

        //public System.Drawing.Image Convert(string inputFile)
        //{
        //    _bRedirectIO = true;
        //    if (Convert(inputFile, "%stdout", _bThrowOnlyException))
        //    {
        //        if ((output != null) && (output.Length > 0))
        //        {
        //            //StringReader sr = new StringReader(output.ToString());
        //            //MemoryStream ms = new MemoryStream(UTF8Encoding.Default.GetBytes(output.ToString()));
        //            System.Drawing.Image returnImage = System.Drawing.Image.FromStream(myProcess.StandardOutput.BaseStream).Clone() as System.Drawing.Image;
        //            //ms.Close();
        //            return returnImage;
        //        }
        //    }
        //    return null;
        //}

        /// <summary>Convert a single file!</summary>
        /// <param name="inputFile">The file PDf to convert</param>
        /// <param name="outputFile">The image file that will be created</param>
        /// <param name="throwException">if the function should throw an exception
        /// or display a message box</param>
        /// <remarks>You must pass all the parameter for the conversion
        /// as Proprieties of this class</remarks>
        /// <returns>True if the conversion succed!</returns>
        public bool Convert(string inputFile, string outputFile,bool throwException)
        {
            #region Check Input
            //Avoid to work when the file doesn't exist
            if (string.IsNullOrEmpty(inputFile))
            {
                if (throwException)
                    throw new ArgumentNullException("inputFile");
                else
                {
                    System.Windows.Forms.MessageBox.Show("The inputfile is missing");
                    return false;
                }
            }
            if (!System.IO.File.Exists(inputFile))
            {
                if (throwException)
                    throw new ArgumentException(string.Format("The file :'{0}' doesn't exist", inputFile), "inputFile");
                else
                {
                    System.Windows.Forms.MessageBox.Show(string.Format("The file :'{0}' doesn't exist", inputFile));
                    return false;
                }
            }
            if (string.IsNullOrEmpty(_sDeviceFormat))
            {
                if (throwException)
                    throw new ArgumentNullException("Device");
                else
                {
                    System.Windows.Forms.MessageBox.Show("You didn't provide a device for the conversion");
                    return false;
                }
            }
            #endregion
            #region Variables
            int intReturn, intCounter, intElementCount;
            //The pointer to the current istance of the dll
            IntPtr intGSInstanceHandle;
            object[] aAnsiArgs;
            IntPtr[] aPtrArgs;
            GCHandle[] aGCHandle;
            IntPtr callerHandle, intptrArgs;
            GCHandle gchandleArgs;
            #endregion
            //Generate the list of the parameters i need to pass to the dll
            string[] sArgs = GetGeneratedArgs(inputFile, outputFile);
            #region Convert Unicode strings to null terminated ANSI byte arrays
            // Convert the Unicode strings to null terminated ANSI byte arrays
            // then get pointers to the byte arrays.
            intElementCount = sArgs.Length;
            aAnsiArgs = new object[intElementCount];
            aPtrArgs = new IntPtr[intElementCount];
            aGCHandle = new GCHandle[intElementCount];
            //Convert the parameters
            for (intCounter = 0; intCounter < intElementCount; intCounter++)
            {
                aAnsiArgs[intCounter] = StringToAnsiZ(sArgs[intCounter]);
                aGCHandle[intCounter] = GCHandle.Alloc(aAnsiArgs[intCounter], GCHandleType.Pinned);
                aPtrArgs[intCounter] = aGCHandle[intCounter].AddrOfPinnedObject();
            }
            gchandleArgs = GCHandle.Alloc(aPtrArgs, GCHandleType.Pinned);
            intptrArgs = gchandleArgs.AddrOfPinnedObject();
            #endregion
            #region Create a new istance of the library!
            intReturn = -1;
            try
            {
                intReturn = gsapi_new_instance(out intGSInstanceHandle, _objHandle);
                //Be sure that we create an istance!
                if (intReturn < 0)
                {
                    for (intCounter = 0; intCounter < intReturn; intCounter++)
                        aGCHandle[intCounter].Free();
                    gchandleArgs.Free();
                    if (throwException)
                        throw new ApplicationException("I can't create a new istance of Ghostscript please verify no other istance are running!");
                    else
                    {
                        System.Windows.Forms.MessageBox.Show("I can't create a new istance of Ghostscript please verify no other istance are running!");
                        return false;
                    }
                }
            }
            catch (DllNotFoundException ex)
            {//in this case the dll we r using isn't the dll we expect
                for (intCounter = 0; intCounter < intReturn; intCounter++)
                    aGCHandle[intCounter].Free();
                gchandleArgs.Free();
                if (throwException)
                    throw new ApplicationException("The gs32dll.dll in the program directory doesn't "+
                        "expose the methods i need!\nplease download the version 8.63 from the original website!");
                else
                {
                    System.Windows.Forms.MessageBox.Show("The gs32dll.dll in the program directory"+
                        " doesn't expose the methods i need!\nPlease download the version 8.63 "+
                        "from the original website!");
                    return false;
                }
            }
            callerHandle = IntPtr.Zero;//remove unwanter handler
            #endregion
            #region Capture the I/O
            if (_bRedirectIO)
            {
                StdioCallBack stdinCallback = new StdioCallBack(gsdll_stdin);
                StdioCallBack stdoutCallback = new StdioCallBack(gsdll_stdout);
                StdioCallBack stderrCallback = new StdioCallBack(gsdll_stderr);
                intReturn = gsapi_set_stdio(intGSInstanceHandle, stdinCallback, stdoutCallback, stderrCallback);
                if (output == null) output = new StringBuilder();
                else output.Remove(0, output.Length);
                myProcess = System.Diagnostics.Process.GetCurrentProcess();
                myProcess.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(SaveOutputToImage);
            }
            #endregion
            intReturn = -1;//if nothing change it there is an error!
            //Ok now is time to call the interesting method
            try { intReturn = gsapi_init_with_args(intGSInstanceHandle, intElementCount, intptrArgs); }
            catch (Exception ex)
            {
                if (throwException)
                    throw new ApplicationException(ex.Message, ex);
                else
                    System.Windows.Forms.MessageBox.Show(ex.Message);
            }
            finally//No matter what happen i MUST close the istance!
            {   //free all the memory
                for (intCounter = 0; intCounter < intReturn; intCounter++)
                {
                    aGCHandle[intCounter].Free();
                }
                gchandleArgs.Free();
                gsapi_exit(intGSInstanceHandle);//Close the istance
                gsapi_delete_instance(intGSInstanceHandle);//delete it
                //In case i was looking for output now stop
                if (myProcess != null) myProcess.OutputDataReceived -= new System.Diagnostics.DataReceivedEventHandler(SaveOutputToImage);
            }
            //Conversion was successfull if return code was 0 or e_Quit
            return (intReturn == 0) | (intReturn == e_Quit);//e_Quit = -101
        }

        void SaveOutputToImage(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            output.Append(e.Data);
        }
        #endregion

        #region Accessory Functions
        /// <summary>This function create the list of parameters to pass to the dll</summary>
        /// <param name="inputFile">the file to convert</param>
        /// <param name="outputFile">where to write the image</param>
        /// <returns>the list of the arguments</returns>
        private string[] GetGeneratedArgs(string inputFile, string outputFile)
        {
            // Count how many extra args are need - HRangel - 11/29/2006, 3:13:43 PM
            ArrayList lstExtraArgs = new ArrayList();

            if ( _sDeviceFormat=="jpeg" && _iJPEGQuality > 0 && _iJPEGQuality < 101)
                lstExtraArgs.Add("-dJPEGQ=" + _iJPEGQuality);

            if (_iWidth > 0 && _iHeight > 0)
                lstExtraArgs.Add("-g" + _iWidth + "x" + _iHeight);

            if (_bFitPage)
                lstExtraArgs.Add("-dPDFFitPage");

            if (_iResolutionX > 0)
            {
                if (_iResolutionY > 0)
                    lstExtraArgs.Add("-r" + _iResolutionX + "x" + _iResolutionY);
                else
                    lstExtraArgs.Add("-r" + _iResolutionX);
            }
            if (_iFirstPageToConvert > 0)
                lstExtraArgs.Add("-dFirstPage=" + _iFirstPageToConvert);
            if (_iLastPageToConvert > 0)
                lstExtraArgs.Add("-dLastPage=" + _iLastPageToConvert);
            //If i want to redirect write it to the standard output!
            if (_bRedirectIO)
            {
                outputFile = "-";
                //In this case you must also use the -q switch to prevent Ghostscript
                //from writing messages to standard output which become
                //mixed with the intended output stream. 
                lstExtraArgs.Add("-q");
            }
            int iFixedCount = 7;
            int iExtraArgsCount = lstExtraArgs.Count;
            string[] args = new string[iFixedCount + lstExtraArgs.Count];
            args[0]="pdf2img";//this parameter have little real use
            args[1]="-dNOPAUSE";//I don't want interruptions
            args[2]="-dBATCH";//stop after
            args[3]="-dSAFER";
            args[4]="-sDEVICE="+_sDeviceFormat;//what kind of export format i should provide
            //For a complete list watch here:
            //http://pages.cs.wisc.edu/~ghost/doc/cvs/Devices.htm
            //Fill the remaining parameters
            for (int i=0; i < iExtraArgsCount; i++)
            {
                args[5+i] = (string) lstExtraArgs[i];
            }
            //Fill outputfile and inputfile
            args[5 + iExtraArgsCount] = string.Format("-sOutputFile={0}",outputFile);
            args[6 + iExtraArgsCount] = string.Format("{0}",inputFile);
            //Ok now save them to be shown 4 debug use
            _sParametersUsed = "";
            foreach (string arg in args)
                _sParametersUsed += " " + arg;
            return args;
        }

        /// <summary>
        /// Convert a Unicode string to a null terminated Ansi string for Ghostscript.
        /// The result is stored in a byte array
        /// </summary>
        /// <param name="str">The parameter i want to convert</param>
        /// <returns>the byte array that contain the string</returns>
        private static byte[] StringToAnsiZ(string str)
        {
            // Later you will need to convert
            // this byte array to a pointer with
            // GCHandle.Alloc(XXXX, GCHandleType.Pinned)
            // and GSHandle.AddrOfPinnedObject()
            //int intElementCount,intCounter;
            if (str == null) str = String.Empty;
            byte[] testVector = new byte[str.Length + 1];
            System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
            encoding.GetBytes(str,0,str.Length,testVector,0);
            testVector[str.Length] = 0;
            return testVector;
        }

        /// <summary>Convert a Pointer to a string to a real string</summary>
        /// <param name="strz"></param>
        /// <returns></returns>
        public static string AnsiZtoString(IntPtr strz)
        {
            return Marshal.PtrToStringAnsi(strz);
        }
        #endregion
        #region Menage Standard Input & Standard Output
        public int gsdll_stdin(IntPtr intGSInstanceHandle, IntPtr strz, int intBytes)
        {
            // This is dumb code that reads one byte at a time
           // Ghostscript doesn't mind this, it is just very slow
            if (intBytes == 0) 
                return 0;
            else
            {
                int ich = Console.Read();
                if (ich == -1)
                    return 0; // EOF
                else
                {
                    byte bch = (byte)ich;
                    GCHandle gcByte = GCHandle.Alloc(bch, GCHandleType.Pinned);
                    IntPtr ptrByte = gcByte.AddrOfPinnedObject();
                    CopyMemory(strz, ptrByte, 1);
                    ptrByte = IntPtr.Zero;
                    gcByte.Free();
                    return 1;
                }
            }
        }

        public int gsdll_stdout(IntPtr intGSInstanceHandle, IntPtr strz , int intBytes)
        {
            if (intBytes > 0)
            {
                Console.Write(Marshal.PtrToStringAnsi(strz));
            }
            return 0;
        }

        public int gsdll_stderr(IntPtr intGSInstanceHandle, IntPtr strz, int intBytes )
        {
            //return gsdll_stdout(intGSInstanceHandle, strz, intBytes);
            Console.Write(Marshal.PtrToStringAnsi(strz));
            return intBytes;
        }
        #endregion
        #region Menage Revision
        public GhostScriptRevision GetRevision()
        {
            // Check revision number of Ghostscript
            int intReturn;
            GS_Revision udtGSRevInfo = new GS_Revision();
            GhostScriptRevision output;
            GCHandle gcRevision;
            gcRevision = GCHandle.Alloc(udtGSRevInfo, GCHandleType.Pinned);
            intReturn = gsapi_revision(ref udtGSRevInfo, 16);
            output.intRevision = udtGSRevInfo.intRevision;
            output.intRevisionDate = udtGSRevInfo.intRevisionDate;
            output.ProductInformation = AnsiZtoString(udtGSRevInfo.strProduct);
            output.CopyrightInformations = AnsiZtoString(udtGSRevInfo.strCopyright);
            gcRevision.Free();
            return output;
        }
        #endregion
    }

    /// <summary>Delegate used by Ghostscript to perform I/O operations</summary>
    /// <param name="handle"></param>
    /// <param name="strptr"></param>
    /// <param name="count"></param>
    /// <returns></returns>
    public delegate int StdioCallBack(IntPtr handle, IntPtr strptr, int count);
    /// <summary>This struct is filled with the information of the version of this ghostscript</summary>
    /// <remarks>Have the layout defined cuz i will fill it with a kernel copy memory</remarks>
    [StructLayout(LayoutKind.Sequential)]
    struct GS_Revision
    {
        public IntPtr strProduct;
        public IntPtr strCopyright;
        public int intRevision;
        public int intRevisionDate;
    }

    public struct GhostScriptRevision
    {
        public string ProductInformation;
        public string CopyrightInformations;
        public int intRevision;
        public int intRevisionDate;
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Lord TaGoH
Web Developer
Italy Italy
No Biography provided

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 28 Mar 2010
Article Copyright 2009 by Lord TaGoH
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid