Skip to content

Commit b515e82

Browse files
committed
Implement woff2 decoder
Woff2 decompression code is port of part of C++ project hosted at https://github.com/google/woff2 DEVSIX-1314
1 parent 3865b5e commit b515e82

15 files changed

+1897
-13
lines changed

io/src/main/java/com/itextpdf/io/IOException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public class IOException extends RuntimeException {
119119
public static final String InvalidCodeEncounteredWhileDecoding2dGroup4CompressedData = "Invalid code encountered while decoding 2D group 4 compressed data.";
120120
public static final String InvalidIccProfile = "Invalid ICC profile.";
121121
public static final String InvalidJpeg2000File = "Invalid JPEG2000 file.";
122+
public static final String InvalidWoff2File = "Invalid WOFF2 font file.";
122123
public static final String InvalidWoffFile = "Invalid WOFF font file.";
123124
@Deprecated
124125
public static final String InvalidMagicValueForBmpFile = "Invalid magic value for bmp file. Must be 'BM'";

io/src/main/java/com/itextpdf/io/font/FontProgramDescriptorFactory.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This file is part of the iText (R) project.
4343
package com.itextpdf.io.font;
4444

4545
import com.itextpdf.io.IOException;
46+
import com.itextpdf.io.font.woff2.Woff2Converter;
4647

4748
public final class FontProgramDescriptorFactory {
4849
private static boolean FETCH_CACHED_FIRST = true;
@@ -71,13 +72,16 @@ public static FontProgramDescriptor fetchDescriptor(String fontName) {
7172
fontDescriptor = fetchType1FontDescriptor(fontName, null);
7273
} else if (isCidFont) {
7374
fontDescriptor = fetchCidFontDescriptor(fontName);
74-
} else if (fontNameLowerCase.endsWith(".ttf") || fontNameLowerCase.endsWith(".otf") || fontNameLowerCase.endsWith(".woff")) {
75+
} else if (fontNameLowerCase.endsWith(".ttf") || fontNameLowerCase.endsWith(".otf")) {
76+
fontDescriptor = fetchTrueTypeFontDescriptor(fontName);
77+
} else if (fontNameLowerCase.endsWith(".woff") || fontNameLowerCase.endsWith(".woff2")) {
78+
byte[] fontProgram;
7579
if (fontNameLowerCase.endsWith(".woff")) {
76-
byte[] fontProgram = WoffConverter.convert(FontProgramFactory.readFontBytesFromPath(baseName));
77-
fontDescriptor = fetchTrueTypeFontDescriptor(fontProgram);
80+
fontProgram = WoffConverter.convert(FontProgramFactory.readFontBytesFromPath(baseName));
7881
} else {
79-
fontDescriptor = fetchTrueTypeFontDescriptor(fontName);
82+
fontProgram = Woff2Converter.convert(FontProgramFactory.readFontBytesFromPath(baseName));
8083
}
84+
fontDescriptor = fetchTrueTypeFontDescriptor(fontProgram);
8185
} else {
8286
fontDescriptor = fetchTTCDescriptor(baseName);
8387
}

io/src/main/java/com/itextpdf/io/font/FontProgramFactory.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.io.font;
4545

4646
import com.itextpdf.io.IOException;
47+
import com.itextpdf.io.font.woff2.FontCompressionException;
48+
import com.itextpdf.io.font.woff2.Woff2Converter;
4749
import com.itextpdf.io.source.RandomAccessFileOrArray;
4850
import com.itextpdf.io.source.RandomAccessSourceFactory;
4951

@@ -176,6 +178,8 @@ public static FontProgram createFont(String name, byte[] fontProgram, boolean ca
176178
try {
177179
if (WoffConverter.isWoffFont(fontProgram)) {
178180
fontProgram = WoffConverter.convert(fontProgram);
181+
} else if (Woff2Converter.isWoff2Font(fontProgram)) {
182+
fontProgram = Woff2Converter.convert(fontProgram);
179183
}
180184
fontBuilt = new TrueTypeFont(fontProgram);
181185
} catch (Exception ignored) {
@@ -197,22 +201,30 @@ public static FontProgram createFont(String name, byte[] fontProgram, boolean ca
197201
fontBuilt = new Type1Font(name, null, null, null);
198202
} else if (isCidFont) {
199203
fontBuilt = new CidFont(name, FontCache.getCompatibleCmaps(baseName));
200-
} else if (".ttf".equals(fontFileExtension) || ".otf".equals(fontFileExtension) || ".woff".equals(fontFileExtension)) {
204+
} else if (".ttf".equals(fontFileExtension) || ".otf".equals(fontFileExtension)) {
205+
if (fontProgram != null) {
206+
fontBuilt = new TrueTypeFont(fontProgram);
207+
} else {
208+
fontBuilt = new TrueTypeFont(name);
209+
}
210+
} else if (".woff".equals(fontFileExtension) || ".woff2".equals(fontFileExtension)) {
211+
if (fontProgram == null) {
212+
fontProgram = readFontBytesFromPath(baseName);
213+
}
201214
if (".woff".equals(fontFileExtension)) {
202-
if (fontProgram == null) {
203-
fontProgram = readFontBytesFromPath(baseName);
204-
}
205215
try {
206216
fontProgram = WoffConverter.convert(fontProgram);
207217
} catch (IllegalArgumentException woffException) {
208218
throw new IOException(IOException.InvalidWoffFile, woffException);
209219
}
220+
} else if (".woff2".equals(fontFileExtension)) {
221+
try {
222+
fontProgram = Woff2Converter.convert(fontProgram);
223+
} catch (FontCompressionException woff2Exception) {
224+
throw new IOException(IOException.InvalidWoff2File, woff2Exception);
225+
}
210226
}
211-
if (fontProgram != null) {
212-
fontBuilt = new TrueTypeFont(fontProgram);
213-
} else {
214-
fontBuilt = new TrueTypeFont(name);
215-
}
227+
fontBuilt = new TrueTypeFont(fontProgram);
216228
} else {
217229
int ttcSplit = baseName.toLowerCase().indexOf(".ttc,");
218230
if (ttcSplit > 0) {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright 2013 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// The parts of ots.h & opentype-sanitiser.h that we need, taken from the
16+
// https://code.google.com/p/ots/ project.
17+
//
18+
// This is part of java port of project hosted at https://github.com/google/woff2
19+
package com.itextpdf.io.font.woff2;
20+
21+
import static com.itextpdf.io.font.woff2.JavaUnsignedUtil.asU8;
22+
import static com.itextpdf.io.font.woff2.JavaUnsignedUtil.toU16;
23+
import static com.itextpdf.io.font.woff2.JavaUnsignedUtil.toU8;
24+
25+
import java.io.BufferedInputStream;
26+
import java.io.ByteArrayInputStream;
27+
import java.io.DataInputStream;
28+
import java.io.IOException;
29+
30+
// -----------------------------------------------------------------------------
31+
// Buffer helper class
32+
//
33+
// This class perform some trival buffer operations while checking for
34+
// out-of-bounds errors. As a family they throw exception if anything is amiss,
35+
// updating the current offset otherwise.
36+
// -----------------------------------------------------------------------------
37+
class Buffer {
38+
private byte[] data;
39+
private int offset;
40+
private int initial_offset;
41+
private int length;
42+
43+
public Buffer(byte[] data, int data_offset, int length) {
44+
this.offset = 0;
45+
this.initial_offset = data_offset;
46+
this.length = length;
47+
this.data = data;
48+
}
49+
50+
public Buffer(Buffer other) {
51+
this.offset = other.offset;
52+
this.initial_offset = other.initial_offset;
53+
this.length = other.length;
54+
this.data = other.data;
55+
}
56+
57+
public int readInt() {
58+
return readAsNumber(4);
59+
}
60+
61+
public short readShort() {
62+
return toU16(readAsNumber(2));
63+
}
64+
65+
public byte readByte() {
66+
return toU8(readAsNumber(1));
67+
}
68+
69+
public void skip(int n_bytes) {
70+
read(null, 0, n_bytes);
71+
}
72+
73+
public void read(byte[] data, int data_offset, int n_bytes) {
74+
if (offset + n_bytes > length || offset > length - n_bytes) {
75+
throw new FontCompressionException(FontCompressionException.BUFFER_READ_FAILED);
76+
}
77+
if (data != null) {
78+
if (data_offset + n_bytes > data.length || data_offset > data.length - n_bytes) {
79+
throw new FontCompressionException(FontCompressionException.BUFFER_READ_FAILED);
80+
}
81+
System.arraycopy(this.data, initial_offset + offset, data, data_offset, n_bytes);
82+
}
83+
this.offset += n_bytes;
84+
}
85+
86+
public int getOffset() {
87+
return offset;
88+
}
89+
90+
public int getInitialOffset() {
91+
return initial_offset;
92+
}
93+
94+
public int getLength() {
95+
return length;
96+
}
97+
98+
private int readAsNumber(int n_bytes) {
99+
byte[] buffer = new byte[n_bytes];
100+
read(buffer, 0, n_bytes);
101+
int result = 0;
102+
for (int i = 0; i < n_bytes; ++i) {
103+
result = (result << 8) | asU8(buffer[i]);
104+
}
105+
return result;
106+
}
107+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.itextpdf.io.font.woff2;
2+
3+
public class FontCompressionException extends RuntimeException {
4+
public static final String BUFFER_READ_FAILED = "Reading woff2 exception";
5+
public static final String READ_BASE_128_FAILED = "Reading woff2 base 128 number exception";
6+
public static final String READ_TABLE_DIRECTORY_FAILED = "Reading woff2 tables directory exception";
7+
public static final String INCORRECT_SIGNATURE = "Incorrect woff2 signature";
8+
public static final String RECONSTRUCT_GLYPH_FAILED = "Reconstructing woff2 glyph exception";
9+
public static final String RECONSTRUCT_POINT_FAILED = "Reconstructing woff2 glyph's point exception";
10+
public static final String PADDING_OVERFLOW = "woff2 padding overflow exception";
11+
public static final String LOCA_SIZE_OVERFLOW = "woff2 loca table content size overflow exception";
12+
public static final String RECONSTRUCT_GLYF_TABLE_FAILED = "Reconstructing woff2 glyf table exception";
13+
public static final String RECONSTRUCT_HMTX_TABLE_FAILED = "Reconstructing woff2 hmtx table exception";
14+
public static final String BROTLI_DECODING_FAILED = "Woff2 brotli decoding exception";
15+
public static final String RECONSTRUCT_TABLE_DIRECTORY_FAILED = "Reconstructing woff2 table directory exception";
16+
public static final String READ_HEADER_FAILED = "Reading woff2 header exception";
17+
public static final String READ_COLLECTION_HEADER_FAILED = "Reading collection woff2 header exception";
18+
public static final String WRITE_FAILED = "Writing woff2 exception";
19+
20+
public FontCompressionException() {
21+
}
22+
23+
public FontCompressionException(String message) {
24+
super(message);
25+
}
26+
27+
public FontCompressionException(String message, Throwable cause) {
28+
super(message, cause);
29+
}
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.itextpdf.io.font.woff2;
2+
3+
/**
4+
* Helper class to deal with unsigned primitives in java
5+
*/
6+
class JavaUnsignedUtil {
7+
public static int asU16(short number) {
8+
return number & 0xffff;
9+
}
10+
11+
public static int asU8(byte number) {
12+
return number & 0xff;
13+
}
14+
15+
public static byte toU8(int number) {
16+
return (byte) (number & 0xff);
17+
}
18+
19+
public static short toU16(int number) {
20+
return (short) (number & 0xffff);
21+
}
22+
23+
public static int compareAsUnsigned(int left, int right) {
24+
return Long.valueOf(left & 0xffffffffL).compareTo(right & 0xffffffffL);
25+
}
26+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2013 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// This is part of java port of project hosted at https://github.com/google/woff2
16+
package com.itextpdf.io.font.woff2;
17+
18+
// Helper for rounding
19+
class Round {
20+
21+
// Round a value up to the nearest multiple of 4. Don't round the value in the
22+
// case that rounding up overflows.
23+
public static int round4(int value) {
24+
if (Integer.MAX_VALUE - value < 3) {
25+
return value;
26+
}
27+
return (value + 3) & ~3;
28+
}
29+
30+
public static long round4(long value) {
31+
if (Long.MAX_VALUE - value < 3) {
32+
return value;
33+
}
34+
return (value + 3) & ~3;
35+
}
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2013 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// This is part of java port of project hosted at https://github.com/google/woff2
16+
package com.itextpdf.io.font.woff2;
17+
18+
import static com.itextpdf.io.font.woff2.JavaUnsignedUtil.toU8;
19+
20+
// Helper functions for storing integer values into byte streams.
21+
// No bounds checking is performed, that is the responsibility of the caller.
22+
class StoreBytes {
23+
24+
public static int storeU32(byte[] dst, int offset, int x) {
25+
dst[offset] = toU8(x >> 24);
26+
dst[offset + 1] = toU8(x >> 16);
27+
dst[offset + 2] = toU8(x >> 8);
28+
dst[offset + 3] = toU8(x);
29+
return offset + 4;
30+
}
31+
32+
public static int storeU16(byte[] dst, int offset, int x) {
33+
dst[offset] = toU8(x >> 8);
34+
dst[offset + 1] = toU8(x);
35+
return offset + 2;
36+
}
37+
}

0 commit comments

Comments
 (0)