2016-04-07 5 views
0

J'ai implémenté une méthode de Justify assez rudimentaire pour dessiner une corde, mais je voudrais l'optimiser pour que l'espacement soit un peu plus dispersé.Justifier une chaîne manuellement pour la méthode DrawString() en C#

Ce que j'ai à ce jour, est la suivante:

string lastword = line.Split(' ').Last(); 
string lineNoLastWord = line.Substring(0,line.LastIndexOf(" ")).Trim();; 
g.DrawString(lineNoLastWord, Font, brush, textBounds, sf); 
g.DrawString(lastword, Font, brush, textBounds, ConvertAlignment(System.Windows.TextAlignment.Right)); 

ConvertAlignment est une méthode personnalisée comme suit:

private StringFormat ConvertAlignment(System.Windows.TextAlignment align) { 
    StringFormat s = new StringFormat(); 
    switch (align) { 
     case System.Windows.TextAlignment.Left: 
     case System.Windows.TextAlignment.Justify: 
      s.LineAlignment=StringAlignment.Near; 
      break; 
     case System.Windows.TextAlignment.Right: 
      s.LineAlignment=StringAlignment.Far; 
      break; 
     case System.Windows.TextAlignment.Center: 
      s.LineAlignment=StringAlignment.Center; 
      break; 
    } 
    s.Alignment = s.LineAlignment; 
    return s; 
} 

Le résultat est proche, mais a besoin d'un ajustement des espaces dans la chaîne lineNoLastWord.

Un peu plus d'arrière-plan derrière le code. line est le résultat d'une méthode qui est responsable de détecter si la chaîne sort des limites (largeur), et de la casser en lignes et mots, en se cassant et en mesurant pour s'assurer que la ligne complète reste dans la largeur de la zone être tiré sur. Le procédé met en oeuvre d'autres propriétés dans une ici classe, mais beaucoup plus grande est l'essentiel de celui-ci:

internal LineBreaker breakIntoLines(string s, int maxLineWidth) { 
    List<string> sResults = new List<string>(); 

    int stringHeight; 
    int lineHeight; 
    int maxWidthPixels = maxLineWidth; 

    string[] lines = s.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.None); 
    using (Graphics g=Graphics.FromImage(Pages[CurrentPage - 1])) { 
     g.CompositingQuality = CompositingQuality.HighQuality; 
     if (maxLineWidth<=0||maxLineWidth>(Pages[CurrentPage-1].Width-X)) { 
      maxWidthPixels=Pages[CurrentPage-1].Width-X; 
     } 
     lineHeight = (Int32)(g.MeasureString("X", Font).Height*(float)((float)LineSpacing/(float)100)); 
     stringHeight = (Int32)g.MeasureString("X", Font).Height; 
     foreach (string line in lines) { 
      string[] words=line.Split(new string[] { " " }, StringSplitOptions.None); 
      sResults.Add(""); 
      for (int i=0; i<words.Length; i++) { 
       if (sResults[sResults.Count-1].Length==0) { 
        sResults[sResults.Count-1]=words[i]; 
       } else { 
        if (g.MeasureString(sResults[sResults.Count-1]+" "+words[i], Font).Width<maxWidthPixels) { 
         sResults[sResults.Count-1]+=" "+words[i]; 
        } else { 
         sResults.Add(words[i]); 
        } 
       } 
      } 
     } 
    } 
    return new LineBreaker() { 
     LineHeight = lineHeight, 
     StringHeight = stringHeight, 
     MaxWidthPixels = maxWidthPixels, 
     Lines = sResults 
    }; 
} 

internal class LineBreaker { 
    public List<string> Lines { get; set; } 
    public int MaxWidthPixels { get; set; } 
    public int StringHeight { get; set; } 
    public int LineHeight { get; set; } 

    public LineBreaker() { 
     Lines = new List<string>(); 
     MaxWidthPixels = 0; 
     StringHeight = 0; 
     LineHeight = 0; 
    } 

    public LineBreaker(List<string> lines, int maxWidthPixels, int stringHeight, int lineHeight) { 
     Lines = lines; 
     MaxWidthPixels = maxWidthPixels; 
     LineHeight = lineHeight; 
     StringHeight = stringHeight; 
    } 
} 

L'image suivante illustre le problème que cela cause:

demonstration

J'ai aussi vu this stackoverflow question and answers, et a trouvé que c'est aussi un moyen inefficace de l'espace en raison de la taille inconnue de la chaîne et la largeur inconnue du document se traduira par la chaîne trop longue si trop de mots, ou trop court avec rien de juste justifié. Une justification complète signifie que le texte s'aligne à la fois sur le côté gauche et sur le côté droit, et généralement, le contenu à l'intérieur est aussi espacé que possible. C'est comme ça que je voudrais l'implémenter.

La solution, est probablement un calcul sur les chaînes lastWord et lineNoLastWord avec des mesures pour assurer la viabilité de la production en ce que pas deux mots dans la chaîne se déroulera ou tassent, et il n'y aura pas de rembourrage sur la côté droit, mais à gauche peut encore contenir un retrait ou une languette. Une autre partie à considérer est que si la chaîne est plus courte qu'un certain seuil, aucune justification ne doit être appliquée.

MISE À JOUR

J'ai le concept suivant, qui devrait fonctionner, juste besoin d'obtenir le mot de l'index spécifié et insérez l'espace approprié:

int lastwordwidth = (Int32)g.MeasureString(" " + lastword, Font).Width; 
int extraspace=lines.MaxWidthPixels-(Int32)(g.MeasureString(" "+lineNoLastWord, Font).Width+lastwordwidth); 
int totalspacesneeded = (Int32)Math.Floor((decimal)(extraspace/lines.SpaceWidth)); 
int spacecount = lineNoLastWord.Count(x => x == ' '); 
int currentwordspace = 0; 

for (int i=0; i<spacecount; i++) { 
    if (currentwordspace>spacecount) { currentwordspace = 0; } 
    // insert spaces where spaces already exist between each word 
    // use currentwordspace to determine which word to replace with a word and another space 

    if (currentwordspace==0) { 
     // insert space after word 
    } else { 
     // insert space before word 
    } 

    currentwordspace++; 
} 
+0

Avez-vous pas [ce] (http://csharphelper.com/blog/2014/10/fully-justify-a-line-of-text-in- c /)? Si vous calculez un flottant pour savoir jusqu'où se déplacer après __each__ mot, il répartit les mots __evenly__ sur la ligne .. – TaW

+0

Oui, j'ai vu que je suppose que je pourrais insérer de manière sélective des espaces qui seraient plus rapides et donc moins d'appels graphiques. –

+0

Eh bien vous pouvez ou mieux vous pouvez utiliser n-espaces mais vous aurez besoin de connaître leur largeur plutôt précisément, ce qui n'est pas aussi simple que cela puisse paraître, étant donné que la plupart des appels ne seront pas aussi précis que prévu. ne vous inquiétez pas des performances avant que vous n'ayez un problème. (Voir ['optimisation prématurée'] (http://c2.com/cgi/wiki?PrematureOptimization) ;-) – TaW

Répondre

0

je me suis dit une solution pour ce qui fonctionne très bien. Voici ma méthode DrawString qui prend en compte l'alignement du texte, et va casser et ajouter de nouvelles "Pages" selon les besoins. Pages, est un objet List<Image> et la méthode NewPage() est responsable de l'ajout d'une nouvelle image à cette liste.

/// <summary> 
/// Add a new string to the current page 
/// </summary> 
/// <param name="text">The string to print</param> 
/// <param name="align">Optional alignment of the string</param> 
public void DrawString(string text, System.Windows.TextAlignment align = System.Windows.TextAlignment.Left, int MaxWidth = -1) { 
    RectangleF textBounds; 
    SolidBrush brush = new SolidBrush(ForeColor); 
    StringFormat sf = ConvertAlignment(align); 
    LineBreaker lines = breakIntoLines(text, MaxWidth); 

    int currentLine = 1; 

    int originX = X; 

    foreach (string line in lines.Lines) { 
     // add string to document 
     using (Graphics g=Graphics.FromImage(Pages[CurrentPage - 1])) { 
      g.CompositingQuality = CompositingQuality.HighQuality; 

      textBounds=new RectangleF(X, Y, lines.MaxWidthPixels, lines.StringHeight); 

      if (align==System.Windows.TextAlignment.Justify) { 

       if (currentLine<lines.Lines.Count) { 
        string lastword=line.Split(' ').Last(); 
        if (line.Contains(' ')) { 
         // routine to caclulate how much padding is needed and apply the extra spaces as evenly as possibly by looping 
         // through the words. it starts at the first word adding a space after if needed and then continues through the 
         // remaining words adding a space before them as needed and excludes the right most word which is printed as right 
         // align always. 
         string lineNoLastWord=line.Substring(0, line.LastIndexOf(" ")).Trim(); 
         List<string> words=lineNoLastWord.Split(' ').ToList<string>(); 
         int lastwordwidth=(Int32)g.MeasureString(" "+lastword, Font).Width; 
         int extraspace=lines.MaxWidthPixels-(Int32)(g.MeasureString(" "+lineNoLastWord, Font).Width+lastwordwidth); 
         int totalspacesneeded=(Int32)Math.Ceiling((decimal)extraspace/(decimal)lines.SpaceWidth); 
         int spacecount=lineNoLastWord.Count(x => x==' '); 
         int currentwordspace=0; 

         if (words.Count>1) { 
          while (totalspacesneeded>0) { 
           if (currentwordspace>spacecount) { currentwordspace=0; } 
           // insert spaces where spaces already exist between each word 
           // use currentwordspace to determine which word to replace with a word and another space 
           if (currentwordspace==0) { 
            // insert space after word 
            words[currentwordspace]+=" "; 
           } else { 
            // insert space before word 
            words[currentwordspace]=" "+words[currentwordspace]; 
           } 
           currentwordspace++; 
           totalspacesneeded--; 
           if (totalspacesneeded==0) { break; } 
          } 
         } 
         lineNoLastWord=String.Join(" ", words); 

         g.DrawString(lineNoLastWord, Font, brush, textBounds, sf); 
         g.DrawString(lastword, Font, brush, textBounds, ConvertAlignment(System.Windows.TextAlignment.Right)); 
        } else { 
         // when only 1 word, just draw it 
         g.DrawString(line, Font, brush, textBounds, ConvertAlignment(System.Windows.TextAlignment.Left)); 
        } 
       } else { 
        // just draw the last line 
        g.DrawString(line, Font, brush, textBounds, ConvertAlignment(System.Windows.TextAlignment.Left)); 
       } 

      } else { 
       g.DrawString(line, Font, brush, textBounds, sf); 
      } 
     } 
     Y+=lines.LineHeight; 
     if (Y+lines.LineHeight>Pages[CurrentPage-1].Height) { 
      NewPage(); 
      if (currentLine<lines.Lines.Count) { X=originX; } 
     } 
     currentLine++; 
    } 
} 

/// <summary> 
/// Break a long string into multiple lines. Is also carriage return aware. 
/// </summary> 
/// <param name="s">the string</param> 
/// <param name="maxLineWidth">the maximum width of the rectangle. if -1, will use the full width of the image</param> 
/// <returns></returns> 
internal LineBreaker breakIntoLines(string s, int maxLineWidth) { 
    List<string> sResults = new List<string>(); 

    int stringHeight; 
    int lineHeight; 
    int maxWidthPixels = maxLineWidth; 
    int spaceWidth; 

    string[] lines = s.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.None); 
    using (Graphics g=Graphics.FromImage(Pages[CurrentPage - 1])) { 
     g.CompositingQuality = CompositingQuality.HighQuality; 
     if (maxLineWidth<=0||maxLineWidth>(Pages[CurrentPage-1].Width-X)) { 
      maxWidthPixels=Pages[CurrentPage-1].Width-X; 
     } 
     lineHeight = (Int32)(g.MeasureString("X", Font).Height*(float)((float)LineSpacing/(float)100)); 
     stringHeight = (Int32)g.MeasureString("X", Font).Height; 
     spaceWidth=(Int32)g.MeasureString(" ", Font).Width; 
     foreach (string line in lines) { 
      string[] words=line.Split(new string[] { " " }, StringSplitOptions.None); 
      sResults.Add(""); 
      for (int i=0; i<words.Length; i++) { 
       if (sResults[sResults.Count-1].Length==0) { 
        sResults[sResults.Count-1]=words[i]; 
       } else { 
        if (g.MeasureString(sResults[sResults.Count-1]+" "+words[i], Font).Width<maxWidthPixels) { 
         sResults[sResults.Count-1]+=" "+words[i]; 
        } else { 
         sResults.Add(words[i]); 
        } 
       } 
      } 
     } 
    } 
    return new LineBreaker() { 
     LineHeight = lineHeight, 
     StringHeight = stringHeight, 
     MaxWidthPixels = maxWidthPixels, 
     Lines = sResults, 
     SpaceWidth = spaceWidth 
    }; 
} 

/// <summary> 
/// Helper method to convert TextAlignment to StringFormat 
/// </summary> 
/// <param name="align">System.Windows.TextAlignment</param> 
/// <returns>System.Drawing.StringFormat</returns> 
private StringFormat ConvertAlignment(System.Windows.TextAlignment align) { 
    StringFormat s = new StringFormat(); 
    switch (align) { 
     case System.Windows.TextAlignment.Left: 
     case System.Windows.TextAlignment.Justify: 
      s.LineAlignment=StringAlignment.Near; 
      break; 
     case System.Windows.TextAlignment.Right: 
      s.LineAlignment=StringAlignment.Far; 
      break; 
     case System.Windows.TextAlignment.Center: 
      s.LineAlignment=StringAlignment.Center; 
      break; 
    } 
    s.Alignment = s.LineAlignment; 
    return s; 
} 

/// <summary> 
/// Class to hold the line data after broken up and measured using breakIntoLines() 
/// </summary> 
internal class LineBreaker { 
    public List<string> Lines { get; set; } 
    public int MaxWidthPixels { get; set; } 
    public int StringHeight { get; set; } 
    public int LineHeight { get; set; } 

    public int SpaceWidth { get; set; } 

    public LineBreaker() { 
     Lines = new List<string>(); 
     MaxWidthPixels = 0; 
     StringHeight = 0; 
     LineHeight = 0; 
     SpaceWidth = 0; 
    } 

    public LineBreaker(List<string> lines, int maxWidthPixels, int stringHeight, int lineHeight, int spaceWidth) { 
     Lines = lines; 
     MaxWidthPixels = maxWidthPixels; 
     LineHeight = lineHeight; 
     StringHeight = stringHeight; 
     SpaceWidth = spaceWidth; 
    } 
} 

La combinaison de la méthode ci-dessus prend en charge:

  • alignement gauche
  • d'alignement droit
  • d'alignement Centre
  • Justifier alignement - La dernière ligne envoyé avec Justifier, sera juste à gauche au tirage car c'est généralement la fin d'un paragraphe.
  • Toutes les lignes envoyées vérifient les contraintes à l'aide de l'image ou d'une largeur spécifiée comprise entre la position X actuelle et le bord. Les largeurs dépassant ou dans la plage négative seront définies à la distance entre X et le côté droit de l'image. Chaque ligne est dans sa propre boîte englobante.
  • Les lignes ne sont pas tronquées.
  • emballage ligne est rompue sur le mot selon les besoins et le retour chariot ("\ n" ou "\ r \ n")

LineSpacing est juste un nombre entier où 100 signifie 100% de LineHeight. X est un nombre entier permettant d'obtenir/de définir la position X. Y est un nombre entier pour obtenir/définir la position Y. Font est un getter/setter pour une System.Drawing.Font