Artículo técnico

Guionado y columnas equilibradas en PDFlibPas Delphi

PDFlibPas hace fluir un pasaje retenido a través de una a 64 columnas de igual ancho con DrawTextFlowColumns, y corta sus líneas con guionado acotado y sensible al idioma una vez que usted llama a SetTextFlowLanguage y SetTextFlowHyphenation. Se soportan nueve idiomas, y el idioma puede heredarse del valor /Lang del Catalog del documento en lugar de fijarse por cada flujo

Ambas funciones existen por la misma razón: una columna angosta es donde el salto de línea ingenuo deja de parecer tipografía y empieza a parecer un reporte de errores

¿Por qué el texto justificado se desmorona en columnas angostas?

Porque la justificación distribuye el espacio sobrante en los huecos entre palabras de una línea, y la cantidad sobrante depende de lo que quepa. En una medida ancha, el sobrante es pequeño y el ojo nunca lo nota. Reduzca el ancho a la mitad y una sola palabra larga que no cabe se empuja a la línea siguiente, dejando que sus predecesoras absorban todo ese espacio. Tres líneas así en secuencia producen los canales blancos verticales que los tipógrafos llaman ríos, y que los lectores experimentan como texto difícil de seguir sin saber por qué

El guionado corrige la causa en lugar del síntoma, al permitir un corte dentro de la palabra. Los compuestos del alemán y el holandés hacen esto innegociable: un sustantivo de 24 caracteres en una columna de 60 milímetros no tiene buen resultado sin un punto de corte. El inglés tolera mejor la ausencia, razón por la cual los productos centrados en inglés a menudo distribuyen código de diseño que falla la primera vez que lo ejecuta un cliente alemán

¿Qué idiomas, y de dónde viene el idioma?

El guionado cubre inglés, alemán, holandés, francés, español, italiano, portugués, ruso y turco. Fíjelo explícitamente por flujo con SetTextFlowLanguage, o déjelo heredarse de la entrada /Lang del Catalog del documento, que es el valor que ya lleva un documento etiquetado y accesible

Vale la pena usar esa herencia en lugar de sobrescribirla. Un documento que declara su idioma en el Catalog le está diciendo el mismo hecho, desde un solo lugar, a los lectores de pantalla, a los indexadores de búsqueda y al guionado, y un solo lugar es donde debe vivir un hecho. Si usted ya está produciendo salida etiquetada como se describe en etiquetado automático para PDF accesibles, la entrada de idioma ya está fijada y el flujo simplemente puede seguirla

var
  Lib: TPDFlib;
  Flow, Drawn: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.AddTrueTypeFont('Georgia', 1);
    Lib.SetTextSize(10.5);

    Flow := Lib.NewTextFlow(ArticleBody);
    try
      Lib.SetTextFlowLanguage(Flow, 'de');
      // Activar, al menos 3 caracteres antes del corte, 3 después
      Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);
      Lib.SetTextFlowMinLines(Flow, 2);   // nunca dejar una sola línea varada

      repeat
        // Tres columnas en una región de 480 pt, canaletas de 18 pt, equilibrado
        Drawn := Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);
        if (Drawn = 0) or (Lib.TextFlowFinished(Flow) = 1) then
          Break;
        Lib.NewPage;
      until False;
    finally
      Lib.ReleaseTextFlow(Flow);
    end;

    Lib.SaveToFile('newsletter.pdf');
  finally
    Lib.Free;
  end;
end;

MinPrefix y MinSuffix son tipografía, no validación

Los dos enteros después del indicador de activación fijan el número mínimo de caracteres que deben permanecer antes y después de un corte. Tres y tres es un valor predeterminado conservador que la mayoría de los estilos de casa aceptan. Dos y dos produce más oportunidades de corte y resultados notablemente más feos, porque un fragmento de dos letras colgando al final de una línea se lee como un error de tipeo

Suba los mínimos cuando el tamaño de tipo sea grande, donde cada fragmento es visualmente prominente, y bájelos solo cuando la columna sea genuinamente angosta y usted haya decidido que una medida ajustada importa más que una limpia. Esta es una decisión de estilo de casa más que una decisión técnica, que es exactamente por qué es un parámetro en lugar de una constante

¿Qué significa realmente "equilibrado" aquí?

El parámetro Balance cambia el comportamiento solo al final de un pasaje. Con el equilibrado activado, las columnas se acortan a un número exacto e igual de líneas cuando todo lo que queda cabe dentro de la región, lo cual es lo que evita que una página final muestre dos columnas completas y una tercera con una sola línea solitaria. Cuando el pasaje no cabe, cada columna conserva su altura completa, así que la página lleva tanto texto como puede, y el resto continúa en la página siguiente

Esa asimetría es el valor predeterminado correcto para documentos continuos. Equilibrar en medio de un artículo que fluye desperdiciaría espacio vertical en cada página por un efecto cosmético que nadie ve, ya que las columnas están completas de todos modos. Equilibrar al final es donde el ojo realmente lo nota, y ahí es precisamente donde se aplica

El salto de línea mide palabras completas

El algoritmo de corte mide palabras completas en lugar de acumular anchos de carácter, y reserva una búsqueda acotada para tokens sobredimensionados que no caben en una línea en absoluto, como una URL o un número de cuenta. Eso mantiene rápido el caso común y acotado el caso patológico, en lugar de lo contrario

Los guiones discrecionales suaves y los guiones automáticos se renderizan solo cuando el corte que marcan es el corte que se elige. Esto suena obvio y es un defecto clásico: una implementación ingenua escribe el carácter de guion mientras mide, y si el corte se mueve, el guion se queda atrás en medio de una línea. Nada luce más como un motor de texto roto que un guion perdido dentro de una palabra

var
  Lib: TPDFlib;
  Flow, Needed: Integer;
begin
  // Decidir el diseño antes de dibujar cualquier cosa
  Flow := Lib.NewTextFlow(ArticleBody);
  try
    Lib.SetTextFlowLanguage(Flow, 'fr');
    Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);

    // Líneas que el resto del pasaje necesita a un ancho de columna
    Needed := Lib.MeasureTextFlow(Flow, 148);
    if Needed > 3 * LinesPerColumn then
      UseTwoPageSpread
    else
      UseSinglePage;

    Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);

    if Lib.TextFlowFinished(Flow) <> 1 then
      CarryOver(Lib.GetTextFlowRemaining(Flow));
  finally
    Lib.ReleaseTextFlow(Flow);
  end;
end;

Mantenga idéntica la configuración de fuente entre cajas

Una regla gobierna todo diseño basado en flujo y vale la pena decirla sin rodeos: DrawTextFlow, DrawTextFlowColumns y MeasureTextFlow cortan líneas usando la fuente seleccionada en el momento en que se llaman. Cambie la fuente o el tamaño entre dos cajas del mismo flujo, o inicie una página nueva sin volver a seleccionar una, y la segunda caja corta las líneas de forma distinta a lo que midió la primera

El síntoma es exasperante precisamente porque parece intermitente: texto que cabe en la página uno se desborda en la página dos, o un conteo de líneas medido no coincide con lo que se dibujó. Seleccione la fuente una vez antes del bucle, vuelva a seleccionarla después de cada NewPage, y el flujo se comporta bien. Cuando aparecen escrituras mixtas en el mismo pasaje, la resolución descrita en respaldo automático de fuentes para texto CJK y emoji aplica tanto a la medición como al dibujo, así que los anchos se mantienen consistentes también a través de las corridas de respaldo

Para diseños de reporte donde el flujo es un elemento entre encabezados, pies de página y bloques con datos, los patrones de composición en el motor de reportes con dataset se combinan limpiamente con los flujos de columna: mida primero, coloque el mobiliario fijo, y luego dele al flujo la región que quede

PDFlibPas es una biblioteca de PDF para Delphi, C++Builder y Lazarus, y el ciclo de vida completo de TextFlow, creación, dibujo, medición, inspección, rebobinado y liberación, también se expone a través de las interfaces DLL y ActiveX. La documentación completa está en la página de la biblioteca PDFlibPas Delphi