123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 |
- /* ====================================================================
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- ==================================================================== */
-
- package org.apache.poi.sl.draw;
-
- import java.awt.Dimension;
- import java.awt.Font;
- import java.awt.Graphics2D;
- import java.awt.Paint;
- import java.awt.font.FontRenderContext;
- import java.awt.font.LineBreakMeasurer;
- import java.awt.font.TextAttribute;
- import java.awt.font.TextLayout;
- import java.awt.geom.Rectangle2D;
- import java.io.InvalidObjectException;
- import java.text.AttributedCharacterIterator;
- import java.text.AttributedCharacterIterator.Attribute;
- import java.text.AttributedString;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Locale;
-
- import org.apache.poi.common.usermodel.fonts.FontGroup;
- import org.apache.poi.common.usermodel.fonts.FontGroup.FontGroupRange;
- import org.apache.poi.common.usermodel.fonts.FontInfo;
- import org.apache.poi.sl.usermodel.AutoNumberingScheme;
- import org.apache.poi.sl.usermodel.Hyperlink;
- import org.apache.poi.sl.usermodel.Insets2D;
- import org.apache.poi.sl.usermodel.PaintStyle;
- import org.apache.poi.sl.usermodel.PlaceableShape;
- import org.apache.poi.sl.usermodel.ShapeContainer;
- import org.apache.poi.sl.usermodel.Sheet;
- import org.apache.poi.sl.usermodel.Slide;
- import org.apache.poi.sl.usermodel.TextParagraph;
- import org.apache.poi.sl.usermodel.TextParagraph.BulletStyle;
- import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;
- import org.apache.poi.sl.usermodel.TextRun;
- import org.apache.poi.sl.usermodel.TextRun.FieldType;
- import org.apache.poi.sl.usermodel.TextShape;
- import org.apache.poi.sl.usermodel.TextShape.TextDirection;
- import org.apache.poi.util.Internal;
- import org.apache.poi.util.LocaleUtil;
- import org.apache.poi.util.POILogFactory;
- import org.apache.poi.util.POILogger;
- import org.apache.poi.util.Units;
-
- public class DrawTextParagraph implements Drawable {
- private static final POILogger LOG = POILogFactory.getLogger(DrawTextParagraph.class);
-
- /** Keys for passing hyperlinks to the graphics context */
- public static final XlinkAttribute HYPERLINK_HREF = new XlinkAttribute("href");
- public static final XlinkAttribute HYPERLINK_LABEL = new XlinkAttribute("label");
-
- protected TextParagraph<?,?,?> paragraph;
- double x, y;
- protected List<DrawTextFragment> lines = new ArrayList<>();
- protected String rawText;
- protected DrawTextFragment bullet;
- protected int autoNbrIdx;
- protected boolean firstParagraph = true;
-
- /**
- * Defines an attribute used for storing the hyperlink associated with
- * some renderable text.
- */
- private static class XlinkAttribute extends Attribute {
-
- XlinkAttribute(String name) {
- super(name);
- }
-
- /**
- * Resolves instances being deserialized to the predefined constants.
- */
- @Override
- protected Object readResolve() throws InvalidObjectException {
- if (HYPERLINK_HREF.getName().equals(getName())) {
- return HYPERLINK_HREF;
- }
- if (HYPERLINK_LABEL.getName().equals(getName())) {
- return HYPERLINK_LABEL;
- }
- throw new InvalidObjectException("unknown attribute name");
- }
- }
-
-
- public DrawTextParagraph(TextParagraph<?,?,?> paragraph) {
- this.paragraph = paragraph;
- }
-
- public void setPosition(double x, double y) {
- // TODO: replace it, by applyTransform????
- this.x = x;
- this.y = y;
- }
-
- public double getY() {
- return y;
- }
-
- /**
- * Sets the auto numbering index of the handled paragraph
- * @param index the auto numbering index
- */
- public void setAutoNumberingIdx(int index) {
- autoNbrIdx = index;
- }
-
- @Override
- public void draw(Graphics2D graphics){
- if (lines.isEmpty()) {
- return;
- }
-
- final boolean isHSLF = isHSLF();
-
- double penY = y;
-
- int indentLevel = paragraph.getIndentLevel();
- Double leftMargin = paragraph.getLeftMargin();
- if (leftMargin == null) {
- // if the marL attribute is omitted, then a value of 347663 is implied
- leftMargin = Units.toPoints(347663L*indentLevel);
- }
- Double indent = paragraph.getIndent();
- if (indent == null) {
- indent = Units.toPoints(347663L*indentLevel);
- }
-
- // Double rightMargin = paragraph.getRightMargin();
- // if (rightMargin == null) {
- // rightMargin = 0d;
- // }
-
- //The vertical line spacing
- Double spacing = paragraph.getLineSpacing();
- if (spacing == null) {
- spacing = 100d;
- }
-
- DrawTextFragment lastLine = null;
- for(DrawTextFragment line : lines){
- double penX;
-
-
- if (!(isFirstParagraph() && lastLine == null)) {
- // penY is now on descent line of the last text fragment
- // need to substract descent height to get back to the baseline of the last fragment
- // then add a multiple of the line height of the current text height
- penY -= line.getLeading() + ((lastLine == null) ? 0 : lastLine.getLayout().getDescent());
-
- if(spacing > 0) {
- // If linespacing >= 0, then linespacing is a percentage of normal line height.
- penY += (spacing*0.01) * line.getHeight(); // + (isHSLF ? line.getLayout().getLeading() : 0));
- } else {
- // negative value means absolute spacing in points
- penY += -spacing;
- }
- penY -= line.getLayout().getAscent();
- }
-
- penX = x + (isHSLF ? leftMargin : leftMargin);
- if (lastLine == null) {
- if (!isEmptyParagraph()) {
- // TODO: find out character style for empty, but bulleted/numbered lines
- bullet = getBullet(graphics, line.getAttributedString().getIterator());
- }
-
- if (bullet != null) {
- bullet.setPosition(isHSLF ? x+indent : x+leftMargin+indent, penY);
- bullet.draw(graphics);
- // don't let text overlay the bullet and advance by the bullet width
- double bulletWidth = bullet.getLayout().getAdvance() + 1;
- penX = x + (isHSLF ? leftMargin : Math.max(leftMargin, leftMargin+indent+bulletWidth));
- }
- }
-
- Rectangle2D anchor = DrawShape.getAnchor(graphics, paragraph.getParentShape());
- // Insets are already applied on DrawTextShape.drawContent
- // but (outer) anchor need to be adjusted
- Insets2D insets = paragraph.getParentShape().getInsets();
- double leftInset = insets.left;
- double rightInset = insets.right;
-
- TextAlign ta = paragraph.getTextAlign();
- if (ta == null) {
- ta = TextAlign.LEFT;
- }
- switch (ta) {
- case CENTER:
- penX += (anchor.getWidth() - line.getWidth() - leftInset - rightInset - leftMargin) / 2;
- break;
- case RIGHT:
- penX += (anchor.getWidth() - line.getWidth() - leftInset - rightInset);
- break;
- default:
- break;
- }
-
- line.setPosition(penX, penY);
- line.draw(graphics);
- penY += line.getHeight();
-
- lastLine = line;
- }
-
- y = penY - y;
- }
-
- public float getFirstLineLeading() {
- return (lines.isEmpty()) ? 0 : lines.get(0).getLeading();
- }
-
- public float getFirstLineHeight() {
- return (lines.isEmpty()) ? 0 : lines.get(0).getHeight();
- }
-
- public float getLastLineHeight() {
- return (lines.isEmpty()) ? 0 : lines.get(lines.size()-1).getHeight();
- }
-
- public boolean isEmptyParagraph() {
- return (lines.isEmpty() || rawText.trim().isEmpty());
- }
-
- @Override
- public void applyTransform(Graphics2D graphics) {
- }
-
- @Override
- public void drawContent(Graphics2D graphics) {
- }
-
- /**
- * break text into lines, each representing a line of text that fits in the wrapping width
- *
- * @param graphics The drawing context for computing text-lengths.
- */
- protected void breakText(Graphics2D graphics){
- lines.clear();
-
- DrawFactory fact = DrawFactory.getInstance(graphics);
- StringBuilder text = new StringBuilder();
- AttributedString at = getAttributedString(graphics, text);
-
- AttributedCharacterIterator it = at.getIterator();
- LineBreakMeasurer measurer = new LineBreakMeasurer(it, graphics.getFontRenderContext());
- for (;;) {
- int startIndex = measurer.getPosition();
-
- // add a pixel to compensate rounding errors
- double wrappingWidth = getWrappingWidth(lines.isEmpty(), graphics) + 1;
- // shape width can be smaller that the sum of insets (this was proved by a test file)
- if(wrappingWidth < 0) {
- wrappingWidth = 1;
- }
-
- // usually "\n" is added after a line, if it occurs before it - only possible as first char -
- // we need to add an empty line
- TextLayout layout;
- int endIndex;
- if (startIndex == 0 && text.toString().startsWith("\n")) {
- layout = measurer.nextLayout((float) wrappingWidth, 1, false);
- endIndex = 1;
- } else {
- int nextBreak = text.indexOf("\n", startIndex + 1);
- if (nextBreak == -1) {
- nextBreak = it.getEndIndex();
- }
-
- layout = measurer.nextLayout((float) wrappingWidth, nextBreak, true);
- if (layout == null) {
- // layout can be null if the entire word at the current position
- // does not fit within the wrapping width. Try with requireNextWord=false.
- layout = measurer.nextLayout((float) wrappingWidth, nextBreak, false);
- }
-
- if (layout == null) {
- // exit if can't break any more
- break;
- }
-
- endIndex = measurer.getPosition();
- // skip over new line breaks (we paint 'clear' text runs not starting or ending with \n)
- if (endIndex < it.getEndIndex() && text.charAt(endIndex) == '\n') {
- measurer.setPosition(endIndex + 1);
- }
-
- TextAlign hAlign = paragraph.getTextAlign();
- if (hAlign == TextAlign.JUSTIFY || hAlign == TextAlign.JUSTIFY_LOW) {
- layout = layout.getJustifiedLayout((float) wrappingWidth);
- }
- }
-
- AttributedString str = new AttributedString(it, startIndex, endIndex);
- DrawTextFragment line = fact.getTextFragment(layout, str);
- lines.add(line);
-
- if(endIndex == it.getEndIndex()) {
- break;
- }
- }
-
- rawText = text.toString();
- }
-
- protected DrawTextFragment getBullet(Graphics2D graphics, AttributedCharacterIterator firstLineAttr) {
- BulletStyle bulletStyle = paragraph.getBulletStyle();
- if (bulletStyle == null) {
- return null;
- }
-
- String buCharacter;
- AutoNumberingScheme ans = bulletStyle.getAutoNumberingScheme();
- if (ans != null) {
- buCharacter = ans.format(autoNbrIdx);
- } else {
- buCharacter = bulletStyle.getBulletCharacter();
- }
- if (buCharacter == null) {
- return null;
- }
-
- PlaceableShape<?,?> ps = getParagraphShape();
- PaintStyle fgPaintStyle = bulletStyle.getBulletFontColor();
- Paint fgPaint;
- if (fgPaintStyle == null) {
- fgPaint = (Paint)firstLineAttr.getAttribute(TextAttribute.FOREGROUND);
- } else {
- fgPaint = new DrawPaint(ps).getPaint(graphics, fgPaintStyle);
- }
-
- float fontSize = (Float)firstLineAttr.getAttribute(TextAttribute.SIZE);
- Double buSz = bulletStyle.getBulletFontSize();
- if (buSz == null) {
- buSz = 100d;
- }
- if (buSz > 0) {
- fontSize *= buSz* 0.01;
- } else {
- fontSize = (float)-buSz;
- }
-
- String buFontStr = bulletStyle.getBulletFont();
- if (buFontStr == null) {
- buFontStr = paragraph.getDefaultFontFamily();
- }
- assert(buFontStr != null);
- FontInfo buFont = new DrawFontInfo(buFontStr);
-
-
- DrawFontManager dfm = DrawFactory.getInstance(graphics).getFontManager(graphics);
- // TODO: check font group defaulting to Symbol
- buFont = dfm.getMappedFont(graphics, buFont);
-
- AttributedString str = new AttributedString(dfm.mapFontCharset(graphics,buFont,buCharacter));
- str.addAttribute(TextAttribute.FOREGROUND, fgPaint);
- str.addAttribute(TextAttribute.FAMILY, buFont.getTypeface());
- str.addAttribute(TextAttribute.SIZE, fontSize);
-
- TextLayout layout = new TextLayout(str.getIterator(), graphics.getFontRenderContext());
- DrawFactory fact = DrawFactory.getInstance(graphics);
- return fact.getTextFragment(layout, str);
- }
-
- protected String getRenderableText(Graphics2D graphics, TextRun tr) {
- if (tr.getFieldType() == FieldType.SLIDE_NUMBER) {
- Slide<?,?> slide = (Slide<?,?>)graphics.getRenderingHint(Drawable.CURRENT_SLIDE);
- return (slide == null) ? "" : Integer.toString(slide.getSlideNumber());
- }
- return getRenderableText(tr);
- }
-
- @Internal
- public String getRenderableText(final TextRun tr) {
- String txtSpace = tr.getRawText();
- if (txtSpace == null) {
- return null;
- }
- if (txtSpace.contains("\t")) {
- txtSpace = txtSpace.replace("\t", tab2space(tr));
- }
- txtSpace = txtSpace.replace('\u000b', '\n');
- final Locale loc = LocaleUtil.getUserLocale();
-
- switch (tr.getTextCap()) {
- case ALL:
- return txtSpace.toUpperCase(loc);
- case SMALL:
- return txtSpace.toLowerCase(loc);
- default:
- return txtSpace;
- }
- }
-
- /**
- * Replace a tab with the effective number of white spaces.
- */
- private String tab2space(TextRun tr) {
- AttributedString string = new AttributedString(" ");
- String fontFamily = tr.getFontFamily();
- if (fontFamily == null) {
- fontFamily = "Lucida Sans";
- }
- string.addAttribute(TextAttribute.FAMILY, fontFamily);
-
- Double fs = tr.getFontSize();
- if (fs == null) {
- fs = 12d;
- }
- string.addAttribute(TextAttribute.SIZE, fs.floatValue());
-
- TextLayout l = new TextLayout(string.getIterator(), new FontRenderContext(null, true, true));
- // some JDK versions return 0 here
- double wspace = l.getAdvance();
-
- final int numSpaces;
- Double tabSz = paragraph.getDefaultTabSize();
- if (wspace <= 0) {
- numSpaces = 4;
- } else {
- if (tabSz == null) {
- tabSz = wspace*4;
- }
- numSpaces = (int)Math.min(Math.ceil(tabSz / wspace), 20);
- }
-
- char[] buf = new char[numSpaces];
- Arrays.fill(buf, ' ');
- return new String(buf);
- }
-
-
- /**
- * Returns wrapping width to break lines in this paragraph
- *
- * @param firstLine whether the first line is breaking
- *
- * @return wrapping width in points
- */
- protected double getWrappingWidth(boolean firstLine, Graphics2D graphics){
- final long TAB_SIZE = 347663L;
- TextShape<?,?> ts = paragraph.getParentShape();
-
- // internal margins for the text box
- Insets2D insets = ts.getInsets();
- double leftInset = insets.left;
- double rightInset = insets.right;
-
- int indentLevel = paragraph.getIndentLevel();
- if (indentLevel == -1) {
- // default to 0, if indentLevel is not set
- indentLevel = 0;
- }
- Double leftMargin = paragraph.getLeftMargin();
- if (leftMargin == null) {
- // if the marL attribute is omitted, then a value of 347663 is implied
- leftMargin = Units.toPoints(TAB_SIZE * indentLevel);
- }
- Double indent = paragraph.getIndent();
- if (indent == null) {
- indent = 0.;
- }
- Double rightMargin = paragraph.getRightMargin();
- if (rightMargin == null) {
- rightMargin = 0d;
- }
-
- Rectangle2D anchor = DrawShape.getAnchor(graphics, ts);
- TextDirection textDir = ts.getTextDirection();
- double width;
- if (!ts.getWordWrap()) {
- Dimension pageDim = ts.getSheet().getSlideShow().getPageSize();
- // if wordWrap == false then we return the advance to the (right) border of the sheet
- switch (textDir) {
- default:
- width = pageDim.getWidth() - anchor.getX();
- break;
- case VERTICAL:
- width = pageDim.getHeight() - anchor.getX();
- break;
- case VERTICAL_270:
- width = anchor.getX();
- break;
- }
- } else {
- switch (textDir) {
- default:
- width = anchor.getWidth() - leftInset - rightInset - leftMargin - rightMargin;
- break;
- case VERTICAL:
- case VERTICAL_270:
- width = anchor.getHeight() - leftInset - rightInset - leftMargin - rightMargin;
- break;
- }
- if (firstLine && bullet == null) {
- // indent is usually negative in XSLF
- width += isHSLF() ? (leftMargin - indent) : -indent;
- }
- }
-
- return width;
- }
-
- private static class AttributedStringData {
- Attribute attribute;
- Object value;
- int beginIndex, endIndex;
- AttributedStringData(Attribute attribute, Object value, int beginIndex, int endIndex) {
- this.attribute = attribute;
- this.value = value;
- this.beginIndex = beginIndex;
- this.endIndex = endIndex;
- }
- }
-
- /**
- * Helper method for paint style relative to bounds, e.g. gradient paint
- */
- @SuppressWarnings("rawtypes")
- private PlaceableShape<?,?> getParagraphShape() {
- return new PlaceableShape(){
- @Override
- public ShapeContainer<?,?> getParent() { return null; }
- @Override
- public Rectangle2D getAnchor() { return paragraph.getParentShape().getAnchor(); }
- @Override
- public void setAnchor(Rectangle2D anchor) {}
- @Override
- public double getRotation() { return 0; }
- @Override
- public void setRotation(double theta) {}
- @Override
- public void setFlipHorizontal(boolean flip) {}
- @Override
- public void setFlipVertical(boolean flip) {}
- @Override
- public boolean getFlipHorizontal() { return false; }
- @Override
- public boolean getFlipVertical() { return false; }
- @Override
- public Sheet<?,?> getSheet() { return paragraph.getParentShape().getSheet(); }
- };
- }
-
- protected AttributedString getAttributedString(Graphics2D graphics, StringBuilder text){
- List<AttributedStringData> attList = new ArrayList<>();
- if (text == null) {
- text = new StringBuilder();
- }
-
- final DrawPaint dp = new DrawPaint(getParagraphShape());
- DrawFontManager dfm = DrawFactory.getInstance(graphics).getFontManager(graphics);
- assert(dfm != null);
-
- for (TextRun run : paragraph){
- String runText = getRenderableText(graphics, run);
- // skip empty runs
- if (runText.isEmpty()) {
- continue;
- }
-
- // user can pass an custom object to convert fonts
-
- runText = dfm.mapFontCharset(graphics, run.getFontInfo(null), runText);
- int beginIndex = text.length();
- text.append(runText);
- int endIndex = text.length();
-
- PaintStyle fgPaintStyle = run.getFontColor();
- Paint fgPaint = dp.getPaint(graphics, fgPaintStyle);
- attList.add(new AttributedStringData(TextAttribute.FOREGROUND, fgPaint, beginIndex, endIndex));
-
- Double fontSz = run.getFontSize();
- if (fontSz == null) {
- fontSz = paragraph.getDefaultFontSize();
- }
- attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz.floatValue(), beginIndex, endIndex));
-
- if(run.isBold()) {
- attList.add(new AttributedStringData(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, beginIndex, endIndex));
- }
- if(run.isItalic()) {
- attList.add(new AttributedStringData(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, beginIndex, endIndex));
- }
- if(run.isUnderlined()) {
- attList.add(new AttributedStringData(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, beginIndex, endIndex));
- attList.add(new AttributedStringData(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_TWO_PIXEL, beginIndex, endIndex));
- }
- if(run.isStrikethrough()) {
- attList.add(new AttributedStringData(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON, beginIndex, endIndex));
- }
- if(run.isSubscript()) {
- attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB, beginIndex, endIndex));
- }
- if(run.isSuperscript()) {
- attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex));
- }
-
- Hyperlink<?,?> hl = run.getHyperlink();
- if (hl != null) {
- attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex));
- attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex));
- }
-
- processGlyphs(graphics, dfm, attList, beginIndex, run, runText);
- }
-
- // ensure that the paragraph contains at least one character
- // We need this trick to correctly measure text
- if (text.length() == 0) {
- Double fontSz = paragraph.getDefaultFontSize();
- text.append(" ");
- attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz.floatValue(), 0, 1));
- }
-
- AttributedString string = new AttributedString(text.toString());
- for (AttributedStringData asd : attList) {
- string.addAttribute(asd.attribute, asd.value, asd.beginIndex, asd.endIndex);
- }
-
- return string;
- }
-
- /**
- * Processing the glyphs is done in two steps.
- * <li>determine the font group - a text run can have different font groups. Depending on the chars,
- * the correct font group needs to be used
- *
- * @see <a href="https://blogs.msdn.microsoft.com/officeinteroperability/2013/04/22/office-open-xml-themes-schemes-and-fonts/">Office Open XML Themes, Schemes, and Fonts</a>
- */
- private void processGlyphs(Graphics2D graphics, DrawFontManager dfm, List<AttributedStringData> attList, final int beginIndex, TextRun run, String runText) {
- // determine font group ranges of the textrun to focus the fallback handling only on that font group
- List<FontGroupRange> ttrList = FontGroup.getFontGroupRanges(runText);
- int rangeBegin = 0;
- for (FontGroupRange ttr : ttrList) {
- FontInfo fiRun = run.getFontInfo(ttr.getFontGroup());
- if (fiRun == null) {
- // if the font group specific font wasn't defined, fallback to LATIN
- fiRun = run.getFontInfo(FontGroup.LATIN);
- }
- FontInfo fiMapped = dfm.getMappedFont(graphics, fiRun);
- FontInfo fiFallback = dfm.getFallbackFont(graphics, fiRun);
- assert(fiFallback != null);
- if (fiMapped == null) {
- fiMapped = dfm.getMappedFont(graphics, new DrawFontInfo(paragraph.getDefaultFontFamily()));
- }
- if (fiMapped == null) {
- fiMapped = fiFallback;
- }
-
- Font fontMapped = dfm.createAWTFont(graphics, fiMapped, 10, run.isBold(), run.isItalic());
- Font fontFallback = dfm.createAWTFont(graphics, fiFallback, 10, run.isBold(), run.isItalic());
-
- // check for unsupported characters and add a fallback font for these
- final int rangeLen = ttr.getLength();
- int partEnd = rangeBegin;
- while (partEnd<rangeBegin+rangeLen) {
- // start with the assumption that the font is able to display the chars
- int partBegin = partEnd;
- partEnd = nextPart(fontMapped, runText, partBegin, rangeBegin+rangeLen, true);
-
- // Now we have 3 cases:
- // (a) the first part couldn't be displayed,
- // (b) only part of the text run could be displayed
- // (c) or all chars can be displayed (default)
-
- if (partBegin < partEnd) {
- // handle (b) and (c)
- attList.add(new AttributedStringData(TextAttribute.FAMILY, fontMapped.getFontName(Locale.ROOT), beginIndex+partBegin, beginIndex+partEnd));
- if (LOG.check(POILogger.DEBUG)) {
- LOG.log(POILogger.DEBUG, "mapped: ",fontMapped.getFontName(Locale.ROOT)," ",(beginIndex+partBegin)," ",(beginIndex+partEnd)," - ",runText.substring(beginIndex+partBegin, beginIndex+partEnd));
- }
- }
-
- // fallback for unsupported glyphs
- partBegin = partEnd;
- partEnd = nextPart(fontMapped, runText, partBegin, rangeBegin+rangeLen, false);
-
- if (partBegin < partEnd) {
- // handle (a) and (b)
- attList.add(new AttributedStringData(TextAttribute.FAMILY, fontFallback.getFontName(Locale.ROOT), beginIndex+partBegin, beginIndex+partEnd));
- if (LOG.check(POILogger.DEBUG)) {
- LOG.log(POILogger.DEBUG, "fallback: ",fontFallback.getFontName(Locale.ROOT)," ",(beginIndex+partBegin)," ",(beginIndex+partEnd)," - ",runText.substring(beginIndex+partBegin, beginIndex+partEnd));
- }
- }
- }
-
- rangeBegin += rangeLen;
- }
- }
-
- private static int nextPart(Font fontMapped, String runText, int beginPart, int endPart, boolean isDisplayed) {
- int rIdx = beginPart;
- while (rIdx < endPart) {
- int codepoint = runText.codePointAt(rIdx);
- if (fontMapped.canDisplay(codepoint) != isDisplayed) {
- break;
- }
- rIdx += Character.charCount(codepoint);
- }
- return rIdx;
- }
-
- /**
- * @return {@code true} if the HSLF implementation is used
- */
- protected boolean isHSLF() {
- return DrawShape.isHSLF(paragraph.getParentShape());
- }
-
- protected boolean isFirstParagraph() {
- return firstParagraph;
- }
-
- protected void setFirstParagraph(boolean firstParagraph) {
- this.firstParagraph = firstParagraph;
- }
- }
|