Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "SFNTNameTable.h"
8 :
9 : #include "BigEndianInts.h"
10 : #include "Logging.h"
11 : #include "mozilla/Move.h"
12 :
13 : #if defined(XP_MACOSX)
14 : #include <CoreFoundation/CoreFoundation.h>
15 : #endif
16 :
17 : namespace mozilla {
18 : namespace gfx {
19 :
20 3 : static const BigEndianUint16 FORMAT_0 = 0;
21 :
22 3 : static const BigEndianUint16 NAME_ID_FAMILY = 1;
23 3 : static const BigEndianUint16 NAME_ID_STYLE = 2;
24 3 : static const BigEndianUint16 NAME_ID_FULL = 4;
25 :
26 3 : static const BigEndianUint16 PLATFORM_ID_UNICODE = 0;
27 3 : static const BigEndianUint16 PLATFORM_ID_MAC = 1;
28 3 : static const BigEndianUint16 PLATFORM_ID_MICROSOFT = 3;
29 :
30 3 : static const BigEndianUint16 ENCODING_ID_MICROSOFT_SYMBOL = 0;
31 3 : static const BigEndianUint16 ENCODING_ID_MICROSOFT_UNICODEBMP = 1;
32 3 : static const BigEndianUint16 ENCODING_ID_MICROSOFT_UNICODEFULL = 10;
33 :
34 3 : static const BigEndianUint16 ENCODING_ID_MAC_ROMAN = 0;
35 :
36 3 : static const BigEndianUint16 LANG_ID_MAC_ENGLISH = 0;
37 :
38 3 : static const BigEndianUint16 LANG_ID_MICROSOFT_EN_US = 0x0409;
39 :
40 : #pragma pack(push, 1)
41 :
42 : // Name table has a header, followed by name records, followed by string data.
43 : struct NameHeader
44 : {
45 : BigEndianUint16 format; // Format selector (=0).
46 : BigEndianUint16 count; // Number of name records.
47 : BigEndianUint16 stringOffset; // Offset to string storage from start of table.
48 : };
49 :
50 : struct NameRecord
51 : {
52 : BigEndianUint16 platformID;
53 : BigEndianUint16 encodingID; // Platform-specific encoding ID
54 : BigEndianUint16 languageID;
55 : BigEndianUint16 nameID;
56 : BigEndianUint16 length; // String length in bytes.
57 : BigEndianUint16 offset; // String offset from start of storage in bytes.
58 : };
59 :
60 : #pragma pack(pop)
61 :
62 : enum ENameDecoder : int
63 : {
64 : eNameDecoderUTF16,
65 : #if defined(XP_MACOSX)
66 : eNameDecoderMacRoman,
67 : #endif
68 : eNameDecoderNone
69 : };
70 :
71 : /* static */
72 : UniquePtr<SFNTNameTable>
73 0 : SFNTNameTable::Create(const uint8_t *aNameData, uint32_t aDataLength)
74 : {
75 0 : MOZ_ASSERT(aNameData);
76 :
77 0 : if (aDataLength < sizeof(NameHeader)) {
78 0 : gfxWarning() << "Name data too short to contain NameHeader.";
79 0 : return nullptr;
80 : }
81 :
82 0 : const NameHeader *nameHeader = reinterpret_cast<const NameHeader*>(aNameData);
83 0 : if (nameHeader->format != FORMAT_0) {
84 0 : gfxWarning() << "Only Name Table Format 0 is supported.";
85 0 : return nullptr;
86 : }
87 :
88 0 : uint16_t stringOffset = nameHeader->stringOffset;
89 :
90 0 : if (stringOffset !=
91 0 : sizeof(NameHeader) + (nameHeader->count * sizeof(NameRecord))) {
92 0 : gfxWarning() << "Name table string offset is incorrect.";
93 0 : return nullptr;
94 : }
95 :
96 0 : if (aDataLength < stringOffset) {
97 0 : gfxWarning() << "Name data too short to contain name records.";
98 0 : return nullptr;
99 : }
100 :
101 : return UniquePtr<SFNTNameTable>(
102 0 : new SFNTNameTable(nameHeader, aNameData, aDataLength));
103 : }
104 :
105 0 : SFNTNameTable::SFNTNameTable(const NameHeader *aNameHeader,
106 0 : const uint8_t *aNameData, uint32_t aDataLength)
107 : : mFirstRecord(reinterpret_cast<const NameRecord*>(aNameData
108 0 : + sizeof(NameHeader)))
109 0 : , mEndOfRecords(mFirstRecord + aNameHeader->count)
110 0 : , mStringData(aNameData + aNameHeader->stringOffset)
111 0 : , mStringDataLength(aDataLength - aNameHeader->stringOffset)
112 : {
113 0 : MOZ_ASSERT(reinterpret_cast<const uint8_t*>(aNameHeader) == aNameData);
114 0 : }
115 :
116 : static bool
117 0 : IsUTF16Encoding(const NameRecord *aNameRecord)
118 : {
119 0 : if (aNameRecord->platformID == PLATFORM_ID_MICROSOFT &&
120 0 : (aNameRecord->encodingID == ENCODING_ID_MICROSOFT_UNICODEBMP ||
121 0 : aNameRecord->encodingID == ENCODING_ID_MICROSOFT_SYMBOL)) {
122 0 : return true;
123 : }
124 :
125 0 : if (aNameRecord->platformID == PLATFORM_ID_UNICODE) {
126 0 : return true;
127 : }
128 :
129 0 : return false;
130 : }
131 :
132 : #if defined(XP_MACOSX)
133 : static bool
134 : IsMacRomanEncoding(const NameRecord *aNameRecord)
135 : {
136 : if (aNameRecord->platformID == PLATFORM_ID_MAC &&
137 : aNameRecord->encodingID == ENCODING_ID_MAC_ROMAN) {
138 : return true;
139 : }
140 :
141 : return false;
142 : }
143 : #endif
144 :
145 : static NameRecordMatchers*
146 0 : CreateCanonicalMatchers(const BigEndianUint16& aNameID)
147 : {
148 : // For Windows, we return only Microsoft platform name record
149 : // matchers. On Mac, we return matchers for both Microsoft platform
150 : // records and Mac platform records.
151 0 : NameRecordMatchers *matchers = new NameRecordMatchers();
152 :
153 : #if defined(XP_MACOSX)
154 : // First, look for the English name.
155 : if (!matchers->append(
156 : [=](const NameRecord *aNameRecord) {
157 : if (aNameRecord->nameID == aNameID &&
158 : aNameRecord->languageID == LANG_ID_MAC_ENGLISH &&
159 : aNameRecord->platformID == PLATFORM_ID_MAC &&
160 : IsMacRomanEncoding(aNameRecord)) {
161 : return eNameDecoderMacRoman;
162 : } else {
163 : return eNameDecoderNone;
164 : }
165 : })) {
166 : MOZ_CRASH();
167 : }
168 :
169 : // Second, look for all languages.
170 : if (!matchers->append(
171 : [=](const NameRecord *aNameRecord) {
172 : if (aNameRecord->nameID == aNameID &&
173 : aNameRecord->platformID == PLATFORM_ID_MAC &&
174 : IsMacRomanEncoding(aNameRecord)) {
175 : return eNameDecoderMacRoman;
176 : } else {
177 : return eNameDecoderNone;
178 : }
179 : })) {
180 : MOZ_CRASH();
181 : }
182 : #endif /* defined(XP_MACOSX) */
183 :
184 : // First, look for the English name (this will normally succeed).
185 0 : if (!matchers->append(
186 0 : [=](const NameRecord *aNameRecord) {
187 0 : if (aNameRecord->nameID == aNameID &&
188 0 : aNameRecord->languageID == LANG_ID_MICROSOFT_EN_US &&
189 0 : aNameRecord->platformID == PLATFORM_ID_MICROSOFT &&
190 0 : IsUTF16Encoding(aNameRecord)) {
191 0 : return eNameDecoderUTF16;
192 : } else {
193 0 : return eNameDecoderNone;
194 : }
195 : })) {
196 0 : MOZ_CRASH();
197 : }
198 :
199 : // Second, look for all languages.
200 0 : if (!matchers->append(
201 0 : [=](const NameRecord *aNameRecord) {
202 0 : if (aNameRecord->nameID == aNameID &&
203 0 : aNameRecord->platformID == PLATFORM_ID_MICROSOFT &&
204 0 : IsUTF16Encoding(aNameRecord)) {
205 0 : return eNameDecoderUTF16;
206 : } else {
207 0 : return eNameDecoderNone;
208 : }
209 : })) {
210 0 : MOZ_CRASH();
211 : }
212 :
213 0 : return matchers;
214 : }
215 :
216 : static const NameRecordMatchers&
217 0 : FullNameMatchers()
218 : {
219 : static const NameRecordMatchers *sFullNameMatchers =
220 0 : CreateCanonicalMatchers(NAME_ID_FULL);
221 0 : return *sFullNameMatchers;
222 : }
223 :
224 : static const NameRecordMatchers&
225 0 : FamilyMatchers()
226 : {
227 : static const NameRecordMatchers *sFamilyMatchers =
228 0 : CreateCanonicalMatchers(NAME_ID_FAMILY);
229 0 : return *sFamilyMatchers;
230 : }
231 :
232 : static const NameRecordMatchers&
233 0 : StyleMatchers()
234 : {
235 : static const NameRecordMatchers *sStyleMatchers =
236 0 : CreateCanonicalMatchers(NAME_ID_STYLE);
237 0 : return *sStyleMatchers;
238 : }
239 :
240 : bool
241 0 : SFNTNameTable::GetU16FullName(mozilla::u16string& aU16FullName)
242 : {
243 0 : if (ReadU16Name(FullNameMatchers(), aU16FullName)) {
244 0 : return true;
245 : }
246 :
247 : // If the full name record doesn't exist create the name from the family space
248 : // concatenated with the style.
249 0 : mozilla::u16string familyName;
250 0 : if (!ReadU16Name(FamilyMatchers(), familyName)) {
251 0 : return false;
252 : }
253 :
254 0 : mozilla::u16string styleName;
255 0 : if (!ReadU16Name(StyleMatchers(), styleName)) {
256 0 : return false;
257 : }
258 :
259 0 : aU16FullName.assign(Move(familyName));
260 0 : aU16FullName.append(u" ");
261 0 : aU16FullName.append(styleName);
262 0 : return true;
263 : }
264 :
265 : bool
266 0 : SFNTNameTable::ReadU16Name(const NameRecordMatchers& aMatchers,
267 : mozilla::u16string& aU16Name)
268 : {
269 0 : MOZ_ASSERT(!aMatchers.empty());
270 :
271 0 : for (size_t i = 0; i < aMatchers.length(); ++i) {
272 0 : const NameRecord* record = mFirstRecord;
273 0 : while (record != mEndOfRecords) {
274 0 : switch (aMatchers[i](record)) {
275 : case eNameDecoderUTF16:
276 0 : return ReadU16NameFromU16Record(record, aU16Name);
277 : #if defined(XP_MACOSX)
278 : case eNameDecoderMacRoman:
279 : return ReadU16NameFromMacRomanRecord(record, aU16Name);
280 : #endif
281 : case eNameDecoderNone:
282 0 : break;
283 : default:
284 0 : MOZ_CRASH("Invalid matcher encoding type");
285 : break;
286 : }
287 0 : ++record;
288 : }
289 : }
290 :
291 0 : return false;
292 : }
293 :
294 : bool
295 0 : SFNTNameTable::ReadU16NameFromU16Record(const NameRecord *aNameRecord,
296 : mozilla::u16string& aU16Name)
297 : {
298 0 : uint32_t offset = aNameRecord->offset;
299 0 : uint32_t length = aNameRecord->length;
300 0 : if (mStringDataLength < offset + length) {
301 0 : gfxWarning() << "Name data too short to contain name string.";
302 0 : return false;
303 : }
304 :
305 0 : const uint8_t *startOfName = mStringData + offset;
306 0 : size_t actualLength = length / sizeof(char16_t);
307 0 : UniquePtr<char16_t[]> nameData(new char16_t[actualLength]);
308 0 : NativeEndian::copyAndSwapFromBigEndian(nameData.get(), startOfName,
309 0 : actualLength);
310 :
311 0 : aU16Name.assign(nameData.get(), actualLength);
312 0 : return true;
313 : }
314 :
315 : #if defined(XP_MACOSX)
316 : bool
317 : SFNTNameTable::ReadU16NameFromMacRomanRecord(const NameRecord *aNameRecord,
318 : mozilla::u16string& aU16Name)
319 : {
320 : uint32_t offset = aNameRecord->offset;
321 : uint32_t length = aNameRecord->length;
322 : if (mStringDataLength < offset + length) {
323 : gfxWarning() << "Name data too short to contain name string.";
324 : return false;
325 : }
326 : if (length > INT_MAX) {
327 : gfxWarning() << "Name record too long to decode.";
328 : return false;
329 : }
330 :
331 : // pointer to the Mac Roman encoded string in the name record
332 : const uint8_t *encodedStr = mStringData + offset;
333 :
334 : CFStringRef cfString;
335 : cfString = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, encodedStr,
336 : length, kCFStringEncodingMacRoman,
337 : false, kCFAllocatorNull);
338 :
339 : // length (in UTF-16 code pairs) of the decoded string
340 : CFIndex decodedLength = CFStringGetLength(cfString);
341 :
342 : // temporary buffer
343 : UniquePtr<UniChar[]> u16Buffer = MakeUnique<UniChar[]>(decodedLength);
344 :
345 : CFStringGetCharacters(cfString, CFRangeMake(0, decodedLength),
346 : u16Buffer.get());
347 :
348 : CFRelease(cfString);
349 :
350 : aU16Name.assign(reinterpret_cast<char16_t*>(u16Buffer.get()), decodedLength);
351 :
352 : return true;
353 : }
354 : #endif
355 :
356 : } // gfx
357 : } // mozilla
|