Rev 8979 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
5980 | avox | 1 | |
2 | #include "fonts/ftface.h" |
||
3 | |||
4 | #include FT_OUTLINE_H |
||
5 | #include FT_GLYPH_H |
||
6 | |||
7 | #include <qobject.h> |
||
8 | #include <qfile.h> |
||
9 | |||
10 | #include "scfonts.h" |
||
9007 | avox | 11 | #include "util.h" |
5980 | avox | 12 | #include "fonts/scfontmetrics.h" |
13 | |||
14 | // static: |
||
15 | FT_Library FtFace::library = NULL; |
||
16 | |||
17 | /***** |
||
18 | ScFace lifecycle: unchecked -> loaded -> glyphs checked |
||
19 | | \-> broken glyphs |
||
20 | \-> broken |
||
21 | usable() == ! broken |
||
22 | embeddable() == glyphs_checked |
||
23 | |||
24 | canRender(unicode) -> CharMap cache? -> loadChar/Glyph -> !broken |
||
25 | Glyphs: width status |
||
26 | -1000 unkown |
||
27 | -2000 broken |
||
28 | >= 0 ok, outline valid |
||
29 | CharMap: unicode -> glyph index |
||
30 | uint[256][256] |
||
31 | unicode ignores: < 32, ... |
||
32 | unicode emulate: spaces, hyphen, ligatures?, diacritics? |
||
33 | *****/ |
||
34 | |||
35 | FtFace::FtFace(QString fam, QString sty, QString vari, QString scname, |
||
36 | QString psname, QString path, int face) |
||
37 | : ScFaceData(), m_face(NULL) |
||
38 | { |
||
39 | family = fam; |
||
40 | style = sty; |
||
41 | variant = vari; |
||
42 | scName = scname; |
||
43 | psName = psname; |
||
44 | fontFile = path; |
||
45 | faceIndex = face; |
||
46 | if (!library) { |
||
47 | if (FT_Init_FreeType( &library )) |
||
9007 | avox | 48 | sDebug(QObject::tr("Freetype2 library not available")); |
5980 | avox | 49 | } |
50 | } |
||
51 | |||
52 | |||
53 | FtFace::~FtFace() { |
||
54 | unload(); |
||
55 | } |
||
56 | |||
57 | |||
58 | FT_Face FtFace::ftFace() const { |
||
59 | if (!m_face) { |
||
60 | if (FT_New_Face( library, QFile::encodeName(fontFile), faceIndex, & m_face )) { |
||
61 | status = ScFace::BROKEN; |
||
62 | m_face = NULL; |
||
9007 | avox | 63 | sDebug(QObject::tr("Font %1(%2) is broken").arg(fontFile).arg(faceIndex)); |
5980 | avox | 64 | } |
65 | else { |
||
66 | load(); |
||
67 | } |
||
68 | } |
||
69 | return m_face; |
||
70 | } |
||
71 | |||
72 | void FtFace::load() const |
||
73 | { |
||
74 | ScFaceData::load(); |
||
75 | |||
76 | if (!m_face) { |
||
77 | if (FT_New_Face( library, QFile::encodeName(fontFile), faceIndex, & m_face )) { |
||
78 | status = ScFace::BROKEN; |
||
79 | m_face = NULL; |
||
9007 | avox | 80 | sDebug(QObject::tr("Font %1(%2) is broken").arg(fontFile).arg(faceIndex)); |
5980 | avox | 81 | return; |
82 | } |
||
83 | } |
||
84 | |||
85 | const_cast<FtFace*>(this)->isStroked = false; |
||
86 | m_encoding = 0; |
||
87 | |||
88 | m_uniEM = static_cast<double>(m_face->units_per_EM); |
||
89 | |||
90 | m_descent = m_face->descender / m_uniEM; |
||
91 | m_ascent = m_face->ascender / m_uniEM; |
||
92 | m_height = m_face->height / m_uniEM; |
||
7868 | fschmid | 93 | |
94 | /* Temporary fix for the broken "Dutch Initials" font */ |
||
95 | if ((m_ascent == 0) && (m_descent == 0)) |
||
96 | { |
||
97 | m_ascent = (m_face->bbox.yMax - m_face->bbox.yMin) / m_uniEM; |
||
98 | m_height = m_ascent; |
||
99 | } |
||
100 | |||
5980 | avox | 101 | m_xHeight = m_height; |
102 | m_capHeight = m_height; |
||
103 | m_maxAdvanceWidth = m_face->max_advance_width / m_uniEM; |
||
104 | m_underlinePos = m_face->underline_position / m_uniEM; |
||
105 | m_strikeoutPos = m_ascent / 3; |
||
106 | m_strokeWidth = m_face->underline_thickness / m_uniEM; |
||
107 | const_cast<FtFace*>(this)->isFixedPitch = m_face->face_flags & 4; |
||
6213 | fschmid | 108 | Ascent = QString::number(m_face->ascender); |
109 | CapHeight = QString::number(m_face->height); |
||
110 | Descender = QString::number(m_face->descender); |
||
111 | FontBBox = QString::number(m_face->bbox.xMin)+" "+QString::number(m_face->bbox.yMin)+" "+QString::number(m_face->bbox.xMax)+" "+QString::number(m_face->bbox.yMax); |
||
112 | ItalicAngle = "0"; |
||
5980 | avox | 113 | |
114 | //FIXME: FT_Set_Charmap(m_face, m_face->charmaps[m_encoding]); |
||
115 | setBestEncoding(m_face); |
||
116 | |||
117 | FT_UInt gindex = 0; |
||
118 | FT_ULong charcode = FT_Get_First_Char( m_face, &gindex ); |
||
119 | int goodGlyph = 0; |
||
120 | int invalidGlyph = 0; |
||
121 | bool error; |
||
122 | |||
123 | while ( gindex != 0 ) |
||
124 | { |
||
125 | error = FT_Load_Glyph( m_face, gindex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP ); |
||
126 | if (error) |
||
127 | { |
||
128 | ++invalidGlyph; |
||
9007 | avox | 129 | sDebug(QObject::tr("Font %1 has broken glyph %2 (charcode %3)").arg(fontFile).arg(gindex).arg(charcode)); |
5980 | avox | 130 | charcode = FT_Get_Next_Char( m_face, charcode, &gindex ); |
131 | continue; |
||
132 | } |
||
133 | |||
134 | if (gindex > maxGlyph) |
||
135 | const_cast<FtFace*>(this)->maxGlyph = gindex; |
||
136 | |||
137 | ++goodGlyph; |
||
138 | if (m_face->glyph->format == FT_GLYPH_FORMAT_PLOTTER) |
||
139 | const_cast<FtFace*>(this)->isStroked = true; |
||
140 | charcode = FT_Get_Next_Char( m_face, charcode, &gindex ); |
||
141 | } |
||
142 | if (invalidGlyph > 0) { |
||
143 | status = ScFace::BROKENGLYPHS; |
||
144 | } |
||
145 | } |
||
146 | |||
147 | |||
148 | void FtFace::unload() const |
||
149 | { |
||
150 | if (m_face) { |
||
151 | FT_Done_Face( m_face ); |
||
152 | m_face = NULL; |
||
153 | } |
||
154 | // clear caches |
||
155 | ScFaceData::unload(); |
||
156 | } |
||
157 | |||
158 | |||
159 | uint FtFace::char2CMap(QChar ch) const |
||
160 | { |
||
161 | // FIXME use cMap cache |
||
162 | FT_Face face = ftFace(); |
||
163 | uint gl = FT_Get_Char_Index(face, ch.unicode()); |
||
164 | return gl; |
||
165 | } |
||
166 | |||
167 | |||
168 | void FtFace::loadGlyph(uint gl) const |
||
169 | { |
||
6348 | avox | 170 | if (m_glyphWidth.contains(gl)) |
171 | return; |
||
172 | |||
5980 | avox | 173 | ScFace::GlyphData GRec; |
174 | FT_Face face = ftFace(); |
||
175 | if (FT_Load_Glyph( face, gl, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP )) |
||
176 | { |
||
9007 | avox | 177 | sDebug(QObject::tr("Font %1 has broken glyph %2").arg(fontFile).arg(gl)); |
5980 | avox | 178 | m_glyphWidth[gl] = 1; |
179 | } |
||
180 | else { |
||
8978 | avox | 181 | double ww = double(face->glyph->metrics.horiAdvance) / m_uniEM; |
182 | double w = (face->glyph->metrics.width + QABS(double(face->glyph->metrics.horiBearingX))) / m_uniEM; |
||
183 | GRec.bbox_width = QMAX(w, ww); |
||
184 | double height = double(face->glyph->metrics.height) / m_uniEM; |
||
185 | GRec.bbox_ascent = double(face->glyph->metrics.horiBearingY) / m_uniEM; |
||
186 | GRec.bbox_descent = height - GRec.bbox_ascent; |
||
8979 | avox | 187 | // qDebug(QString("glyphmetrics %1: EM %2 bearing (%3,%4) size (%5,%6) advance %7 bbox (%8,%9)") |
188 | // .arg(gl).arg(m_uniEM).arg(face->glyph->metrics.horiBearingX).arg(face->glyph->metrics.horiBearingY) |
||
189 | // .arg(face->glyph->metrics.width).arg(face->glyph->metrics.height).arg(face->glyph->metrics.horiAdvance) |
||
190 | // .arg(w).arg(height)); |
||
8978 | avox | 191 | |
5980 | avox | 192 | double x, y; |
193 | bool error = false; |
||
6220 | fschmid | 194 | error = FT_Set_Char_Size( face, 0, 10, 72, 72 ); |
195 | if (error) |
||
196 | m_glyphWidth[gl] = 1; |
||
5980 | avox | 197 | FPointArray outlines = traceGlyph(face, gl, 10, &x, &y, &error); |
198 | if (!error) |
||
199 | { |
||
200 | m_glyphWidth[gl] = ww; |
||
201 | GRec.Outlines = outlines; |
||
202 | GRec.x = x; |
||
203 | GRec.y = y; |
||
204 | GRec.broken = false; |
||
205 | } |
||
206 | else { |
||
207 | m_glyphWidth[gl] = 1; |
||
208 | } |
||
209 | } |
||
210 | m_glyphOutline[gl] = GRec; |
||
211 | if (GRec.broken && status < ScFace::BROKENGLYPHS) |
||
212 | status = ScFace::BROKENGLYPHS; |
||
213 | } |
||
214 | |||
215 | |||
216 | double FtFace::glyphKerning(uint gl, uint gl2, double size) const |
||
217 | { |
||
218 | FT_Vector delta; |
||
219 | FT_Face face = ftFace(); |
||
220 | double result = 0; |
||
221 | /**** |
||
222 | Ok, this looks like a regression between Freetype 2.1.9 -> 2.1.10. |
||
223 | Ignoring the value of FT_HAS_KERNING for now -- AV |
||
224 | ****/ |
||
225 | if (true || FT_HAS_KERNING(face) ) |
||
226 | { |
||
227 | FT_Error error = FT_Get_Kerning(face, gl, gl2, FT_KERNING_UNSCALED, &delta); |
||
228 | if (error) { |
||
9007 | avox | 229 | sDebug(QString("Error %2 when accessing kerning pair for font %1").arg(scName).arg(error)); |
5980 | avox | 230 | } |
231 | else { |
||
232 | result = delta.x / m_uniEM * size; |
||
233 | } |
||
234 | } |
||
235 | else { |
||
9007 | avox | 236 | sDebug(QString("Font %1 has no kerning pairs (according to Freetype)").arg(scName)); |
5980 | avox | 237 | } |
238 | return result; |
||
239 | } |
||
240 | |||
8972 | avox | 241 | /* |
5980 | avox | 242 | GlyphMetrics FtFace::glyphBBox (uint gl, double sz) const |
243 | { |
||
244 | FT_Face face = ftFace(); |
||
245 | GlyphMetrics result; |
||
246 | FT_Error error = FT_Load_Glyph( face, gl, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP ); |
||
247 | if (!error) { |
||
248 | double w = (face->glyph->metrics.width + QABS((double)face->glyph->metrics.horiBearingX)) / m_uniEM * sz; |
||
249 | result.width = QMAX(w, face->glyph->metrics.horiAdvance / m_uniEM * sz); |
||
250 | double height = face->glyph->metrics.height / m_uniEM * sz; |
||
251 | result.ascent = face->glyph->metrics.horiBearingY / m_uniEM * sz; |
||
6371 | avox | 252 | result.descent = height - result.ascent; |
5980 | avox | 253 | } |
254 | else { |
||
255 | result.width = result.ascent = sz; |
||
256 | result.descent = 0; |
||
257 | } |
||
258 | return result; |
||
259 | } |
||
8972 | avox | 260 | */ |
5980 | avox | 261 | |
8972 | avox | 262 | |
5980 | avox | 263 | /// copied from Freetype's FT_Stream_ReadAt() |
264 | FT_Error ftIOFunc( FT_Stream stream, unsigned long pos, unsigned char* buffer, unsigned long count) |
||
265 | { |
||
266 | FT_Error error = FT_Err_Ok; |
||
267 | FT_ULong read_bytes; |
||
268 | |||
269 | if ( pos >= stream->size ) |
||
270 | { |
||
271 | qDebug( "ftIOFunc: invalid i/o; pos = 0x%lx, size = 0x%lx\n", |
||
272 | pos, stream->size ); |
||
273 | |||
274 | return FT_Err_Invalid_Stream_Operation; |
||
275 | } |
||
276 | |||
277 | if ( stream->read ) |
||
278 | read_bytes = stream->read( stream, pos, buffer, count ); |
||
279 | else |
||
280 | { |
||
281 | read_bytes = stream->size - pos; |
||
282 | if ( read_bytes > count ) |
||
283 | read_bytes = count; |
||
284 | |||
285 | memcpy( buffer, stream->base + pos, read_bytes ); |
||
286 | } |
||
287 | |||
288 | stream->pos = pos + read_bytes; |
||
289 | |||
290 | if ( read_bytes < count ) |
||
291 | { |
||
292 | qDebug( "ftIOFunc: invalid read; expected %lu bytes, got %lu\n", |
||
293 | count, read_bytes ); |
||
294 | |||
295 | error = FT_Err_Invalid_Stream_Operation; |
||
296 | } |
||
297 | |||
298 | return error; |
||
299 | } |
||
300 | |||
301 | |||
6163 | avox | 302 | bool FtFace::glyphNames(QMap<uint, std::pair<QChar, QString> >& GList) const |
5980 | avox | 303 | { |
304 | return GlyNames(ftFace(), GList); |
||
305 | } |
||
306 | |||
307 | |||
308 | void FtFace::RawData(QByteArray & bb) const |
||
309 | { |
||
310 | FT_Stream fts = ftFace()->stream; |
||
311 | bb.resize(fts->size); |
||
312 | bool error = ftIOFunc(fts, 0L, reinterpret_cast<FT_Byte *>(bb.data()), fts->size); |
||
313 | if (error) |
||
314 | { |
||
9007 | avox | 315 | sDebug(QObject::tr("Font %1 is broken (read stream), no embedding").arg(fontFile)); |
5980 | avox | 316 | bb.resize(0); |
317 | status = QMAX(status, ScFace::BROKENGLYPHS); |
||
318 | } |
||
6307 | avox | 319 | /* |
320 | // if (showFontInformation) |
||
5980 | avox | 321 | { |
322 | QFile f(fontFile); |
||
6307 | avox | 323 | qDebug(QObject::tr("RawData for Font %1(%2): size=%3 filesize=%4").arg(fontFile).arg(faceIndex).arg(bb.size()).arg(f.size())); |
5980 | avox | 324 | } |
6307 | avox | 325 | */ |
5980 | avox | 326 | } |
327 | |||
328 |