'How do you properly use PDPageContentStream::setTextRise?

Using PDFBox, given data notated like this: [G]Glory be to [D]God [Em]the [C]Father,\n[G]And to [A]Christ the [D]Son,, I am creating a guitar chord sheet like this:

enter image description here

My approach was to iterate through each character in the song and check the current index against the map.. whenever the map has an entry to that character index, we "jump" to the line above, write the chord, then jump back down.

The method setTextRise looked promising, but still processes the horizontal spacing incorrectly:

enter image description here

Here's an SSCCE (needs PDFBox libraries) that produces the PDF above:

public static void main(String[] args) {
    try {
        
        String extracted_text = "Capo 1\n\n1\n[G]Glory be to [D]God [Em]the [C]Father,\n[G]And to [A]Christ the [D]Son,\n[B7]Glory to the [Em]Holy [C]Spirit—\n[D-D7]Ever [ G]One.\n\n2\nAs we view the vast creation,\nPlanned with wondrous skill,\nSo our hearts would move to worship,\nAnd be still.\n\n3\nBut, our God, how great Thy yearning\nTo have sons who love\nIn the Son e’en now to praise Thee,\nLove to prove!\n\n4\n’Twas Thy thought in revelation,\nTo present to men\nSecrets of Thine own affections,\nTheirs to win.\n\n5\nSo in Christ, through His redemption\n(Vanquished evil powers!)\nThou hast brought, in new creation,\nWorshippers!\n\n6\nGlory be to God the Father,\nAnd to Christ the Son,\nGlory to the Holy Spirit—\nEver One.\n".replaceAll("\n", "\r");
        
        String[] lines = extracted_text.split("\\r");
        
        ArrayList<SongLine> songlines = new ArrayList<>();
        for(String s : lines) {
            LinkedHashMap<Integer, String> chords = new LinkedHashMap();
            StringBuilder line = new StringBuilder();
            StringBuilder currentchord = null;
            int index = 0;
            for(char c : s.toCharArray()) {
                if(currentchord != null) {
                    if(c == ']') {
                        chords.put(index, currentchord.toString());
                        currentchord = null;
                    } else {
                        currentchord.append(c);
                    }
                } else {
                    if(c == '[') {
                        currentchord = new StringBuilder();
                    } else {
                        line.append(c);
                        index++;
                    }
                }
            }
            
            SongLine sl = new SongLine();
            if(chords.size() > 0)
                sl.char_index_to_chords = chords;
            sl.line = line.toString();
            
            songlines.add(sl);
        }
        
        try (PDDocument doc = new PDDocument()) {
            PDPage page = new PDPage();
            PDPageContentStream pcs = new PDPageContentStream(doc, page);
            int firstLineX = 25;
            int firstLineY = 700;
            boolean first = true;

            float leading = 14.5f;
            pcs.beginText();
            pcs.newLineAtOffset(firstLineX, firstLineY);
            pcs.setFont(PDType1Font.TIMES_ROMAN, 12);
            pcs.setLeading(leading);
            for(SongLine line : songlines) {
                if(line.char_index_to_chords != null)
                    System.out.println(line.char_index_to_chords.toString());
                System.out.println(line.line);
                if(!first) {
                    pcs.newLine();
                }
                first = false;
                if(line.char_index_to_chords != null) {
                    pcs.newLine();
                }
                for(int i = 0; i < line.line.length(); i++) {
                    pcs.showText(String.valueOf(line.line.charAt(i)));
                    if(line.char_index_to_chords != null && line.char_index_to_chords.containsKey(i)) {
                        
                        pcs.setTextRise(12);
                        pcs.showText(line.char_index_to_chords.get(i));
                        pcs.setTextRise(0);
                    }
                }
            }
            pcs.endText();
            pcs.close();
            doc.addPage(page);
            String path = "0001.pdf";
            doc.save(path);
            
            Desktop.getDesktop().open(new File(path));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}


static class SongLine {
    Map<Integer, String> char_index_to_chords;
    String line;
}

What would you do in PDFBox to create the text aligned with chords (like in the first image)?



Solution 1:[1]

I got it. The answer was not setTextRise, rather newLineAtOffset while using getStringWidth to calculate font size:

for(SongLine line : songlines) {
    if(!first) {
        pcs.newLine();
    }
    first = false;
    if(line.char_index_to_chords != null) {
        float offset = 0;
        for(Entry<Integer, String> entry : line.char_index_to_chords.entrySet()) {
            float offsetX = font.getStringWidth(line.char_index_to_leading_lyrics.get(entry.getKey())) / (float)1000 * fontSize;
            pcs.newLineAtOffset(offsetX, 0);
            offset += offsetX;
            pcs.showText(entry.getValue());
        }
        pcs.newLineAtOffset(-offset, -leading);
    }
    pcs.showText(line.line);
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ryvantage