1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: CharacterLayoutManager.java 1835810 2018-07-13 10:29:57Z ssteiner $ */
19 
20 package org.apache.fop.layoutmgr.inline;
21 
22 import java.util.LinkedList;
23 import java.util.List;
24 
25 import org.apache.fop.area.Trait;
26 import org.apache.fop.area.inline.InlineArea;
27 import org.apache.fop.area.inline.TextArea;
28 import org.apache.fop.fo.flow.Character;
29 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
30 import org.apache.fop.fonts.Font;
31 import org.apache.fop.fonts.FontSelector;
32 import org.apache.fop.layoutmgr.InlineKnuthSequence;
33 import org.apache.fop.layoutmgr.KnuthElement;
34 import org.apache.fop.layoutmgr.KnuthGlue;
35 import org.apache.fop.layoutmgr.KnuthPenalty;
36 import org.apache.fop.layoutmgr.KnuthSequence;
37 import org.apache.fop.layoutmgr.LayoutContext;
38 import org.apache.fop.layoutmgr.LeafPosition;
39 import org.apache.fop.layoutmgr.Position;
40 import org.apache.fop.layoutmgr.TraitSetter;
41 import org.apache.fop.traits.MinOptMax;
42 import org.apache.fop.traits.SpaceVal;
43 import org.apache.fop.util.CharUtilities;
44 
45 /**
46  * LayoutManager for the fo:character formatting object
47  */
48 public class CharacterLayoutManager extends LeafNodeLayoutManager {
49     private MinOptMax letterSpaceIPD;
50     private int hyphIPD;
51     private Font font;
52     private CommonBorderPaddingBackground borderProps;
53 
54     /**
55      * Constructor
56      *
57      * @param node the fo:character formatting object
58      */
CharacterLayoutManager(Character node)59     public CharacterLayoutManager(Character node) {
60         super(node);
61     }
62 
63     /** {@inheritDoc} */
64     @Override
initialize()65     public void initialize() {
66         Character fobj = (Character)this.fobj;
67         font = FontSelector.selectFontForCharacter(fobj, this);
68         SpaceVal ls = SpaceVal.makeLetterSpacing(fobj.getLetterSpacing());
69         letterSpaceIPD = ls.getSpace();
70         hyphIPD = fobj.getCommonHyphenation().getHyphIPD(font);
71         borderProps = fobj.getCommonBorderPaddingBackground();
72         setCommonBorderPaddingBackground(borderProps);
73     }
74 
createCharacterArea()75     private TextArea createCharacterArea() {
76         Character fobj = (Character) this.fobj;
77         TextArea text = new TextArea();
78         text.setChangeBarList(getChangeBarList());
79         char ch = fobj.getCharacter();
80         int ipd = font.getCharWidth(ch);
81         int blockProgressionOffset = 0;
82         int level = fobj.getBidiLevel();
83         if (CharUtilities.isAnySpace(ch)) {
84             // add space unless it's zero-width:
85             if (!CharUtilities.isZeroWidthSpace(ch)) {
86                 text.addSpace(ch, ipd, CharUtilities.isAdjustableSpace(ch),
87                               blockProgressionOffset, level);
88             }
89         } else {
90             int[] levels = (level >= 0) ? new int[] {level} : null;
91             text.addWord(String.valueOf(ch), ipd, null, levels, null, blockProgressionOffset);
92         }
93 
94         TraitSetter.setProducerID(text, fobj.getId());
95         TraitSetter.addTextDecoration(text, fobj.getTextDecoration());
96         text.setIPD(font.getCharWidth(fobj.getCharacter()));
97         text.setBPD(font.getAscender() - font.getDescender());
98         text.setBaselineOffset(font.getAscender());
99         TraitSetter.addFontTraits(text, font);
100         text.addTrait(Trait.COLOR, fobj.getColor());
101         return text;
102     }
103 
104     @Override
getEffectiveArea(LayoutContext layoutContext)105     protected InlineArea getEffectiveArea(LayoutContext layoutContext) {
106         InlineArea area = createCharacterArea();
107         if (!layoutContext.treatAsArtifact()) {
108             TraitSetter.addStructureTreeElement(area, ((Character) fobj).getStructureTreeElement());
109         }
110         return area;
111     }
112 
113     /** {@inheritDoc} */
getNextKnuthElements(LayoutContext context, int alignment)114     public List getNextKnuthElements(LayoutContext context, int alignment) {
115         Character fobj = (Character) this.fobj;
116 
117         // TODO: may need some special handling for fo:character
118         alignmentContext = new AlignmentContext(font
119                                     , font.getFontSize()
120                                     , fobj.getAlignmentAdjust()
121                                     , fobj.getAlignmentBaseline()
122                                     , fobj.getBaselineShift()
123                                     , fobj.getDominantBaseline()
124                                     , context.getAlignmentContext());
125 
126         KnuthSequence seq = new InlineKnuthSequence();
127         addKnuthElementsForBorderPaddingStart(seq);
128 
129         // create the AreaInfo object to store the computed values
130         MinOptMax ipd = MinOptMax.getInstance(font.getCharWidth(fobj.getCharacter()));
131         areaInfo = new AreaInfo((short) 0, ipd, false, alignmentContext);
132 
133         // node is a fo:Character
134         if (letterSpaceIPD.isStiff()) {
135             // constant letter space, only return a box
136             seq.add(new KnuthInlineBox(areaInfo.ipdArea.getOpt(), areaInfo.alignmentContext,
137                                         notifyPos(new LeafPosition(this, 0)), false));
138         } else {
139             // adjustable letter space, return a sequence of elements;
140             // at the moment the character is supposed to have no letter spaces,
141             // but returning this sequence allows us to change only one element
142             // if addALetterSpaceTo() is called
143             seq.add(new KnuthInlineBox(areaInfo.ipdArea.getOpt(), areaInfo.alignmentContext,
144                                         notifyPos(new LeafPosition(this, 0)), false));
145             seq.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
146                                             new LeafPosition(this, -1), true));
147             seq.add(new KnuthGlue(0, 0, 0,
148                                          new LeafPosition(this, -1), true));
149             seq.add(new KnuthInlineBox(0, null,
150                                         notifyPos(new LeafPosition(this, -1)), true));
151         }
152 
153         addKnuthElementsForBorderPaddingEnd(seq);
154 
155         LinkedList<KnuthSequence> returnList = new LinkedList<KnuthSequence>();
156         returnList.add(seq);
157         setFinished(true);
158         return returnList;
159     }
160 
161     /** {@inheritDoc} */
162     @Override
getWordChars(Position pos)163     public String getWordChars(Position pos) {
164         return String.valueOf(((Character) fobj).getCharacter());
165     }
166 
167     /** {@inheritDoc} */
168     @Override
hyphenate(Position pos, HyphContext hc)169     public void hyphenate(Position pos, HyphContext hc) {
170         if (hc.getNextHyphPoint() == 1) {
171             // the character ends a syllable
172             areaInfo.isHyphenated = true;
173             somethingChanged = true;
174         } else {
175             // hc.getNextHyphPoint() returned -1 (no more hyphenation points)
176             // or a number > 1;
177             // the character does not end a syllable
178         }
179         hc.updateOffset(1);
180     }
181 
182     /** {@inheritDoc} */
183     @Override
applyChanges(List oldList)184     public boolean applyChanges(List oldList) {
185         setFinished(false);
186         return somethingChanged;
187     }
188 
189     /** {@inheritDoc} */
190     @Override
getChangedKnuthElements(List oldList, int alignment)191     public List getChangedKnuthElements(List oldList, int alignment) {
192         if (isFinished()) {
193             return null;
194         }
195 
196         LinkedList<KnuthElement> returnList = new LinkedList<KnuthElement>();
197 
198         addKnuthElementsForBorderPaddingStart(returnList);
199 
200         if (letterSpaceIPD.isStiff() || areaInfo.letterSpaces == 0) {
201             // constant letter space, or no letter space
202             returnList.add(new KnuthInlineBox(areaInfo.ipdArea.getOpt(),
203                                         areaInfo.alignmentContext,
204                                         notifyPos(new LeafPosition(this, 0)), false));
205             if (areaInfo.isHyphenated) {
206                 returnList.add(new KnuthPenalty(hyphIPD, KnuthPenalty.FLAGGED_PENALTY, true,
207                         new LeafPosition(this, -1), false));
208             }
209         } else {
210             // adjustable letter space
211             returnList.add(new KnuthInlineBox(areaInfo.ipdArea.getOpt()
212                     - areaInfo.letterSpaces * letterSpaceIPD.getOpt(), areaInfo.alignmentContext,
213                     notifyPos(new LeafPosition(this, 0)), false));
214             returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, false,
215                     new LeafPosition(this, -1), true));
216             returnList.add(new KnuthGlue(letterSpaceIPD.mult(areaInfo.letterSpaces),
217                     new LeafPosition(this, -1), true));
218             returnList.add(
219                     new KnuthInlineBox(0, null, notifyPos(new LeafPosition(this, -1)), true));
220             if (areaInfo.isHyphenated) {
221                 returnList.add(new KnuthPenalty(hyphIPD, KnuthPenalty.FLAGGED_PENALTY, true,
222                         new LeafPosition(this, -1), false));
223             }
224         }
225 
226         addKnuthElementsForBorderPaddingEnd(returnList);
227 
228         setFinished(true);
229         return returnList;
230     }
231 
232 }
233