28 de agosto de 2021

Olvídese de TXTWIDTH - use GdipMeasureString

Articulo original: Forget TXTWIDTH - use GdipMeasureString
https://doughennig.blogspot.com/2006/04/forget-txtwidth-use-gdipmeasurestring.html
Autor: Doug Hennig
Traducido por: Luis María Guayán


Durante años, hemos utilizado código como el siguiente para determinar el ancho de una cadena:

lnWidth = txtwidth(lcText, lcFontName, lnFontSize, ;
  lcFontStyle)
lnWidth = lnWidth * fontmetric(6, lcFontName, ;
  lnFontSize, lcFontStyle)

Este código funciona bien en muchas situaciones, pero no en una en particular: cuando se define el ancho de un objeto en un informe.

El valor calculado anteriormente está en píxeles, por lo que debe convertir el valor a FRU (las unidades utilizadas en los informes, que son 1/10000 de pulgada); debe multiplicar por 104,166 (10000 FRU por pulgada / 96 píxeles por pulgada). En lugar de hacer todo ese trabajo, puede utilizar el método GetFRUTextWidth del objeto auxiliar FFC _FRXCursor:

loFRXCursor = newobject('FRXCursor', ;
  home() + 'FFC\_FRXCursor.vcx')
lnWidth = loFRXCursor.GetFRUTextWidth(lcText, ;
  lcFontName, lnFontSize, lcFontStyle)

El problema es que esto en realidad no le da el valor correcto. El motivo es que los informes usan GDI + para la representación y GDI + representa los objetos un poco más grandes de lo esperado.

Para ver este problema, haga lo siguiente:

use home() + 'samples\data\customer'
loFRXCursor = newobject('FRXCursor', ;
  home() + 'FFC\_FRXCursor.vcx')
select max(loFRXCursor.GetFRUTextWidth(trim(company), ;
  'Arial', 10)) from customer into array laWidth
wait window laWidth[1]

Obtengo 22500. Ahora cree un informe, agregue un campo, ingrese "empresa" como expresión y hágalo 2.25 pulgadas de ancho (22500 FRU / 10000 FRU por pulgada). Obtenga una vista previa del informe. La elipsis reveladora al final de algunos valores indica que el tamaño del campo no era lo suficientemente amplio.

Esto me volvió loco durante años. Descubrí un factor empírico "fudge" para agregar al ancho calculado; 19 píxeles (1979.154 FRU) parecían funcionar la mayor parte del tiempo, pero ocasionalmente encontraba que no era suficiente para algunos valores.

Afortunadamente, dado que los informes usan GDI +, podemos usar una función GDI + para calcular con precisión el ancho. GdipMeasureString determina varias cosas sobre la cadena especificada, incluido el ancho. Aún mejor, VFP 9 viene con un objeto contenedor de GDI + para que no tenga que comprender la API de GDI + para llamar a GdipMeasureString.

Para mostrar un ejemplo del uso de las clases contenedoras de GDI +, eche un vistazo a esta función:

function GetWidth(tcText, tcFontName, tnFontSize)
local loGDI, ;
  loFont, ;
  lnChars, ;
  lnLines, ;
  loSize
loGDI = newobject('GPGraphics', ;
  home() + 'FFC\_GDIPlus.vcx')
loFont = newobject('GPFont', ;
  home() + 'FFC\_GDIPlus.vcx', '', tcFontName, ;
  tnFontSize, 0, 3)
loGDI.CreateFromHWnd(_screen.HWnd)
lnChars = 0
lnLines = 0
loSize  = loGDI.MeasureStringA(tcText, loFont, , , ;
  @lnChars, @lnLines)
lnWidth = loSize.W
release loGDI, loFont, loSize
return lnWidth

Ahora intente lo siguiente:

select max(GetWidth(trim(company), ;
  'Arial', 10)) from customer into array laWidth
wait window ceiling(laWidth[1] * 104.166)

Esto da 23838. Cambie el ancho del campo en el informe a 2,384 pulgadas y vuelva a obtener una vista previa. Esta vez los valores encajan correctamente.

El único problema ahora es que este código puede tardar mucho en ejecutarse si hay muchos registros porque para cada llamada, se crean un par de objetos contenedores de GDI + y se realiza alguna configuración de GDI +. Creé una clase contenedora para GdipMeasureString llamada SFGDIMeasureString que funciona de manera mucho más eficiente.

Veamos esta clase en secciones. Aquí está el comienzo: define algunas constantes, la clase y sus propiedades:

* Estos #DEFINEs se toman de
* home() + 'ffc\gdiplus.h'

#define GDIPLUS_FontStyle_Regular     0
#define GDIPLUS_FontStyle_Bold        1
#define GDIPLUS_FontStyle_Italic      2
#define GDIPLUS_FontStyle_BoldItalic  3
#define GDIPLUS_FontStyle_Underline   4
#define GDIPLUS_FontStyle_Strikeout   8
#define GDIPLUS_STATUS_OK       0
#define GDIPLUS_Unit_Point            3

define class SFGDIMeasureString as Custom
  oGDI    = .NULL.
    && a reference to a GPGraphics object
  oFormat = .NULL.
    && a reference to a GPStringFormat object
  oFont   = .NULL.
    && a reference to a GPFont object
  oSize   = .NULL.
    && a reference to a GPSize object
  nChars  = 0
   && the number of characters fitted in the
    && bounding box
  nLines  = 0
    && the number of lines in the bounding box
  nWidth  = 0
    && the width of the bounding box
  nHeight = 0
    && the height of the bounding box
  nStatus = 0
    && the status code from GDI+ functions

El método Init crea una instancia de algunos objetos auxiliares y declara la función GdipMeasureString. Destruye los objetos miembros con armas nucleares:

function Init
  This.oGDI    = newobject('GPGraphics', ;
    home() + 'ffc\_gdiplus.vcx')
  This.oFormat = newobject('GPStringFormat', ;
    home() + 'ffc\_gdiplus.vcx')
  This.oFont   = newobject('GPFont', ;
    home() + 'ffc\_gdiplus.vcx')
  This.oSize   = newobject('GPSize', ;
    home() + 'ffc\_gdiplus.vcx')
  declare integer GdipMeasureString ;
    in gdiplus.dll ;
    integer nGraphics, string cUnicode, ;
    integer nLength, integer nFont, ;
    string cLayoutRect, integer nStringFormat, ;
    string @cRectOut, integer @nChars, ;
    integer @nLines
endfunc

function Destroy
  store .NULL. to This.oGDI, This.oFormat, ;
    This.oFont, This.oSize
endfunc

MeasureString determina las dimensiones del cuadro delimitador para la cadena especificada:

function MeasureString(tcString, tcFontName, ;
  tnFontSize, tcStyle)
  local lcStyle, ;
    lnStyle, ;
    lnChars, ;
    lnLines, ;
    lcBoundingBox, ;
    lnGDIHandle, ;
    lnFontHandle, ;
    lnFormatHandle, ;
    lcRectF, ;
    lnStatus, ;
    llReturn
  with This

* Asegúrese de que los parámetros se pasen correctamente.

    do case
      case vartype(tcString) <> 'C' or ;
        empty(tcString)
        error 11
        return .F.
      case pcount() > 1 and ;
        (vartype(tcFontName) <> 'C' or ;
        empty(tcFontName) or ;
        vartype(tnFontSize) <> 'N' or ;
        not between(tnFontSize, 1, 128))
        error 11
        return .F.
      case pcount() = 4 and ;
       (vartype(tcStyle) <> 'C' or ;
        empty(tcStyle))
        error 11
        return .F.
    endcase

* Configure el objeto Font si se especificaron la fuente y el tamaño.

    if pcount() > 1
      lcStyle = iif(vartype(tcStyle) = 'C', ;
        tcStyle, '')
      .SetFont(tcFontName, tnFontSize, lcStyle)
    endif pcount() > 1

* Inicializar las variables de salida utilizadas en GdipMeasureString.

    lnChars       = 0
    lnLines       = 0
    lcBoundingBox = replicate(chr(0), 16)

* Obtenga los identificadores de GDI + que necesitamos.

    lnGDIHandle = .oGDI.GetHandle()
    if lnGDIHandle = 0
      .oGDI.CreateFromHWnd(_screen.HWnd)
      lnGDIHandle = .oGDI.GetHandle()
    endif lnGDIHandle = 0
    lnFontHandle   = .oFont.GetHandle()
    lnFormatHandle = .oFormat.GetHandle()

* Obtenga el tamaño del cuadro de diseño.

    lcRectF = replicate(chr(0), 8) + ;
      .oSize.GdipSizeF

* Llame a la función GdipMeasureString para obtener las dimensiones
* del cuadro delimitador para la cadena especificada.

    .nStatus = GdipMeasureString(lnGDIHandle, ;
      strconv(tcString, 5), len(tcString), ;
      lnFontHandle, lcRectF, lnFormatHandle, ;
      @lcBoundingBox, @lnChars, @lnLines)
    if .nStatus = GDIPLUS_STATUS_OK
      .nChars  = lnChars
      .nLines  = lnLines
      .nWidth  = ctobin(substr(lcBoundingBox, ;
         9, 4), 'N')
      .nHeight = ctobin(substr(lcBoundingBox, ;
        13, 4), 'N')
      llReturn = .T.
    else
      llReturn = .F.
    endif .nStatus = GDIPLUS_STATUS_OK
  endwith
  return llReturn
endfunc

GetWidth es un método de utilidad que devuelve el ancho de la cadena especificada:

function GetWidth(tcString, tcFontName, ;
  tnFontSize, tcStyle)
  local llReturn, ;
    lnReturn
  with This
    do case
      case pcount() < 2
        llReturn = .MeasureString(tcString)
      case pcount() < 4
        llReturn = .MeasureString(tcString, ;
          tcFontName, tnFontSize)
      otherwise
        llReturn = .MeasureString(tcString, ;
          tcFontName, tnFontSize, tcStyle)
    endcase
    if llReturn
      lnReturn = .nWidth
    endif llReturn
  endwith
  return lnReturn
endfunc

SetSize establece las dimensiones del cuadro de diseño para la cadena:

function SetSize(tnWidth, tnHeight)
  if vartype(tnWidth) = 'N' and ;
    tnWidth >= 0 and ;
    vartype(tnHeight) = 'N' and tnHeight >=0
    This.oSize.Create(tnWidth, tnHeight)
  else
    error 11
  endif vartype(tnWidth) = 'N' ...
endfunc

SetFont establece el nombre, el tamaño y el estilo de la fuente que se utilizará:

function SetFont(tcFontName, tnFontSize, tcStyle)
  local lcStyle
  do case
    case pcount() <= 2 and ;
      (vartype(tcFontName) <> 'C' or ;
      empty(tcFontName) or ;
      vartype(tnFontSize) <> 'N' or ;
      not between(tnFontSize, 1, 128))
      error 11
      return .F.
    case pcount() = 3 and ;
      vartype(tcStyle) <> 'C'
      error 11
      return .F.
  endcase
  lcStyle = iif(vartype(tcStyle) = 'C', tcStyle, '')
  lnStyle = iif('B' $ lcStyle, ;
      GDIPLUS_FontStyle_Bold, 0) + ;
    iif('I' $ lcStyle, ;
      GDIPLUS_FontStyle_Italic, 0) + ;
    iif('U' $ lcStyle, ;
      GDIPLUS_FontStyle_Underline, 0) + ;
    iif('-' $ lcStyle, ;
      GDIPLUS_FontStyle_Strikeout, 0)
  This.oFont.Create(tcFontName, tnFontSize, ;
    lnStyle, GDIPLUS_Unit_Point)
endfunc

Probemos el ejemplo anterior usando esta clase:

loGDI = newobject('SFGDIMeasureString', ;
  'SFGDIMeasureString.prg')
select max(loGDI.GetWidth(trim(company), 'Arial', 10)) ;
  from customer into array laWidth
wait window laWidth[1] * 10000/96

Esto es mucho más rápido que la función GetWidth presentada anteriormente. Lo siguiente se ejecutaría aún más rápido porque el objeto de fuente no tiene que inicializarse en cada llamada:

loGDI = newobject('SFGDIMeasureString', ;
  'SFGDIMeasureString.prg')
loGDI.SetFont('Arial', 10)
select max(loGDI.GetWidth(trim(company))) ;
  from customer into array laWidth
wait window laWidth[1] * 10000/96

Lo bueno de esta clase es que puede hacer mucho más que calcular el ancho de una cuerda. Es cy también determina la altura o el número de líneas que tomará una cadena en un cierto ancho (piense en establecer MEMOWIDTH en un cierto ancho y luego usar MEMLINES (), pero más rápido, más preciso y fuentes de apoyo).

Por ejemplo, tengo una clase de diálogo de mensaje genérico que utilizo para mostrar advertencias, errores y otros tipos de mensajes al usuario. No uso MESSAGEBOX () para esto porque mi clase admite varios botones con subtítulos personalizados. El problema es que los botones aparecen debajo de un cuadro de edición utilizado para mostrar el mensaje. Entonces, ¿cuánto espacio tengo que asignar para la altura del cuadro de edición? Si no especifico lo suficiente, el usuario debe desplazarse para ver el mensaje. Si especifico demasiado, los mensajes cortos se ven ridículos porque hay mucho espacio en blanco antes de los botones. Ahora, puedo hacer que el cuadro de edición tenga un tamaño arbitrario y usar SFGDIMeasureString para determinar la altura necesaria para el cuadro de edición para un mensaje dado, ajustando las posiciones de los botones dinámicamente. Para hacerlo, llamo al método SetSize para decirle a SFGDIMeasureString el ancho del cuadro de edición (paso un valor muy grande, como 10000, para la altura, por lo que no es un factor), luego llamo a MeasureString y uso el valor de la propiedad nHeight para la altura del cuadro de edición.

Estoy encontrando muchos más usos para esta clase. Espero que también te resulte útil.

15 de agosto de 2021

Cuadro de controles de la barra de título en el lado izquierdo

Artículo original: CtrlBox on Left Side
http://sandstorm36.blogspot.com/2018/08/ctrlbox-on-left-side.html
Autor: Jun Tangunan
Traducido por: Google Translate


Dado que algunos utilizan árabe/urdu, que se ocupa de la lectura y la entrada de datos de derecha a izquierda, este truco podría resultarles útil. Que es transponer también la posición del cuadro de controles de la barra de título en el lado izquierdo.

Este truco es realmente bastante simple y requiere solo 3 líneas de códigos que involucran GetWindowLong y SetWindowLong. Acabo de agregar algunos códigos para mostrar cómo se ve. Vea si esto puede resultarle útil.

Salud!

loTest = CREATEOBJECT("Form1")
loTest.SHOW(1)
READ EVENTS

DEFINE CLASS form1 AS FORM
  AUTOCENTER = .T.
  CAPTION = "ControlBox en el lado izquierdo"
  SHOWWINDOW = 2

  ADD OBJECT label1 AS LABEL WITH ;
    TOP = 20,;
    LEFT = 10,;
    FONTSIZE = 16,;
    WIDTH = THISFORM.WIDTH -20,;
    HEIGHT = THISFORM.HEIGHT - 20,;
    WORDWRAP = .T.,;
    CAPTION = "Esto muestra cómo invertir las posiciones de los objetos de la barra "+;
    "de título, como el cuadro de control, el icono y las etiquetas, dejando el interior "+;
    "del formulario en las posiciones normales de izquierda a derecha."

  PROCEDURE LOAD
    DECLARE INTEGER SetWindowLong IN user32 INTEGER HWND, INTEGER nIndex, INTEGER dwNewLong
    DECLARE INTEGER GetWindowLong IN user32 INTEGER HWND, INTEGER nIndex
    SetWindowLong(THISFORM.HWND, -20, BITOR(GetWindowLong(THISFORM.HWND, -16), 0x80000))
  ENDPROC

  PROCEDURE DESTROY
    CLEAR EVENTS
  ENDPROC
ENDDEFINE

6 de agosto de 2021

Iconos de Segoe MDL2 Assets en VFP9 con GDI+

Articulo original: Segoe MDL2 Assets Icons in VFP9 with Gdi+
http://vfpimaging.blogspot.com/2021/04/segoe-mdl2-assets-icons-in-vfp9-with-gdi.html
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Como se discutió anteriormente en este blog, VFP no puede mostrar de forma nativa ningún carácter que tenga su CHR() mayor que 0xFF (decimal 255).

Hay varias fuentes muy interesantes que traen íconos muy interesantes y actualizados que podríamos usar en nuestras aplicaciones, como SEGOE MDL2 ASSETS, utilizado por Windows 10 en todas partes.

Los Unicodes se pueden obtener directamente a través de CharMap.EXE o en toda la web. Aquí hay un excelente punto de partida: https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font

Los ejemplos a continuación usan GDI+ para guardar cualquier carácter deseado como una imagen, lo que nos permitirá usar esas imágenes geniales en nuestras aplicaciones. Usan las clases _GDIPLUS.VCX FFC, pero también es muy fácil de adaptar a GdiPlusX, si es necesario.

¡Adáptalo a tus necesidades!

Básicamente, una función que recupera un solo carácter Unicode y lo guarda como un archivo de imagen.

Uso:
Para obtener el icono "Imprimir":

EXTRAIGA UN ÚNICO ICONO

lcFile = "Imprimir.bmp"
lcUnicode = "e749"
lcFont = "ACTIVOS SEGOE MDL2"
lnSize = 32 && píxeles
lnForeColor = RGB (0, 0, 255) && Negro
lnBackColor = RGB (255, 255, 255) && Blanco
= MakeImageFromUnicode (m.lcFile, lcUnicode, lcFont, lnSize, lnForeColor, lnBackColor)

Guarde el siguiente código como "MakeImageFromUnicode.prg":

FUNCTION MakeImageFromUnicode(tcFileName, tcUnicode, tcFontName, tnImgSize, tnForeColor, tnBackColor)
  *!* tcUnicode allows up to 2 characters, that will be drawn one over the other
  *!* Par1: Main Unicode
  *!* Par2: Socondary Unicode
  *!* Par3: Mode, where 0=Center, 1=TopLeft, 2=TopRight, 3=BottLeft, 4=BottRight
  *!* Par4: Size of the 2nd character

  LOCAL lnChars, lnFactor, lnFontHeight, lnFontSize, lnHeight, lnLines, lnNewFontSize, lnWidth
  LOCAL lqUnicode
  LOCAL lcUnicode1, lcUnicode2, lnMode, lnSize2
  IF EMPTY(m.tcFileName) OR EMPTY(m.tcUnicode) OR EMPTY(m.tcFontName) OR EMPTY(m.tnImgSize)
    RETURN
  ENDIF

  m.lnFontSize = 48
  m.lnWidth   = m.tnImgSize
  m.lnHeight   = m.tnImgSize

  * Create a font object using the text object's settings.
  LOCAL loFont0 AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
  m.loFont0 = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
  m.loFont0.CREATE(m.tcFontName, m.lnFontSize, 0, 3) && 0 = Font Style

  LOCAL loGfx0 AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
  m.loGfx0 = NEWOBJECT('gpGraphics', HOME() + 'FFC\_GdiPlus.vcx')
  m.loGfx0.CreateFromHWnd(_SCREEN.HWND)
  m.lnChars = 0
  m.lnLines = 0

  LOCAL loSize AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
  m.loSize  = m.loGfx0.MeasureStringA("A", m.loFont0, , , @m.lnChars, @m.lnLines)
  * lnFontWidth = loSize.W
  m.lnFontHeight  = m.loSize.H
  m.lnFactor    = m.lnFontHeight / m.tnImgSize
  m.lnNewFontSize  = INT(m.lnFontSize / m.lnFactor)

  * Create a font object using the text object's settings.
  LOCAL loFont AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
  m.loFont = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
  m.loFont.CREATE(m.tcFontName, m.lnNewFontSize, 0, 3) && 0 = Font Style

  LOCAL loBMP AS GpBitmap OF HOME() + "FFC/_GdiPlus.vcx"
  m.loBMP = NEWOBJECT("gpBitmap", HOME() + "FFC/_GdiPlus.vcx")
  #DEFINE GdiPlus_PixelFormat_32BPPARGB        0x0026200a
  m.loBMP.CREATE(m.lnHeight, m.lnHeight, GdiPlus_PixelFormat_32BPPARGB)

  LOCAL loGfx AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
  m.loGfx = NEWOBJECT('gpGraphics', HOME() + 'FFC/_GdiPlus.vcx')
  m.loGfx.CreateFromImage(m.loBMP)

  * Setting the Backcolor
  LOCAL loBackColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
  IF EMPTY(m.tnBackColor)
    m.loBackColor = 0xFFFFFFFF && White background
  ELSE
    m.loBackColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
    m.loBackColor.FoxRGB = m.tnBackColor
  ENDIF
  m.loGfx.CLEAR(m.loBackColor) && Background

  * Create a rectangle
  LOCAL loRect AS GpRectangle OF HOME() + "FFC/_GdiPlus.vcx"
  m.loRect = NEWOBJECT("GPRectangle", HOME() + 'FFC/_GdiPlus.vcx', "", 0, 0, m.lnWidth, m.lnHeight)
  m.loRect.Y = m.loRect.Y + 1

  * Setting the Forecolor
  LOCAL loColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
  IF EMPTY(m.tnForeColor)
    m.tnForeColor = 0 && Black
  ENDIF
  m.loColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
  m.loColor.FoxRGB = m.tnForeColor

  LOCAL loBrush AS GpSolidBrush OF HOME() + "FFC/_GdiPlus.vcx"
  m.loBrush = NEWOBJECT("gpSolidBrush", HOME() + 'FFC/_GdiPlus.vcx', "", m.loColor)

  * The character need to be drawn at the center of the image object
  * Get a basic string format object
  * StringAlignment enumeration
  * Applies to GpStringFormat::Alignment, GpStringFormat::LineAlignment
  #DEFINE GDIPLUS_STRINGALIGNMENT_Near  0  && in Left-To-Right locale, this is Left
  #DEFINE GDIPLUS_STRINGALIGNMENT_Center  1
  #DEFINE GDIPLUS_STRINGALIGNMENT_Far    2  && in Left-To-Right locale, this is Right
  LOCAL loStringFormat AS gpStringFormat OF HOME() + "FFC/_GdiPlus.vcx"
  m.loStringFormat = NEWOBJECT("GpStringFormat", HOME() + "FFC/_GdiPlus.vcx")
  m.loStringFormat.CREATE()
  m.loStringFormat.ALIGNMENT     = GDIPLUS_STRINGALIGNMENT_Center
  m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center

  * Prepare the Unicode
  m.lcUnicode1 = GETWORDNUM(m.tcUnicode, 1, ",")
  m.lqUnicode   = LEFT(BINTOC(EVALUATE("0x" + m.lcUnicode1), "4RS"), 2)

  * Draw the string
  m.loGfx.DrawStringW(m.lqUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)
  m.lcUnicode2  = GETWORDNUM(m.tcUnicode, 2, ",")

  IF NOT EMPTY(m.lcUnicode2)
    m.lqUnicode  = LEFT(BINTOC(EVALUATE("0x" + m.lcUnicode2), "4RS"), 2)
    m.lnMode  = VAL(GETWORDNUM(m.tcUnicode, 3, ","))
    m.lnSize2  = VAL(GETWORDNUM(m.tcUnicode, 4, ","))
    m.lnSize2  = EVL(m.lnSize2, 100)

    lnNewFontSize = CEILING(m.lnNewFontSize * (lnSize2/100))
    m.loFont.CREATE(m.tcFontName, m.lnNewFontSize, 0, 3) && 0 = Font Style
    m.loStringFormat.ALIGNMENT     = GDIPLUS_STRINGALIGNMENT_Center
    m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center

    m.loRect.w = INT(m.lnWidth  * (m.lnSize2 / 100))
    m.loRect.H = INT(m.lnHeight * (m.lnSize2 / 100))

    DO CASE
      CASE m.lnMode = 0 && No transformation, the 2nd image will be drawn over the original
        m.loRect.x = INT((m.lnWidth  - m.loRect.w) / 2)
        m.loRect.Y = INT((m.lnHeight - m.loRect.H) / 2)

      CASE m.lnMode = 1 && Top-Left
        m.loRect.x = 0
        m.loRect.Y = 0

      CASE m.lnMode = 2 && Top-Right
        m.loRect.x = m.lnWidth - m.loRect.w
        m.loRect.Y = 0

      CASE m.lnMode = 3 && Bottom-Left
        m.loRect.x = 0
        m.loRect.Y = m.lnHeight - m.loRect.H

      CASE m.lnMode = 4 && Bottom-Right
        m.loRect.x = m.lnWidth - m.loRect.w
        m.loRect.Y = m.lnHeight - m.loRect.H

      OTHERWISE
    ENDCASE
    m.loRect.Y = m.loRect.Y + 1
    m.loGfx.DrawStringW(m.lqUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)
  ENDIF

  * Save as image
  m.loBMP.SaveToFile(m.tcFileName, "image/bmp")

  RETURN
ENDFUNC

La función también le permite crear nuevos íconos fusionando dos, en este caso, el ícono de Impresora y Configuración en la parte inferior derecha:

PERSONALIZA TUS ICONOS

* Setup the initial 5 variables
LOCAL lcFontName, lnImgSize, lnForeColor, lnBackColor, lcImageType
m.lcFontName  = "SEGOE MDL2 ASSETS"
m.lnImgSize	  = 64  && The desired bmp size in pixels
m.lnForeColor = RGB(0, 0, 0) && the ForeColor
m.lnBackColor = RGB(255, 255, 255) && the BackColor
m.lcImageType = "bmp" && available: bmp, jpg, gif, tif, png

EXTRACCIÓN DE TODOS LOS ICONOS DE UNA FUENTE

La función anterior se puede adaptar para extraer todos los caracteres de una fuente determinada, utilizando un bucle.

Las fuentes suelen tener algunos códigos que no se utilizan, por lo que en el siguiente código utilicé un truco simple para detectar las dimensiones vacías de la fuente, y cada vez que se cumplan las mismas condiciones en el bucle, se descartará el Unicode.

Simplemente ejecute el siguiente código para extraer todos los íconos de cualquier fuente determinada, con el tamaño y los colores de imagen deseados. ¡Ajuste las variables iniciales para que se adapten a sus necesidades!

* Setup the initial 5 variables
LOCAL lcFontName, lnImgSize, lnForeColor, lnBackColor, lcImageType
m.lcFontName  = "SEGOE MDL2 ASSETS"
m.lnImgSize    = 64  && The desired bmp SIZE IN PIXELS
m.lnForeColor = RGB(0, 0, 0) && the FORECOLOR
m.lnBackColor = RGB(255, 255, 255) && the BACKCOLOR
m.lcImageType = "bmp" && available: bmp, jpg, gif, tif, png

* Let's start
LOCAL lcEmptyUnicode, lcFileName, lcHex, lcUnicode, lnChars, lnEmptyH, lnEmptyW, lnFactor
LOCAL lnFontHeight, lnFontSize, lnFontWidth, lnHeight, lnLines, lnNewFontSize, lnWidth, loSizeReal, N

m.lnFontSize  = 48
m.lnWidth    = m.lnImgSize
m.lnHeight    = m.lnImgSize
m.lcImageType = LOWER(EVL(m.lcImageType, "bmp"))

* Create a rectangle
LOCAL loRect AS GpRectangle OF HOME() + "FFC/_GdiPlus.vcx"
m.loRect   = NEWOBJECT("GPRectangle", HOME() + 'FFC/_GdiPlus.vcx', "", 0, 0, m.lnWidth, m.lnHeight)
m.loRect.Y = m.loRect.Y + 1

* The character need to be drawn at the center of the image object
* Get a basic string format object
* StringAlignment enumeration
* Applies to GpStringFormat::Alignment, GpStringFormat::LineAlignment
#DEFINE GDIPLUS_STRINGALIGNMENT_Near  0  && IN LEFT-TO-RIGHT locale, THIS IS LEFT
#DEFINE GDIPLUS_STRINGALIGNMENT_Center  1
#DEFINE GDIPLUS_STRINGALIGNMENT_Far    2  && IN LEFT-TO-RIGHT locale, THIS IS RIGHT
LOCAL loStringFormat AS gpStringFormat OF HOME() + "FFC/_GdiPlus.vcx"
m.loStringFormat = NEWOBJECT("GpStringFormat", HOME() + "FFC/_GdiPlus.vcx")
m.loStringFormat.CREATE()
m.loStringFormat.ALIGNMENT     = GDIPLUS_STRINGALIGNMENT_Center
m.loStringFormat.LineAlignment = GDIPLUS_STRINGALIGNMENT_Center

* Create a font object using the text object's settings.
LOCAL loFont0 AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont0 = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont0.CREATE(m.lcFontName, m.lnFontSize, 0, 3) && 0 = FONT STYLE

LOCAL loGfx0 AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx0 = NEWOBJECT('gpGraphics', HOME() + 'FFC\_GdiPlus.vcx')
m.loGfx0.CreateFromHWnd(_SCREEN.HWND)

LOCAL loSize AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
m.lnChars     = 0
m.lnLines     = 0
m.loSize     = m.loGfx0.MeasureStringA("A", m.loFont0, , , @m.lnChars, @m.lnLines)
m.lnFontWidth  = m.loSize.W
m.lnFontHeight = m.loSize.H

m.lnFactor    = m.lnFontHeight / m.lnImgSize
m.lnNewFontSize  = INT(m.lnFontSize / m.lnFactor)

* Create a font object using the text object's settings.
LOCAL loFont AS GpFont OF HOME() + "FFC/_GdiPlus.vcx"
m.loFont = NEWOBJECT('gpFont', HOME() + 'FFC/_GdiPlus.vcx')
m.loFont.CREATE(m.lcFontName, m.lnNewFontSize, 0, 3) && 0 = FONT STYLE

* Get the measure of the empty character, that will be used to avoid saving it several times
m.lcEmptyUnicode = CHR(0) + CHR(0)
LOCAL loSizeEmpty AS gpSize OF HOME() + "FFC/_GdiPlus.vcx"
m.loSizeEmpty = m.loGfx0.MeasureStringW(m.lcEmptyUnicode, m.loFont, m.loRect, m.loStringFormat, @m.lnChars, @m.lnLines)
m.lnEmptyW    = m.loSizeEmpty.W
m.lnEmptyH    = m.loSizeEmpty.H

LOCAL loBMP AS GpBitmap OF HOME() + "FFC/_GdiPlus.vcx"
m.loBMP = NEWOBJECT("gpBitmap", HOME() + "FFC/_GdiPlus.vcx")
#DEFINE GdiPlus_PixelFormat_32BPPARGB        0x0026200a
m.loBMP.CREATE(m.lnHeight, m.lnHeight, GdiPlus_PixelFormat_32BPPARGB)

LOCAL loGfx AS GpGraphics OF HOME() + "FFC/_GdiPlus.vcx"
m.loGfx = NEWOBJECT('gpGraphics', HOME() + 'FFC/_GdiPlus.vcx')
m.loGfx.CreateFromImage(m.loBMP)

* Setting the Backcolor
LOCAL loBackColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.lnBackColor)
  m.loBackColor = 0xFFFFFFFF && White background
ELSE
  m.loBackColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
  m.loBackColor.FoxRGB = m.lnBackColor
ENDIF

* Setting the Forecolor
LOCAL loColor AS GpColor OF HOME() + "FFC/_GdiPlus.vcx"
IF EMPTY(m.lnForeColor)
  m.lnForeColor = 0 && Black
ENDIF
m.loColor     = NEWOBJECT("gpColor", HOME() + 'FFC/_GdiPlus.vcx')
m.loColor.FoxRGB = m.lnForeColor

LOCAL loBrush AS GpSolidBrush OF HOME() + "FFC/_GdiPlus.vcx"
m.loBrush = NEWOBJECT("gpSolidBrush", HOME() + 'FFC/_GdiPlus.vcx', "", m.loColor)

FOR m.n = 0xe001 TO 0xf8b3 && the LAST available FOUND IN charmap
  m.lcHex     = TRANSFORM(m.n, "@0")
  m.lcHex     = STRTRAN(m.lcHex, "0x0000", "")
  m.lcFileName = FORCEEXT(m.lcHex, m.lcImageType)

  * Prepare the Unicode
  m.lcUnicode   = LEFT(BINTOC(EVALUATE("0x" + m.lcHex), "4RS"), 2)

  m.loSizeReal = m.loGfx0.MeasureStringW(m.lcUnicode, m.loFont, m.loRect, m.loStringFormat, @m.lnChars, @m.lnLines)
  IF m.loSizeReal.W == m.pnEmptyW AND m.loSizeReal.H == m.pnEmptyH
    LOOP
  ENDIF

  m.loGfx.CLEAR(m.loBackColor) && Background

  * Draw the string
  m.loGfx.DrawStringW(m.lcUnicode, m.loFont, m.loRect, m.loStringFormat, m.loBrush)

  * Save as image
  m.loBMP.SaveToFile(m.lcFileName, "image/" + m.lcImageType)
ENDFOR

* Clear GDI+ objects
m.loRect         = NULL
m.loStringFormat = NULL
m.loColor        = NULL
m.loBackColor    = NULL
m.loBrush        = NULL
m.loSize         = NULL
m.loSizeEmpty    = NULL
m.loGfx0         = NULL
m.loGfx          = NULL
m.loBMP          = NULL
m.loFont0        = NULL
m.loFont         = NULL

RETURN

IMPORTANTE

No olvide que todas las fuentes tienen licencia. Eso significa que primero debe verificar si está autorizado a distribuir las imágenes generadas. Asegúrese de leer el EULA y ver qué puede o no puede hacer con ellos, ¿de acuerdo?

VEA TAMBIEN