You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GlyphMapping.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. /* $Id$ */
  18. package org.apache.fop.fonts;
  19. import java.util.List;
  20. import org.apache.commons.logging.Log;
  21. import org.apache.commons.logging.LogFactory;
  22. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
  23. import org.apache.fop.complexscripts.fonts.GlyphTable;
  24. import org.apache.fop.complexscripts.util.CharScript;
  25. import org.apache.fop.traits.MinOptMax;
  26. import org.apache.fop.util.CharUtilities;
  27. /**
  28. * Stores the mapping of a text fragment to glyphs, along with various information.
  29. */
  30. public class GlyphMapping {
  31. private static final Log LOG = LogFactory.getLog(GlyphMapping.class);
  32. /** Inclusive. */
  33. public final int startIndex;
  34. /** Exclusive. */
  35. public final int endIndex;
  36. private int wordCharLength;
  37. public final int wordSpaceCount;
  38. public int letterSpaceCount;
  39. public MinOptMax areaIPD;
  40. public final boolean isHyphenated;
  41. public final boolean isSpace;
  42. public boolean breakOppAfter;
  43. public final Font font;
  44. public final int level;
  45. public final int[][] gposAdjustments;
  46. public String mapping;
  47. public List associations;
  48. public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount,
  49. MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
  50. Font font, int level, int[][] gposAdjustments) {
  51. this(startIndex, endIndex, wordSpaceCount, letterSpaceCount, areaIPD, isHyphenated,
  52. isSpace, breakOppAfter, font, level, gposAdjustments, null, null);
  53. }
  54. public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount,
  55. MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
  56. Font font, int level, int[][] gposAdjustments, String mapping, List associations) {
  57. assert startIndex <= endIndex;
  58. this.startIndex = startIndex;
  59. this.endIndex = endIndex;
  60. this.wordCharLength = -1;
  61. this.wordSpaceCount = wordSpaceCount;
  62. this.letterSpaceCount = letterSpaceCount;
  63. this.areaIPD = areaIPD;
  64. this.isHyphenated = isHyphenated;
  65. this.isSpace = isSpace;
  66. this.breakOppAfter = breakOppAfter;
  67. this.font = font;
  68. this.level = level;
  69. this.gposAdjustments = gposAdjustments;
  70. this.mapping = mapping;
  71. this.associations = associations;
  72. }
  73. public static GlyphMapping doGlyphMapping(TextFragment text, int startIndex, int endIndex,
  74. Font font, MinOptMax letterSpaceIPD, MinOptMax[] letterSpaceAdjustArray,
  75. char precedingChar, char breakOpportunityChar, final boolean endsWithHyphen, int level,
  76. boolean dontOptimizeForIdentityMapping, boolean retainAssociations, boolean retainControls) {
  77. GlyphMapping mapping;
  78. if (font.performsSubstitution() || font.performsPositioning()) {
  79. mapping = processWordMapping(text, startIndex, endIndex, font,
  80. breakOpportunityChar, endsWithHyphen, level,
  81. dontOptimizeForIdentityMapping, retainAssociations, retainControls);
  82. } else {
  83. mapping = processWordNoMapping(text, startIndex, endIndex, font,
  84. letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen, level);
  85. }
  86. return mapping;
  87. }
  88. private static GlyphMapping processWordMapping(TextFragment text, int startIndex,
  89. int endIndex, final Font font, final char breakOpportunityChar,
  90. final boolean endsWithHyphen, int level,
  91. boolean dontOptimizeForIdentityMapping, boolean retainAssociations, boolean retainControls) {
  92. int e = endIndex; // end index of word in FOText character buffer
  93. int nLS = 0; // # of letter spaces
  94. String script = text.getScript();
  95. String language = text.getLanguage();
  96. if (LOG.isDebugEnabled()) {
  97. LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {"
  98. + " +M"
  99. + ", level = " + level
  100. + " }");
  101. }
  102. // 1. extract unmapped character sequence.
  103. CharSequence ics = text.subSequence(startIndex, e);
  104. // 2. if script is not specified (by FO property) or it is specified as 'auto',
  105. // then compute dominant script.
  106. if ((script == null) || "auto".equals(script)) {
  107. script = CharScript.scriptTagFromCode(CharScript.dominantScript(ics));
  108. }
  109. if ((language == null) || "none".equals(language)) {
  110. language = "dflt";
  111. }
  112. // 3. perform mapping of chars to glyphs ... to glyphs ... to chars, retaining
  113. // associations if requested.
  114. List associations = retainAssociations ? new java.util.ArrayList() : null;
  115. CharSequence mcs = font.performSubstitution(ics, script, language, associations, retainControls);
  116. // 4. compute glyph position adjustments on (substituted) characters.
  117. int[][] gpa = null;
  118. if (font.performsPositioning()) {
  119. // handle GPOS adjustments
  120. gpa = font.performPositioning(mcs, script, language);
  121. }
  122. if (useKerningAdjustments(font, script, language)) {
  123. // handle standard (non-GPOS) kerning adjustments
  124. gpa = getKerningAdjustments(mcs, font, gpa);
  125. }
  126. // 5. reorder combining marks so that they precede (within the mapped char sequence) the
  127. // base to which they are applied; N.B. position adjustments (gpa) are reordered in place.
  128. mcs = font.reorderCombiningMarks(mcs, gpa, script, language, associations);
  129. // 6. compute word ipd based on final position adjustments.
  130. MinOptMax ipd = MinOptMax.ZERO;
  131. for (int i = 0, n = mcs.length(); i < n; i++) {
  132. int c = mcs.charAt(i);
  133. // TODO !BMP
  134. int w = font.getCharWidth(c);
  135. if (w < 0) {
  136. w = 0;
  137. }
  138. if (gpa != null) {
  139. w += gpa[i][GlyphPositioningTable.Value.IDX_X_ADVANCE];
  140. }
  141. ipd = ipd.plus(w);
  142. }
  143. // [TBD] - handle letter spacing
  144. return new GlyphMapping(startIndex, e, 0, nLS, ipd, endsWithHyphen, false,
  145. breakOpportunityChar != 0, font, level, gpa,
  146. !dontOptimizeForIdentityMapping && CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString(),
  147. associations);
  148. }
  149. private static boolean useKerningAdjustments(final Font font, String script, String language) {
  150. return font.hasKerning() && !font.hasFeature(GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, script, language, "kern");
  151. }
  152. /**
  153. * Given a mapped character sequence MCS, obtain glyph position adjustments from the
  154. * font's kerning data.
  155. *
  156. * @param mcs mapped character sequence
  157. * @param font applicable font
  158. * @return glyph position adjustments (or null if no kerning)
  159. */
  160. private static int[][] getKerningAdjustments(CharSequence mcs, final Font font, int[][] gpa) {
  161. int nc = mcs.length();
  162. // extract kerning array
  163. int[] ka = new int[nc]; // kerning array
  164. for (int i = 0, n = nc, cPrev = -1; i < n; i++) {
  165. int c = mcs.charAt(i);
  166. // TODO !BMP
  167. if (cPrev >= 0) {
  168. ka[i] = font.getKernValue(cPrev, c);
  169. }
  170. cPrev = c;
  171. }
  172. // was there a non-zero kerning?
  173. boolean hasKerning = false;
  174. for (int i = 0, n = nc; i < n; i++) {
  175. if (ka[i] != 0) {
  176. hasKerning = true;
  177. break;
  178. }
  179. }
  180. // if non-zero kerning, then create and return glyph position adjustment array
  181. if (hasKerning) {
  182. if (gpa == null) {
  183. gpa = new int[nc][4];
  184. }
  185. for (int i = 0, n = nc; i < n; i++) {
  186. if (i > 0) {
  187. gpa [i - 1][GlyphPositioningTable.Value.IDX_X_ADVANCE] += ka[i];
  188. }
  189. }
  190. return gpa;
  191. } else {
  192. return null;
  193. }
  194. }
  195. private static GlyphMapping processWordNoMapping(TextFragment text, int startIndex, int endIndex,
  196. final Font font, MinOptMax letterSpaceIPD, MinOptMax[] letterSpaceAdjustArray,
  197. char precedingChar, final char breakOpportunityChar, final boolean endsWithHyphen, int level) {
  198. boolean kerning = font.hasKerning();
  199. MinOptMax wordIPD = MinOptMax.ZERO;
  200. if (LOG.isDebugEnabled()) {
  201. LOG.debug("PW: [" + startIndex + "," + endIndex + "]: {"
  202. + " -M"
  203. + ", level = " + level
  204. + " }");
  205. }
  206. for (int i = startIndex; i < endIndex; i++) {
  207. char currentChar = text.charAt(i);
  208. // character width
  209. int charWidth = font.getCharWidth(currentChar);
  210. wordIPD = wordIPD.plus(charWidth);
  211. // kerning
  212. if (kerning) {
  213. int kern = 0;
  214. if (i > startIndex) {
  215. char previousChar = text.charAt(i - 1);
  216. kern = font.getKernValue(previousChar, currentChar);
  217. } else if (precedingChar != 0) {
  218. kern = font.getKernValue(precedingChar, currentChar);
  219. }
  220. if (kern != 0) {
  221. addToLetterAdjust(letterSpaceAdjustArray, i, kern);
  222. wordIPD = wordIPD.plus(kern);
  223. }
  224. }
  225. }
  226. if (kerning
  227. && (breakOpportunityChar != 0)
  228. && !isSpace(breakOpportunityChar)
  229. && endIndex > 0
  230. && endsWithHyphen) {
  231. int kern = font.getKernValue(text.charAt(endIndex - 1), breakOpportunityChar);
  232. if (kern != 0) {
  233. addToLetterAdjust(letterSpaceAdjustArray, endIndex, kern);
  234. // TODO: add kern to wordIPD?
  235. }
  236. }
  237. // shy+chars at start of word: wordLength == 0 && breakOpportunity
  238. // shy only characters in word: wordLength == 0 && !breakOpportunity
  239. int wordLength = endIndex - startIndex;
  240. int letterSpaces = 0;
  241. if (wordLength != 0) {
  242. letterSpaces = wordLength - 1;
  243. // if there is a break opportunity and the next one (break character)
  244. // is not a space, it could be used as a line end;
  245. // add one more letter space, in case other text follows
  246. if ((breakOpportunityChar != 0) && !isSpace(breakOpportunityChar)) {
  247. letterSpaces++;
  248. }
  249. }
  250. assert letterSpaces >= 0;
  251. wordIPD = wordIPD.plus(letterSpaceIPD.mult(letterSpaces));
  252. // create and return the AreaInfo object
  253. return new GlyphMapping(startIndex, endIndex, 0,
  254. letterSpaces, wordIPD,
  255. endsWithHyphen,
  256. false, breakOpportunityChar != 0, font, level, null);
  257. }
  258. private static void addToLetterAdjust(MinOptMax[] letterSpaceAdjustArray, int index, int width) {
  259. if (letterSpaceAdjustArray[index] == null) {
  260. letterSpaceAdjustArray[index] = MinOptMax.getInstance(width);
  261. } else {
  262. letterSpaceAdjustArray[index] = letterSpaceAdjustArray[index].plus(width);
  263. }
  264. }
  265. /**
  266. * Indicates whether a character is a space in terms of this layout manager.
  267. *
  268. * @param ch the character
  269. * @return true if it's a space
  270. */
  271. public static boolean isSpace(final char ch) {
  272. return ch == CharUtilities.SPACE
  273. || CharUtilities.isNonBreakableSpace(ch)
  274. || CharUtilities.isFixedWidthSpace(ch);
  275. }
  276. /**
  277. * Obtain number of 'characters' contained in word. If word is mapped, then this
  278. * number may be less than or greater than the original length (breakIndex -
  279. * startIndex). We compute and memoize thius length upon first invocation of this
  280. * method.
  281. */
  282. public int getWordLength() {
  283. if (wordCharLength == -1) {
  284. if (mapping != null) {
  285. wordCharLength = mapping.length();
  286. } else {
  287. assert endIndex >= startIndex;
  288. wordCharLength = endIndex - startIndex;
  289. }
  290. }
  291. return wordCharLength;
  292. }
  293. public void addToAreaIPD(MinOptMax idp) {
  294. areaIPD = areaIPD.plus(idp);
  295. }
  296. public String toString() {
  297. return super.toString() + "{"
  298. + "interval = [" + startIndex + "," + endIndex + "]"
  299. + ", isSpace = " + isSpace
  300. + ", level = " + level
  301. + ", areaIPD = " + areaIPD
  302. + ", letterSpaceCount = " + letterSpaceCount
  303. + ", wordSpaceCount = " + wordSpaceCount
  304. + ", isHyphenated = " + isHyphenated
  305. + ", font = " + font
  306. + "}";
  307. }
  308. }