Skip to content

Commit b4dcc1d

Browse files
committed
IndigenousTextOverlayRenderer
1 parent 26a84e4 commit b4dcc1d

1 file changed

Lines changed: 126 additions & 11 deletions

File tree

api/src/main/java/ca/bc/gov/educ/grad/report/dto/reports/jasper/impl/IndigenousTextOverlayRenderer.java

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import java.awt.RenderingHints;
2525
import java.awt.font.FontRenderContext;
2626
import java.awt.font.LineBreakMeasurer;
27+
import java.awt.font.TextHitInfo;
2728
import java.awt.font.TextLayout;
29+
import java.awt.geom.Rectangle2D;
2830
import java.awt.image.BufferedImage;
2931
import java.io.ByteArrayOutputStream;
3032
import java.io.IOException;
@@ -47,6 +49,7 @@ final class IndigenousTextOverlayRenderer {
4749

4850
private static final Logger LOG = Logger.getLogger(IndigenousTextOverlayRenderer.class.getName());
4951
private static final int IMAGE_SCALE = 4;
52+
private static final int COMBINING_COMMA_ABOVE = 0x0313;
5053
private static final String FONT_BC_SANS = "BCSans";
5154
private static final Map<String, Font> FONT_CACHE = new ConcurrentHashMap<>();
5255

@@ -160,9 +163,9 @@ private static void drawText(final Graphics2D graphics, final JRPrintText textEl
160163
float y = topPadding + verticalOffset(textElement, contentHeight, totalHeight);
161164

162165
for (final LineLayout line : lines) {
163-
y += line.layout.getAscent();
164-
line.layout.draw(graphics, leftPadding + horizontalOffset(textElement, contentWidth, line), y);
165-
y += line.layout.getDescent() + line.layout.getLeading();
166+
y += line.ascent();
167+
line.draw(graphics, leftPadding + horizontalOffset(textElement, contentWidth, line), y);
168+
y += line.descent() + line.leading();
166169
}
167170
}
168171

@@ -183,9 +186,12 @@ private static List<LineLayout> layoutText(final String text, final Font font, f
183186
final int end = paragraph.length();
184187

185188
while (measurer.getPosition() < end) {
186-
final TextLayout layout = measurer.nextLayout(width);
189+
final int start = measurer.getPosition();
190+
measurer.nextLayout(width);
191+
final int lineEnd = measurer.getPosition();
187192
final boolean lastLine = measurer.getPosition() >= end;
188-
lines.add(new LineLayout(justify(layout, width, lastLine, textElement)));
193+
final String lineText = paragraph.substring(start, lineEnd);
194+
lines.add(new LineLayout(lineText, font, frc, width, lastLine, textElement));
189195
}
190196
}
191197

@@ -198,7 +204,8 @@ private static Font fitSingleLineFont(final String text, final Font font, final
198204
return font;
199205
}
200206

201-
final TextLayout layout = new TextLayout(text, font, frc);
207+
final String baseText = stripManualMarks(text).text;
208+
final TextLayout layout = new TextLayout(baseText.isEmpty() ? " " : baseText, font, frc);
202209
final float layoutHeight = layout.getAscent() + layout.getDescent() + layout.getLeading();
203210
final float widthScale = layout.getAdvance() <= 0f ? 1f : width / layout.getAdvance();
204211
final float heightScale = layoutHeight <= 0f ? 1f : height / layoutHeight;
@@ -222,10 +229,10 @@ private static float horizontalOffset(final JRPrintText textElement, final float
222229
final HorizontalTextAlignEnum alignment = textElement.getHorizontalTextAlign();
223230

224231
if (alignment == HorizontalTextAlignEnum.CENTER) {
225-
return Math.max(0f, (width - line.layout.getAdvance()) / 2f);
232+
return Math.max(0f, (width - line.advance()) / 2f);
226233
}
227234
if (alignment == HorizontalTextAlignEnum.RIGHT) {
228-
return Math.max(0f, width - line.layout.getAdvance());
235+
return Math.max(0f, width - line.advance());
229236
}
230237
return 0f;
231238
}
@@ -345,15 +352,123 @@ private static Color hiddenTextColor(final JRPrintText textElement) {
345352
return Color.WHITE;
346353
}
347354

355+
private static StrippedText stripManualMarks(final String text) {
356+
final StringBuilder stripped = new StringBuilder();
357+
final List<ManualMark> marks = new ArrayList<>();
358+
int previousBaseOffset = -1;
359+
360+
for (int offset = 0; offset < text.length();) {
361+
final int codePoint = text.codePointAt(offset);
362+
final int charCount = Character.charCount(codePoint);
363+
364+
if (codePoint == COMBINING_COMMA_ABOVE) {
365+
marks.add(new ManualMark(previousBaseOffset, codePoint));
366+
} else {
367+
final int strippedOffset = stripped.length();
368+
stripped.appendCodePoint(codePoint);
369+
if (!isCombiningMark(codePoint)) {
370+
previousBaseOffset = strippedOffset;
371+
}
372+
}
373+
374+
offset += charCount;
375+
}
376+
377+
return new StrippedText(stripped.toString(), marks);
378+
}
379+
380+
private static boolean isCombiningMark(final int codePoint) {
381+
final int type = Character.getType(codePoint);
382+
return type == Character.NON_SPACING_MARK
383+
|| type == Character.COMBINING_SPACING_MARK
384+
|| type == Character.ENCLOSING_MARK;
385+
}
386+
387+
private static final class StrippedText {
388+
private final String text;
389+
private final List<ManualMark> marks;
390+
391+
private StrippedText(final String text, final List<ManualMark> marks) {
392+
this.text = text;
393+
this.marks = marks;
394+
}
395+
}
396+
397+
private static final class ManualMark {
398+
private final int previousBaseOffset;
399+
private final int codePoint;
400+
401+
private ManualMark(final int previousBaseOffset, final int codePoint) {
402+
this.previousBaseOffset = previousBaseOffset;
403+
this.codePoint = codePoint;
404+
}
405+
}
406+
348407
private static final class LineLayout {
349-
private final TextLayout layout;
408+
private final TextLayout baseLayout;
409+
private final List<ManualMark> marks;
410+
private final Font font;
411+
private final FontRenderContext fontRenderContext;
350412

351413
private LineLayout(final TextLayout layout) {
352-
this.layout = layout;
414+
this.baseLayout = layout;
415+
this.marks = new ArrayList<>();
416+
this.font = null;
417+
this.fontRenderContext = null;
418+
}
419+
420+
private LineLayout(final String text, final Font font, final FontRenderContext fontRenderContext,
421+
final float width, final boolean lastLine, final JRPrintText textElement) {
422+
final StrippedText stripped = stripManualMarks(text);
423+
final String baseText = stripped.text.isEmpty() ? " " : stripped.text;
424+
this.baseLayout = justify(new TextLayout(baseText, font, fontRenderContext),
425+
width, lastLine, textElement);
426+
this.marks = stripped.marks;
427+
this.font = font;
428+
this.fontRenderContext = fontRenderContext;
429+
}
430+
431+
private void draw(final Graphics2D graphics, final float x, final float baseline) {
432+
baseLayout.draw(graphics, x, baseline);
433+
if (marks.isEmpty() || font == null || fontRenderContext == null) {
434+
return;
435+
}
436+
437+
for (final ManualMark mark : marks) {
438+
if (mark.previousBaseOffset < 0 || mark.previousBaseOffset >= baseLayout.getCharacterCount()) {
439+
continue;
440+
}
441+
442+
final TextLayout markLayout = new TextLayout(new String(Character.toChars(mark.codePoint)),
443+
font, fontRenderContext);
444+
final Rectangle2D markBounds = markLayout.getBounds();
445+
final float[] before = baseLayout.getCaretInfo(TextHitInfo.beforeOffset(mark.previousBaseOffset));
446+
final float[] after = baseLayout.getCaretInfo(TextHitInfo.afterOffset(mark.previousBaseOffset));
447+
final float baseGlyphCenter = (before[0] + after[0]) / 2f;
448+
final float markX = x + baseGlyphCenter - (float) markBounds.getCenterX();
449+
450+
markLayout.draw(graphics, markX, baseline);
451+
}
452+
}
453+
454+
private float advance() {
455+
return baseLayout.getAdvance();
456+
}
457+
458+
private float ascent() {
459+
return baseLayout.getAscent();
460+
}
461+
462+
private float descent() {
463+
return baseLayout.getDescent();
464+
}
465+
466+
private float leading() {
467+
return baseLayout.getLeading();
353468
}
354469

355470
private float height() {
356-
return layout.getAscent() + layout.getDescent() + layout.getLeading();
471+
return ascent() + descent() + leading();
357472
}
358473
}
359474
}

0 commit comments

Comments
 (0)