Skip to content

Commit aa02c1b

Browse files
committed
PDFBOX-4213: add placeholder for Tamil
git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1929971 13f79535-47bb-0310-9956-ffa450edef68
1 parent 13df798 commit aa02c1b

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public GsubWorker getGsubWorker(CmapLookup cmapLookup, GsubData gsubData)
5454
return new GsubWorkerForLatin(gsubData);
5555
case DFLT:
5656
return new GsubWorkerForDflt(gsubData);
57+
case TAMIL:
58+
//TODO implement me
5759
default:
5860
return new DefaultGsubWorker();
5961
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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+
package org.apache.fontbox.ttf.gsub;
19+
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Set;
25+
26+
import org.apache.fontbox.ttf.CmapLookup;
27+
import org.apache.fontbox.ttf.model.GsubData;
28+
import org.apache.fontbox.ttf.model.ScriptFeature;
29+
import org.apache.logging.log4j.LogManager;
30+
import org.apache.logging.log4j.Logger;
31+
32+
/**
33+
*
34+
* Tamil-specific implementation of GSUB system.
35+
*
36+
* @author TODO
37+
*
38+
*/
39+
public class GsubWorkerForTamil implements GsubWorker
40+
{
41+
private static final Logger LOG = LogManager.getLogger(GsubWorkerForTamil.class);
42+
43+
44+
/**
45+
* This sequence is very important. This has been taken from <a href=
46+
* "https://docs.microsoft.com/en-us/typography/script-development/tamil">https://docs.microsoft.com/en-us/typography/script-development/tamil</a>
47+
*/
48+
private static final List<String> FEATURES_IN_ORDER = Arrays.asList("locl", "nukt", "akhn",
49+
"rphf", "pref", "half", "pres", "abvs", "blws",
50+
"psts", "haln", "calt");
51+
52+
//TODO adjust all below this line. The existing code has been copied from Gujarati
53+
54+
// Reph glyphs
55+
private static final char[] REPH_CHARS = {'\u0BB0','\u0BCD'};
56+
// Glyphs to precede reph
57+
private static final char[] BEFORE_REPH_CHARS= {'\u0BB8','\u0BCD'};
58+
59+
// Gujarati vowel sign I
60+
private static final char BEFORE_HALF_CHAR = '\u0ABF';
61+
62+
private final CmapLookup cmapLookup;
63+
private final GsubData gsubData;
64+
65+
private final List<Integer> rephGlyphIds;
66+
private final List<Integer> beforeRephGlyphIds;
67+
private final List<Integer> beforeHalfGlyphIds;
68+
69+
GsubWorkerForTamil(CmapLookup cmapLookup, GsubData gsubData)
70+
{
71+
this.cmapLookup = cmapLookup;
72+
this.gsubData = gsubData;
73+
beforeHalfGlyphIds = getBeforeHalfGlyphIds();
74+
rephGlyphIds = getRephGlyphIds();
75+
beforeRephGlyphIds=getbeforeRephGlyphIds();
76+
}
77+
78+
@Override
79+
public List<Integer> applyTransforms(List<Integer> originalGlyphIds)
80+
{
81+
List<Integer> intermediateGlyphsFromGsub = adjustRephPosition(originalGlyphIds);
82+
intermediateGlyphsFromGsub = repositionGlyphs(intermediateGlyphsFromGsub);
83+
for (String feature : FEATURES_IN_ORDER)
84+
{
85+
if (!gsubData.isFeatureSupported(feature))
86+
{
87+
LOG.debug("the feature {} was not found", feature);
88+
continue;
89+
}
90+
LOG.debug("applying the feature {}", feature);
91+
ScriptFeature scriptFeature = gsubData.getFeature(feature);
92+
intermediateGlyphsFromGsub = applyGsubFeature(scriptFeature,
93+
intermediateGlyphsFromGsub);
94+
}
95+
return Collections.unmodifiableList(intermediateGlyphsFromGsub);
96+
}
97+
98+
private List<Integer> repositionGlyphs(List<Integer> originalGlyphIds)
99+
{
100+
List<Integer> repositionedGlyphIds = new ArrayList<>(originalGlyphIds);
101+
int listSize = repositionedGlyphIds.size();
102+
int foundIndex = listSize - 1;
103+
int nextIndex = listSize - 2;
104+
while (nextIndex > -1)
105+
{
106+
int glyph = repositionedGlyphIds.get(foundIndex);
107+
int prevIndex = foundIndex + 1;
108+
if (beforeHalfGlyphIds.contains(glyph))
109+
{
110+
repositionedGlyphIds.remove(foundIndex);
111+
repositionedGlyphIds.add(nextIndex--, glyph);
112+
}
113+
else if (rephGlyphIds.get(1).equals(glyph) && prevIndex < listSize)
114+
{
115+
int prevGlyph = repositionedGlyphIds.get(prevIndex);
116+
if (beforeHalfGlyphIds.contains(prevGlyph))
117+
{
118+
repositionedGlyphIds.remove(prevIndex);
119+
repositionedGlyphIds.add(nextIndex--, prevGlyph);
120+
}
121+
}
122+
foundIndex = nextIndex--;
123+
}
124+
return repositionedGlyphIds;
125+
}
126+
127+
private List<Integer> adjustRephPosition(List<Integer> originalGlyphIds)
128+
{
129+
List<Integer> rephAdjustedList = new ArrayList<>(originalGlyphIds);
130+
for (int index = 0; index < originalGlyphIds.size() - 2; index++)
131+
{
132+
int raGlyph = originalGlyphIds.get(index);
133+
int viramaGlyph = originalGlyphIds.get(index + 1);
134+
if (raGlyph == rephGlyphIds.get(0) && viramaGlyph == rephGlyphIds.get(1))
135+
{
136+
// reph virama cons => cons reph virama
137+
int nextConsonantGlyph = originalGlyphIds.get(index + 2);
138+
rephAdjustedList.set(index, nextConsonantGlyph);
139+
rephAdjustedList.set(index + 1, raGlyph);
140+
rephAdjustedList.set(index + 2, viramaGlyph);
141+
142+
if (index + 3 < originalGlyphIds.size())
143+
{
144+
// reph virama cons matra => cons matra reph virama
145+
int matraGlyph = originalGlyphIds.get(index + 3);
146+
if (beforeRephGlyphIds.contains(matraGlyph))
147+
{
148+
rephAdjustedList.set(index + 1, matraGlyph);
149+
rephAdjustedList.set(index + 2, raGlyph);
150+
rephAdjustedList.set(index + 3, viramaGlyph);
151+
}
152+
}
153+
}
154+
}
155+
return rephAdjustedList;
156+
}
157+
158+
private List<Integer> applyGsubFeature(ScriptFeature scriptFeature, List<Integer> originalGlyphs)
159+
{
160+
Set<List<Integer>> allGlyphIdsForSubstitution = scriptFeature.getAllGlyphIdsForSubstitution();
161+
if (allGlyphIdsForSubstitution.isEmpty())
162+
{
163+
LOG.debug("getAllGlyphIdsForSubstitution() for {} is empty", scriptFeature.getName());
164+
return originalGlyphs;
165+
}
166+
GlyphArraySplitter glyphArraySplitter = new GlyphArraySplitterRegexImpl(
167+
allGlyphIdsForSubstitution);
168+
List<List<Integer>> tokens = glyphArraySplitter.split(originalGlyphs);
169+
List<Integer> gsubProcessedGlyphs = new ArrayList<>(tokens.size());
170+
tokens.forEach(chunk ->
171+
{
172+
if (scriptFeature.canReplaceGlyphs(chunk))
173+
{
174+
List<Integer> replacementForGlyphs = scriptFeature.getReplacementForGlyphs(chunk);
175+
gsubProcessedGlyphs.addAll(replacementForGlyphs);
176+
}
177+
else
178+
{
179+
gsubProcessedGlyphs.addAll(chunk);
180+
}
181+
});
182+
LOG.debug("originalGlyphs: {}, gsubProcessedGlyphs: {}", originalGlyphs, gsubProcessedGlyphs);
183+
return gsubProcessedGlyphs;
184+
}
185+
186+
private List<Integer> getBeforeHalfGlyphIds()
187+
{
188+
return List.of(getGlyphId(BEFORE_HALF_CHAR));
189+
}
190+
191+
private List<Integer> getRephGlyphIds()
192+
{
193+
List<Integer> result = new ArrayList<>();
194+
for (char character : REPH_CHARS)
195+
{
196+
result.add(getGlyphId(character));
197+
}
198+
return Collections.unmodifiableList(result);
199+
}
200+
201+
private List<Integer> getbeforeRephGlyphIds()
202+
{
203+
List<Integer> glyphIds = new ArrayList<>();
204+
for (char character : BEFORE_REPH_CHARS)
205+
{
206+
glyphIds.add(getGlyphId(character));
207+
}
208+
return Collections.unmodifiableList(glyphIds);
209+
}
210+
211+
private Integer getGlyphId(char character)
212+
{
213+
return cmapLookup.getGlyphId(character);
214+
}
215+
}

fontbox/src/main/java/org/apache/fontbox/ttf/model/Language.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public enum Language
3535
BENGALI(new String[] { "bng2", "beng" }),
3636
DEVANAGARI(new String[] { "dev2", "deva" }),
3737
GUJARATI(new String[] { "gjr2", "gujr" }),
38+
TAMIL(new String[] { "tml2", "taml" }),
3839
LATIN(new String[] { "latn" }),
3940
DFLT(new String[] { "DFLT" }),
4041

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
package org.apache.fontbox.ttf.gsub;
19+
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
22+
import java.io.IOException;
23+
24+
import org.apache.fontbox.ttf.CmapLookup;
25+
import org.apache.fontbox.ttf.TTFParser;
26+
import org.apache.fontbox.ttf.TrueTypeFont;
27+
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.Test;
30+
31+
/**
32+
* Integration test for {@link GsubWorkerForTamil}. Has various combinations of glyphs to test
33+
* proper working of the GSUB system.
34+
*/
35+
class GsubWorkerForTamilTest
36+
{
37+
private static final String LOHIT_TAMIL_TTF = "src/test/resources/ttf/Lohit-Tamil.ttf";
38+
39+
private CmapLookup cmapLookup;
40+
private GsubWorker gsubWorkerForTamil;
41+
42+
@BeforeEach
43+
public void init() throws IOException
44+
{
45+
try (TrueTypeFont ttf = new TTFParser().parse(new RandomAccessReadBufferedFile(LOHIT_TAMIL_TTF)))
46+
{
47+
cmapLookup = ttf.getUnicodeCmapLookup();
48+
gsubWorkerForTamil = new GsubWorkerFactory().getGsubWorker(cmapLookup, ttf.getGsubData());
49+
}
50+
}
51+
52+
@Test
53+
void testDummy()
54+
{
55+
System.out.println("GSUB worker: " + gsubWorkerForTamil);
56+
assertTrue(gsubWorkerForTamil instanceof DefaultGsubWorker); // change to GsubWorkerForTamil when implemented
57+
}
58+
59+
60+
}
64.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)