diff --git a/source/gui/GUItext.cpp b/source/gui/GUItext.cpp
index d832884..fcb6adf 100644
a
|
b
|
GUI text
|
30 | 30 | |
31 | 31 | static const wchar_t TagStart = '['; |
32 | 32 | static const wchar_t TagEnd = ']'; |
| 33 | // List of word demlimitor bounds |
| 34 | // The list contains ranges of word delimitors. The odd indexed chars are the start |
| 35 | // of a range, the even are the end of a range. The list must be sorted in INCREASING ORDER |
| 36 | static const int NUM_WORD_DELIMITORS = 4*2; |
| 37 | static const u16 WordDelimitors[NUM_WORD_DELIMITORS] = { |
| 38 | ' ' , ' ', // spaces |
| 39 | '-' , '-', // hyphens |
| 40 | 0x3000, 0x3002, // ideographic symbols (maybe this range should be wider) |
| 41 | 0x4E00, 0x9FFF // characters in the Chinese unicode block |
| 42 | // TODO add unicode blocks of other languages that don't use spaces |
| 43 | }; |
33 | 44 | |
34 | 45 | void CGUIString::SFeedback::Reset() |
35 | 46 | { |
… |
… |
void CGUIString::SetValue(const CStrW& str)
|
513 | 524 | // Add a delimiter at start and at end, it helps when |
514 | 525 | // processing later, because we don't have make exceptions for |
515 | 526 | // those cases. |
516 | | // We'll sort later. |
517 | 527 | m_Words.push_back(0); |
518 | | m_Words.push_back((int)m_RawString.length()); |
519 | | |
520 | | // Space: ' ' |
521 | | for (position=0, curpos=0;;position = curpos+1) |
522 | | { |
523 | | // Find the next word-delimiter. |
524 | | long dl = m_RawString.Find(position, ' '); |
525 | | |
526 | | if (dl == -1) |
527 | | break; |
528 | | |
529 | | curpos = dl; |
530 | | m_Words.push_back((int)dl+1); |
531 | | } |
532 | | |
533 | | // Dash: '-' |
534 | | for (position=0, curpos=0;;position = curpos+1) |
535 | | { |
536 | | // Find the next word-delimiter. |
537 | | long dl = m_RawString.Find(position, '-'); |
538 | | |
539 | | if (dl == -1) |
540 | | break; |
541 | | |
542 | | curpos = dl; |
543 | | m_Words.push_back((int)dl+1); |
544 | | } |
545 | 528 | |
546 | | // New Line: '\n' |
547 | | for (position=0, curpos=0;;position = curpos+1) |
| 529 | // Add word boundaries in increasing order |
| 530 | for (u32 i = 0; i < m_RawString.length(); ++i) |
548 | 531 | { |
549 | | // Find the next word-delimiter. |
550 | | long dl = m_RawString.Find(position, '\n'); |
551 | | |
552 | | if (dl == -1) |
553 | | break; |
554 | | |
555 | | curpos = dl; |
556 | | |
557 | | // Add before and |
558 | | m_Words.push_back((int)dl); |
559 | | m_Words.push_back((int)dl+1); |
| 532 | wchar_t c = m_RawString[i]; |
| 533 | if (c == '\n') |
| 534 | { |
| 535 | m_Words.push_back((int)i); |
| 536 | m_Words.push_back((int)i+1); |
| 537 | continue; |
| 538 | } |
| 539 | for (int n = 0; n < NUM_WORD_DELIMITORS; n += 2) |
| 540 | { |
| 541 | if (c <= WordDelimitors[n+1]) |
| 542 | { |
| 543 | if (c >= WordDelimitors[n]) |
| 544 | m_Words.push_back((int)i+1); |
| 545 | // assume the WordDelimitors list is stored in increasing order |
| 546 | break; |
| 547 | } |
| 548 | } |
560 | 549 | } |
561 | 550 | |
562 | | sort(m_Words.begin(), m_Words.end()); |
| 551 | m_Words.push_back((int)m_RawString.length()); |
563 | 552 | |
564 | 553 | // Remove duplicates (only if larger than 2) |
565 | 554 | if (m_Words.size() > 2) |
diff --git a/source/tools/fontbuilder2/fontbuilder.py b/source/tools/fontbuilder2/fontbuilder.py
index efffc9c..ef8b05c 100644
a
|
b
|
import Packer
|
7 | 7 | |
8 | 8 | # Representation of a rendered glyph |
9 | 9 | class Glyph(object): |
10 | | def __init__(self, ctx, renderstyle, char, idx): |
| 10 | def __init__(self, ctx, renderstyle, char, idx, face, size): |
11 | 11 | self.renderstyle = renderstyle |
12 | 12 | self.char = char |
13 | 13 | self.idx = idx |
| 14 | self.face = face |
| 15 | self.size = size |
14 | 16 | self.glyph = (idx, 0, 0) |
15 | 17 | |
| 18 | if not ctx.get_font_face() == self.face: |
| 19 | ctx.set_font_face(self.face) |
| 20 | ctx.set_font_size(self.size) |
16 | 21 | extents = ctx.glyph_extents([self.glyph]) |
17 | 22 | |
18 | 23 | self.xadvance = round(extents[4]) |
… |
… |
class Glyph(object):
|
52 | 57 | self.pos = packer.Pack(self.w, self.h) |
53 | 58 | |
54 | 59 | def render(self, ctx): |
| 60 | if not ctx.get_font_face() == self.face: |
| 61 | ctx.set_font_face(self.face) |
| 62 | ctx.set_font_size(self.size) |
55 | 63 | ctx.save() |
56 | 64 | ctx.translate(self.x0, self.y0) |
57 | 65 | ctx.translate(self.pos.x, self.pos.y) |
… |
… |
def load_char_list(filename):
|
81 | 89 | return set(chars) |
82 | 90 | |
83 | 91 | # Construct a Cairo context and surface for rendering text with the given parameters |
84 | | def setup_context(width, height, face, size, renderstyle): |
| 92 | def setup_context(width, height, renderstyle): |
85 | 93 | format = (cairo.FORMAT_ARGB32 if "colour" in renderstyle else cairo.FORMAT_A8) |
86 | 94 | surface = cairo.ImageSurface(format, width, height) |
87 | 95 | ctx = cairo.Context(surface) |
88 | | ctx.set_font_face(face) |
89 | | ctx.set_font_size(size) |
90 | 96 | ctx.set_line_join(cairo.LINE_JOIN_ROUND) |
91 | 97 | return ctx, surface |
92 | 98 | |
93 | | def generate_font(chars, outname, ttf, loadopts, size, renderstyle): |
| 99 | def generate_font(outname, ttfNames, loadopts, size, renderstyle, dsizes): |
94 | 100 | |
95 | | (face, indexes) = FontLoader.create_cairo_font_face_for_file(ttf, 0, loadopts) |
| 101 | faceList = [] |
| 102 | indexList = [] |
| 103 | for i in range(len(ttfNames)): |
| 104 | (face, indices) = FontLoader.create_cairo_font_face_for_file("../../../binaries/data/tools/fontbuilder/fonts/%s" % ttfNames[i], 0, loadopts) |
| 105 | faceList.append(face) |
| 106 | if not ttfNames[i] in dsizes: |
| 107 | dsizes[ttfNames[i]] = 0 |
| 108 | indexList.append(indices) |
96 | 109 | |
97 | | (ctx, _) = setup_context(1, 1, face, size, renderstyle) |
| 110 | (ctx, _) = setup_context(1, 1, renderstyle) |
98 | 111 | |
99 | | (ascent, descent, linespacing, _, _) = ctx.font_extents() |
| 112 | # TODO this gets the line height from the default font |
| 113 | # while entire texts can be in the fallback font |
| 114 | ctx.set_font_face(faceList[0]); |
| 115 | ctx.set_font_size(size + dsizes[ttfNames[0]]) |
| 116 | (_, _, linespacing, _, _) = ctx.font_extents() |
100 | 117 | |
101 | 118 | # Estimate the 'average' height of text, for vertical center alignment |
102 | | charheight = round(ctx.glyph_extents([(indexes("I"), 0.0, 0.0)])[3]) |
| 119 | charheight = round(ctx.glyph_extents([(indexList[0]("I"), 0.0, 0.0)])[3]) |
103 | 120 | |
104 | 121 | # Translate all the characters into glyphs |
105 | 122 | # (This is inefficient if multiple characters have the same glyph) |
106 | 123 | glyphs = [] |
107 | | for c in chars: |
108 | | idx = indexes(c) |
109 | | if ord(c) == 0xFFFD and idx == 0: # use "?" if the missing-glyph glyph is missing |
110 | | idx = indexes("?") |
111 | | if idx: |
112 | | glyphs.append(Glyph(ctx, renderstyle, c, idx)) |
| 124 | #for c in chars: |
| 125 | for c in range(0x20, 0xFFFD): |
| 126 | for i in range(len(indexList)): |
| 127 | idx = indexList[i](unichr(c)) |
| 128 | if c == 0xFFFD and idx == 0: # use "?" if the missing-glyph glyph is missing |
| 129 | idx = indexList[i]("?") |
| 130 | if idx: |
| 131 | glyphs.append(Glyph(ctx, renderstyle, unichr(c), idx, faceList[i], size + dsizes[ttfNames[i]])) |
| 132 | break |
113 | 133 | |
114 | 134 | # Sort by decreasing height (tie-break on decreasing width) |
115 | 135 | glyphs.sort(key = lambda g: (-g.h, -g.w)) |
116 | 136 | |
117 | 137 | # Try various sizes to pack the glyphs into |
118 | 138 | sizes = [] |
119 | | for h in [32, 64, 128, 256, 512, 1024]: |
120 | | for w in [32, 64, 128, 256, 512, 1024]: |
121 | | sizes.append((w, h)) |
| 139 | for h in [32, 64, 128, 256, 512, 1024, 2048, 4096]: |
| 140 | sizes.append((h, h)) |
| 141 | sizes.append((h*2, h)) |
122 | 142 | sizes.sort(key = lambda (w, h): (w*h, max(w, h))) # prefer smaller and squarer |
123 | 143 | |
124 | 144 | for w, h in sizes: |
125 | 145 | try: |
126 | | #packer = Packer.DumbRectanglePacker(w, h) |
127 | | packer = Packer.CygonRectanglePacker(w, h) |
| 146 | packer = Packer.DumbRectanglePacker(w, h) |
| 147 | #packer = Packer.CygonRectanglePacker(w, h) |
128 | 148 | for g in glyphs: |
129 | 149 | g.pack(packer) |
130 | 150 | except Packer.OutOfSpaceError: |
131 | 151 | continue |
132 | 152 | |
133 | | ctx, surface = setup_context(w, h, face, size, renderstyle) |
| 153 | ctx, surface = setup_context(w, h, renderstyle) |
134 | 154 | for g in glyphs: |
135 | | g.render(ctx) |
| 155 | g.render(ctx) |
136 | 156 | surface.write_to_png("%s.png" % outname) |
137 | 157 | |
138 | 158 | # Output the .fnt file with all the glyph positions etc |
… |
… |
def generate_font(chars, outname, ttf, loadopts, size, renderstyle):
|
143 | 163 | fnt.write("%d\n" % len(glyphs)) |
144 | 164 | fnt.write("%d\n" % linespacing) |
145 | 165 | fnt.write("%d\n" % charheight) |
146 | | glyphs.sort(key = lambda g: ord(g.char)) |
| 166 | # sorting unneeded, as glyphs are added in increasing order |
| 167 | #glyphs.sort(key = lambda g: ord(g.char)) |
147 | 168 | for g in glyphs: |
148 | 169 | x0 = g.x0 |
149 | 170 | y0 = g.y0 |
… |
… |
stroked1 = { "colour": True, "stroke": [((0, 0, 0, 1), 2.0), ((0, 0, 0, 1), 2.0)
|
168 | 189 | stroked2 = { "colour": True, "stroke": [((0, 0, 0, 1), 2.0)], "fill": [(1, 1, 1, 1), (1, 1, 1, 1)] } |
169 | 190 | stroked3 = { "colour": True, "stroke": [((0, 0, 0, 1), 2.5)], "fill": [(1, 1, 1, 1), (1, 1, 1, 1)] } |
170 | 191 | |
171 | | chars = load_char_list("charset.txt") |
| 192 | DejaVuSansMono = (["DejaVuSansMono.ttf","FreeMono.ttf", "HanaMinA.ttf"], FontLoader.FT_LOAD_DEFAULT) |
| 193 | DejaVuSans = (["DejaVuSans.ttf","FreeSans.ttf", "HanaMinA.ttf"], FontLoader.FT_LOAD_DEFAULT) |
| 194 | PagellaRegular = (["texgyrepagella-regular.otf","FreeSerif.ttf", "HanaMinA.ttf"], FontLoader.FT_LOAD_NO_HINTING) |
| 195 | PagellaBold = (["texgyrepagella-bold.otf","FreeSerifBold.ttf", "HanaMinA.ttf"], FontLoader.FT_LOAD_NO_HINTING) |
172 | 196 | |
173 | | DejaVuSansMono = ("DejaVuSansMono.ttf", FontLoader.FT_LOAD_DEFAULT) |
174 | | DejaVuSans = ("DejaVuSans.ttf", FontLoader.FT_LOAD_DEFAULT) |
175 | | PagellaRegular = ("texgyrepagella-regular.otf", FontLoader.FT_LOAD_NO_HINTING) |
176 | | PagellaBold = ("texgyrepagella-bold.otf", FontLoader.FT_LOAD_NO_HINTING) |
| 197 | # Define the size differences used to render different fallback fonts |
| 198 | dsizes = {'HanaMinA.ttf': 2} |
177 | 199 | |
178 | 200 | fonts = ( |
179 | 201 | ("mono-10", DejaVuSansMono, 10, filled), |
… |
… |
fonts = (
|
201 | 223 | ("serif-stroke-16", PagellaRegular, 16, stroked2), |
202 | 224 | ) |
203 | 225 | |
204 | | for (name, (fontname, loadopts), size, style) in fonts: |
| 226 | for (name, (fontnames, loadopts), size, style) in fonts: |
205 | 227 | print "%s..." % name |
206 | | generate_font(chars, "../../../binaries/data/mods/public/fonts/%s" % name, "../../../binaries/data/tools/fontbuilder/fonts/%s" % fontname, loadopts, size, style) |
| 228 | generate_font("../../../binaries/data/mods/public/fonts/%s" % name, fontnames, loadopts, size, style, dsizes) |