Este artículo proviene de un motor de traducción automática.
El factor DirectX
Geometrías de contorno de caracteres desbordadas
Descargar el código de ejemplo
El avance más significativo en la tipografía digital en ordenadores personales ocurrió más de 20 años con el interruptor de las fuentes de mapa de bits a fuentes de contorno. En las versiones de Windows antes de Windows 3.1, texto en pantalla se generó desde archivos fuente consiste en pequeños mapas de bits de tamaño de punto específico. Estos mapas de bits pueden escalarse para tamaños intermedios, pero no sin una pérdida de fidelidad.
Adobe Systems Inc. fue pionero en un enfoque alternativo para mostrar fuentes de computadora con PostScript, que define los caracteres fuente con contornos gráficos que consta de líneas rectas y curvas de Bézier. Que podría escalar estos contornos a cualquier dimensión y algorítmicos "consejos" que ayudó a mantener la fidelidad en los tamaños pequeño punto. Como alternativa a PostScript para fuentes en la pantalla del ordenador personal, Apple Inc. desarrolló la especificación de fuente TrueType, que Microsoft adoptada más tarde. Eventualmente se convirtió en estándar OpenType común de hoy.
En estos días, damos por hecho la escalabilidad continua de fuentes en pantalla, así como la capacidad para rotar o Sesgar texto mediante transformaciones gráficas. Sin embargo también es posible obtener las geometrías reales que definen los contornos de estos caracteres de fuente y utilizan para fines inusuales, tales como esbozar los caracteres de texto, o recorte o realizar transformaciones no lineales.
De fuente a geometría de recorte
Si desea obtener geometrías de contorno de carácter en una aplicación Windows Store, no ayudará la API de tiempo de ejecución de Windows. Tendrás que usar DirectX. El método GetGlyphRunOutline de la IDWriteFontFace escribe los contornos de carácter en un IDWriteGeometrySink (que es lo mismo que un ID2D1SimplifiedGeometrySink) que define (o contribuye a) un objeto ID2D1PathGeometry.
Figura 1 muestra el constructor de una clase de representación en una aplicación Windows 8.1 denominada ClipToText que he creado desde la plantilla aplicación DirectX (XAML). El proyecto incluye el archivo de fuente Negrita Miramonte distribuible, y el código muestra cómo convertir un glifo a una geometría de ruta. Como de costumbre, he eliminado las comprobaciones de valores HRESULT errantes para fines de claridad.
Figura 1 conversión de un glifo de ejecutar a una geometría de ruta
ClipToTextRenderer::ClipToTextRenderer(
const std::shared_ptr<DeviceResources>& deviceResources) :
m_deviceResources(deviceResources)
{
// Get font file
ComPtr<IDWriteFactory> factory = m_deviceResources->GetDWriteFactory();
String^ filePath = Package::Current->InstalledLocation->Path +
"\\Fonts\\Miramob.ttf";
ComPtr<IDWriteFontFile> fontFile;
factory->CreateFontFileReference(filePath->Data(),
nullptr, &fontFile);
// Get font face
ComPtr<IDWriteFontFace> fontFace;
factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE,
1,
fontFile.GetAddressOf(),
0,
DWRITE_FONT_SIMULATIONS_NONE,
&fontFace);
// Create path geometry and open it
m_deviceResources->GetD2DFactory()->CreatePathGeometry(&m_clipGeometry);
ComPtr<ID2D1GeometrySink> geometrySink;
m_clipGeometry->Open(&geometrySink);
// Get glyph run outline ("CLIP")
uint16 glyphIndices [] = { 0x0026, 0x002F, 0x002C, 0x0033 };
float emSize = 96.0f;
// 72 points, arbitrary in this program
fontFace->GetGlyphRunOutline(emSize,
glyphIndices,
nullptr,
nullptr,
ARRAYSIZE(glyphIndices),
false,
false,
geometrySink.Get());
// Don't forget to close the geometry sink!
geometrySink->Close();
CreateDeviceDependentResources();
}
Aunque el código en figura 1 obtiene un objeto IDWriteFontFace de una fuente privada cargada, aplicaciones también pueden obtener objetos de cara fuente de fuentes en colecciones de fuentes, incluyendo la colección de fuentes del sistema. El código en figura 1 especifica índices glifo correspondiente explícitamente en el texto "CLIP", sino también puede derivan índices de glifo de una cadena de caracteres Unicode utilizando el método GetGlyphIndices.
Una vez que haya creado un objeto de ID2D1PathGeometry, se puede utilizar para rellenar (en cuyo caso el resultado se ve igual que procesa texto), dibujo (que representa sólo los contornos), o recorte. Figura 2 muestra un método Render que escalas y traduce la geometría de ruta para definir una región de recorte que abarca el área de pantalla completa. Tenga en cuenta la geometría de ruta tiene coordenadas positivas y negativas. El (0, 0) origen de la geometría de ruta corresponde a la línea de base al principio del glifo de correr.
Figura 2 recorte con una geometría de ruta
bool ClipToTextRenderer::Render()
{
if (!m_needsRedraw)
return false;
ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
Windows::Foundation::Size outputBounds = m_deviceResources->GetOutputBounds();
context->SaveDrawingState(m_stateBlock.Get());
context->BeginDraw();
context->Clear(ColorF(ColorF::DarkBlue));
// Get the clip geometry bounds
D2D_RECT_F geometryBounds;
m_clipGeometry->GetBounds(D2D1::IdentityMatrix(), &geometryBounds);
// Define transforms to center and scale clipping geometry
Matrix3x2F orientationTransform =
m_deviceResources->GetOrientationTransform2D();
Matrix3x2F translateTransform =
Matrix3x2F::Translation(SizeF(-geometryBounds.left, -geometryBounds.top));
float scaleHorz = outputBounds.Width /
(geometryBounds.right - geometryBounds.left);
float scaleVert = outputBounds.Height /
(geometryBounds.bottom - geometryBounds.top);
Matrix3x2F scaleTransform = Matrix3x2F::Scale(SizeF(scaleHorz, scaleVert));
// Set the geometry for clipping
ComPtr<ID2D1Layer> layer;
context->CreateLayer(&layer);
context->PushLayer(
LayerParameters(InfiniteRect(),
m_clipGeometry.Get(),
D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
translateTransform * scaleTransform
* orientationTransform), layer.Get());
// Draw lines radiating from center
translateTransform = Matrix3x2F::Translation(outputBounds.Width / 2,
outputBounds.Height / 2);
for (float angle = 0; angle < 360; angle += 1)
{
Matrix3x2F rotationTransform = Matrix3x2F::Rotation(angle);
context->SetTransform(rotationTransform * translateTransform *
orientationTransform);
context->DrawLine(Point2F(0, 0),
Point2F(outputBounds.Width / 2, outputBounds.Height / 2),
m_whiteBrush.Get(), 2);
}
context->PopLayer();
HRESULT hr = context->EndDraw();
if (hr != D2DERR_RECREATE_TARGET)
{
DX::ThrowIfFailed(hr);
}
context->RestoreDrawingState(m_stateBlock.Get());
m_needsRedraw = false;
return true;
}
El método Render entonces dibuja una serie de líneas que irradian desde el centro de la pantalla, creando la imagen que se muestra en la figura 3.
Figura 3 la pantalla ClipToText
Más profundo en las definiciones de geometría
En términos generales, una geometría de ruta es una colección de figuras, cada una de ellas es una colección de segmentos conectados. Estos segmentos toman la forma de líneas rectas, cuadrático y cúbico curvas de Bézier y arcos (que son las curvas en el perímetro de una elipse). Una figura puede ser que o bien cerrado, en cuyo caso el extremo está conectado al punto de inicio, o abierta.
El método GetGlyphRunOutline escribe los contornos del glifo en un IDWriteGeometrySink, que es lo mismo que un ID2D1SimplifiedGeometrySink. A su vez es la clase de los padres a un ID2D1GeometrySink regular. Usando ID2D1SimplifiedGeometrydisipador en lugar de ID2D1GeometrySink implica que la geometría resultante camino contiene figuras que consiste únicamente en líneas rectas y curvas Bézier cúbicas — no hay curvas de Bézier cuadráticas y sin arcos.
Para contornos de carácter fuente, estos segmentos están siempre cerrados — es decir, el punto final de la figura se conecta con el punto de partida. La geometría de ruta creada en el programa de ClipToText para los personajes "CLIP" consta de cinco figuras — una cifra para cada una de las tres primeras letras y dos para la última carta para tener en cuenta el interior de la parte superior de la P.
Tal vez te gustaría acceder a las líneas reales y curvas de Bézier que componen la geometría del camino para que se les pueden manipular en formas raras e inusuales. Al principio, esto no parece posible. Una vez inicializado con datos de un objeto ID2D1PathGeometry, el objeto es inmutable, y la interfaz no proporciona ninguna manera de obtener los contenidos.
Aunque hay una solución: Puede escribir su propia clase que implementa la interfaz ID2D1SimplifiedGeometrySink y pasar una instancia de esa clase al método GetGlyphRunOutline. Su implementación personalizada de ID2D1SimplifiedGeometrySink debe contener métodos denominados BeginFigure, AddLines, AddBeziers y EndFigure (entre otros). En estos métodos puede guardar la geometría de ruta completa en un árbol de estructuras que se pueden definir.
Esto es lo que hice. Las estructuras definí para guardar el contenido de una geometría de ruta se muestran en figura 4. Estas estructuras muestran cómo una geometría de ruta es una colección de objetos figura trazado y cada figura de ruta es una colección de segmentos conectados que consta de líneas rectas y curvas Bézier cúbicas.
Figura 4 estructuras para guardar el contenido de una geometría de ruta
struct PathSegmentData
{
bool IsBezier;
std::vector<D2D1_POINT_2F> Points;
// for IsBezier == false
std::vector<D2D1_BEZIER_SEGMENT> Beziers;
// for IsBezier == true
};
struct PathFigureData
{
D2D1_POINT_2F StartPoint;
D2D1_FIGURE_BEGIN FigureBegin;
D2D1_FIGURE_END FigureEnd;
std::vector<PathSegmentData> Segments;
};
struct PathGeometryData
{
D2D1_FILL_MODE FillMode;
std::vector<PathFigureData> Figures;
Microsoft::WRL::ComPtr<ID2D1PathGeometry>
GeneratePathGeometry(ID2D1Factory * factory);
};
Mi aplicación de la ID2D1SimplifiedGeometrySink se llama InterrogableGeometrySink, llamado así porque contiene un método que devuelve la geometría resultante como un objeto PathGeometryData. Se muestran las partes más interesantes del InterrogableGeometrySink en figura 5.
Figura 5 más de la clase InterrogableGeometrySink
void InterrogableGeometrySink::BeginFigure(D2D1_POINT_2F startPoint,
D2D1_FIGURE_BEGIN figureBegin)
{
m_pathFigureData.StartPoint = startPoint;
m_pathFigureData.FigureBegin = figureBegin;
m_pathFigureData.Segments.clear();
}
void InterrogableGeometrySink::AddLines(const D2D1_POINT_2F *points,
UINT pointsCount)
{
PathSegmentData polyLineSegment;
polyLineSegment.IsBezier = false;
polyLineSegment.Points.assign(points, points + pointsCount);
m_pathFigureData.Segments.push_back(polyLineSegment);
}
void InterrogableGeometrySink::AddBeziers(const D2D1_BEZIER_SEGMENT *beziers,
UINT beziersCount)
{
PathSegmentData polyBezierSegment;
polyBezierSegment.IsBezier = true;
polyBezierSegment.Beziers.assign(beziers, beziers + beziersCount);
m_pathFigureData.Segments.push_back(polyBezierSegment);
}
void InterrogableGeometrySink::EndFigure(D2D1_FIGURE_END figureEnd)
{
m_pathFigureData.FigureEnd = figureEnd;
m_pathGeometryData.Figures.push_back(m_pathFigureData);
}
HRESULT InterrogableGeometrySink::Close()
{
// Assume that the class accessing the geometry sink knows what it's doing
return S_OK;
}
// Method for this implementation
PathGeometryData InterrogableGeometrySink::GetPathGeometryData()
{
return m_pathGeometryData;
}
Simplemente pasar una instancia de InterrogableGeometrySink a GetGlyphRunOutline para obtener el objeto PathGeometryData que describe los contornos de carácter. PathGeometryData también contiene un método denominado GeneratePathGeometry que utiliza el árbol de figuras y segmentos para crear un objeto ID2D1PathGeometry que puede utilizar para dibujar, rellenar o recorte. La diferencia es que antes de llamar a GeneratePathGeometry, su programa puede modificar los puntos que componen la línea y segmentos de Bézier. Incluso puede agregar o quitar segmentos o figuras.
La clase InterrogableGeometrySink y las estructuras de apoyo son parte de un proyecto llamado RealTextEditor; por "Real" me refiero a que los contornos de texto en lugar del texto se puede editar. Cuando salga el programa, muestra los grandes personajes "DX". Puntee o haga clic en la pantalla para activar el modo de edición. En modo de edición, los personajes se contornean y aparecerán puntos.
Puntos verdes marcan los inicios y extremos de segmentos de línea y segmentos de Bézier. Puntos rojos son puntos de control de Bézier. Puntos de control están conectados a los extremos correspondientes con líneas rojas. Puedes agarrar esos puntos con el ratón — son un poco demasiado pequeños para los dedos — y arrastre, distorsionar los caracteres de texto en formas extrañas, como figura 6 demuestra.
Figura 6 modificado carácter contornos en RealTextEditor
RealTextEditor no tiene ninguna facilidad para salvar sus geometrías de carácter personalizado, pero ciertamente puede agregar uno. La intención de este programa no es para editar caracteres fuente, sino ilustrar claramente cómo fuente caracteres son definidos por una serie de líneas rectas y curvas de Bézier conectado en figuras cerradas — en este caso tres figuras, dos para el interior y fuera de la D y otro para la X.
Manipulaciones algorítmicas
Una vez que tienes una definición de geometría de ruta en forma de estructuras tales como PathGeometryData, PathFigureData y PathSegmentData, también se pueden manipular los puntos individuales algorítmicamente, torciendo y torneado personajes de cualquier manera te por favor, tal vez creando una imagen como se muestra en la figura 7.
Figura 7 el programa OscillatingText
Bueno, no del todo. La imagen se muestra en la figura 7 no es posible usando un objeto PathGeometryData generado a partir de la InterrogableGeometrySink clase sólo te he mostrado. En muchas fuentes sans serif simple, el capital H consiste en 12 puntos conectados por líneas rectas. Si estás tratando con esos puntos, es imposible les puede modificar las líneas rectas del H convertido en curvas.
Sin embargo, usted puede resolver ese problema con una versión mejorada de InterrogableGeometrySink llamado InterpolatableGeometrySink. Cuando esta nueva clase encuentra con una línea recta en el método AddLines, se rompe esa línea en varias líneas más pequeñas. (Usted puede controlar esta función con un argumento del constructor). El resultado es una definición de la geometría de ruta completamente maleable.
El programa de OscillatingText responsable de la imagen en figura 7 realmente cambios al interior de los personajes y hacia atrás, tanto como un baile de hula. Este algoritmo se implementa en el método de actualización en la clase de representación. Se conservan dos copias de un PathGeometryData: La fuente (identificada como "src") describe el contorno de texto original, y el destino ("dst") contiene puntos modificados basados en el algoritmo. El método Update concluye llamando a GeneratePathGeometry en la estructura del destino, y eso es lo que muestra el programa en su método Render.
A veces, cuando algorítmicamente alterar una geometría de ruta, es posible que prefiera trabajar únicamente con líneas en lugar de las curvas Bézier. Puedes hacerlo. Puede definir un objeto ID2D1PathGeometry de una llamada a GetGlyphRunOutline y luego llamar a simplificar en ese ID2D1PathGeometry usando el D2D1_GEOMETRY_SIMPLIconstante de FICATION_OPTION_LINES y un InterpolatableGeometrySink instancia.
Desde el tiempo de ejecución de Windows DirectX
Si ya conoces con las estructuras API de tiempo de ejecución de Windows, PathGeometryData, PathFigureData y PathSegmentData en figura 4 probablemente parecerá muy familiar. El espacio de nombres Windows::Xaml::UI::Media contiene clases similares llamadas PathGeometry, PathFigure y PathSegment, de la que derivan PolyLineSegment y PolyBezierSegment. Estas son las clases que se utiliza para definir la geometría de un camino en el tiempo de ejecución de Windows, que normalmente procesar mediante el elemento Path.
Por supuesto, la semejanza no debería ser sorprendente. Después de todo, el tiempo de ejecución de Windows está basado en DirectX. ¿Qué implica esta similitud es que puede escribir una clase que implementa ID2D1SimplifiedGeometrySink para construir un árbol de objetos PathGeometry, PathFigure, PolyLineSegment y PolyBezierSegment. El objeto PathGeometry resultante es directamente utilizable por una aplicación de tiempo de ejecución de Windows y se puede hacer referencia en un archivo XAML. (También podrías escribir una implementación de ID2D1SimplifiedGeometrySink que se genera una representación XAML de un PathGeometry e Inserte un archivo XAML en cualquier entorno basada en XAML, como Silverlight).
La solución TripleAnimatedOutline muestra esta técnica. La solución contiene un proyecto de componente de tiempo de ejecución de Windows denominado SimpleDWriteLib que contiene una clase pública ref denominada TextGeometryGenerator, que proporciona acceso a las fuentes del sistema y genera geometrías esquema basados en estas fuentes. Porque esta clase ref es parte de un componente de tiempo de ejecución de Windows, la interfaz pública consiste únicamente en tiempo de ejecución de Windows tipos. Hice esa interfaz pública consisten sobre todo en las propiedades de dependencia y podría ser utilizado con los enlaces en un archivo XAML. El proyecto SimpleDWriteLib también contiene una clase privada denominada InteroperableGeometrySink que implementa la interfaz ID2D1SimplifiedGeometrySink y construye un objeto PathGeometry de tiempo de ejecución de Windows.
Puede utilizar este PathGeometry con un elemento Path. Pero ten cuidado: Cuando el motor de tiempo de ejecución de Windows diseño calcula el tamaño de un elemento Path para propósitos de diseño, sólo utiliza coordenadas positivas. Para hacer más fácil de usar en un archivo XAML el PathGeometry, TextGeometryGenerator define un DWRITE_GLYPH_OFFSET que modifica las coordenadas basadas en el campo capHeight de la estructura de las métricas de fuente. Esto sirve para ajustar las coordenadas de la geometría para comenzar en la parte superior de los caracteres de la fuente, en lugar de origen y para eliminar las coordenadas más negativas.
Para demostrar la interoperabilidad de los componentes de SimpleDWriteLib, el proyecto de aplicación de TripleAnimatedOutline está escrito en Visual Basic. Pero no se preocupe: No tuve que escribir ningún código Visual Basic . Todo lo he añadido a este proyecto está en el archivo MainPage.xaml se muestra en la figura 8. El cuadro de lista muestra todas las fuentes en el sistema del usuario, y una geometría esquema basada en la fuente seleccionada está animada de tres maneras:
- Viajes de puntos alrededor de los personajes;
- Un pincel de degradado Barre más allá del texto;
- Una transformación de proyección lo gira alrededor del eje vertical.
Figura 8 el archivo XAML TripleAnimatedOutline
<Page
x:Class="TripleAnimatedOutline.MainPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TripleAnimatedOutline"
xmlns:dwritelib="using:SimpleDWriteLib">
<Page.Resources>
<dwritelib:TextGeometryGenerator x:Key="geometryGenerator"
Text="Outline"
FontFamily="Times New Roman"
FontSize="192"
FontStyle="Italic" />
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0"
ItemsSource="{Binding Source={StaticResource geometryGenerator},
Path=FontFamilies}"
SelectedItem="{Binding Source={StaticResource geometryGenerator},
Path=FontFamily,
Mode=TwoWay}" />
<Path Name="path"
Grid.Column="1"
Data="{Binding Source={StaticResource geometryGenerator}, Path=Geometry}"
Fill="LightGray"
StrokeThickness="6"
StrokeDashArray="0 2"
StrokeDashCap="Round"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path.Stroke>
<LinearGradientBrush StartPoint="0 0" EndPoint="1 0"
SpreadMethod="Reflect">
<GradientStop Offset="0" Color="Red" />
<GradientStop Offset="1" Color="Blue" />
<LinearGradientBrush.RelativeTransform>
<TranslateTransform x:Name="brushTransform" />
</LinearGradientBrush.RelativeTransform>
</LinearGradientBrush>
</Path.Stroke>
<Path.Projection>
<PlaneProjection x:Name="projectionTransform" />
</Path.Projection>
</Path>
</Grid>
<Page.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="path"
Storyboard.TargetProperty="StrokeDashOffset"
EnableDependentAnimation="True"
From="0" To="2" Duration="0:0:1"
RepeatBehavior="Forever" />
<DoubleAnimation Storyboard.TargetName="brushTransform"
Storyboard.TargetProperty="X"
EnableDependentAnimation="True"
From="0" To="2" Duration="0:0:3.1"
RepeatBehavior="Forever" />
<DoubleAnimation Storyboard.TargetName="projectionTransform"
Storyboard.TargetProperty="RotationY"
EnableDependentAnimation="True"
From="0" To="360" Duration="0:0:6.7"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Page.Triggers>
</Page>
Un segundo programa también utiliza SimpleDWriteLib. Esto es RippleText, un programa de C# que utiliza un evento CompositionTarget.Rendering para realizar una animación en el código. Al igual que OscillatingText, RippleText obtiene dos objetos idénticos PathGeometry. Uno como una fuente inmutable y el otro utiliza como un destino cuyos puntos se transforman algorítmico. El algoritmo consiste en una curva sinusoidal animada que se aplica a las coordenadas verticales, dando lugar a distorsiones, como las que se muestran en figura 9.
Figura 9 en la pantalla de RippleText
Aunque los ejemplos que he mostrado aquí son extremas en muchos sentidos, tienes la opción de crear efectos más sutiles. Sospecho que gran parte de la característica en Microsoft Word está construido alrededor de técnicas que implican la manipulación de caracteres del WordArt esboza, así podría proporcionar algo de inspiración.
Usted también puede integrar estas técnicas en el código más normal visualización de texto basado en IDWriteTextLayout. Esta interfaz tiene un método llamado Draw que acepta una instancia de una clase que implementa la interfaz IDWriteTextRenderer. Es una clase que voy a escribir para obtener acceso al objeto DWRITE_GLYPH_RUN que describe el texto que se va a procesar. Puede realizar cambios en el glifo de correr y luego procesar la versión modificada, o puede generar las geometrías de contorno del personaje en ese momento y modificar los contornos antes de su representación.
Gran parte de la potencia de DirectX radica en su flexibilidad y adaptabilidad a diferentes escenarios.
Charles Petzold es desde hace mucho tiempo colaborador de MSDN Magazine y el autor de "Programación Windows, 6ª edición" (Microsoft Press, 2012), un libro acerca de cómo escribir aplicaciones para Windows 8. Su sitio Web es charlespetzold.com.
Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Jim Galasyn (Microsoft)