* [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts
@ 2016-01-15 1:10 Simon Glass
2016-01-15 1:10 ` [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer Simon Glass
` (19 more replies)
0 siblings, 20 replies; 33+ messages in thread
From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw)
To: u-boot
The existing 8x16 font is adequate for most purposes. It is small and fast.
However for boot screens where information must be presented to the user,
the console font is not ideal. Common requirements are larger and
better-looking fonts. In many systems U-Boot is 'behind the scenes' and
does not display user-facing data. For those situations where this is not
the case, we need to present firmware screens with visually attractive
menus.
This series adds a console driver which uses TrueType fonts built into
U-Boot. It can render them at any size. This can be used in scripts to
place text as needed on the display.
This driver is not really designed to operate with the command line. Much
of U-Boot expects a fixed-width font. But to keep things working correctly,
rudimentary support for the console is provided. The main missing feature is
support for command-line editing.
The TrueType implementation is STB, a fairly light-weight TrueType-rendering
implementation from http://nothings.org/. This integrates fairly easily with
U-Boot.
The driver is tested on sandbox and a few ARM hardware devices. It should
be possible to use it on any hardware. The main new dependency is floating
point which is available on many modern systems. Care is taken to ensure
this dependency is isolated to this one driver.
Simon Glass (19):
video: Add stb TrueType font renderer
Makefile: Add rules to build in .ttf files
video kconfig console_normal
video: Use fractional units for X coordinates
video: Handle the 'bell' character
video: Provide a left margin for the text console
video: Provide a signal when a new console line is started
video: Provide a backspace method
video: Add a console driver that uses TrueType fonts
video: Add the Nimbus sans font
video: Add the AnkaCoder mono-spaced font
video: Add the Rufscript handwriting font
video: Add the Cantoraone decorative font
License: Add the Open Font License
video: Allow selection of the driver and font size
video: sandbox: Allow selection of font size and console name
video: sandbox: Enable truetype fonts for sandbox
video: test: Add console tests for truetype
video: Correct 'tor' typo in comment
Licenses/OFL.txt | 97 +
Licenses/README | 1 +
configs/sandbox_defconfig | 4 +-
drivers/video/Kconfig | 36 +-
drivers/video/Makefile | 6 +-
drivers/video/console_normal.c | 24 +-
drivers/video/console_rotate.c | 66 +-
drivers/video/console_truetype.c | 550 +++++
drivers/video/fonts/Kconfig | 51 +
drivers/video/fonts/Makefile | 11 +
drivers/video/fonts/ankacoder_c75_r.ttf | Bin 0 -> 65596 bytes
drivers/video/fonts/cantoraone_regular.ttf | Bin 0 -> 163116 bytes
drivers/video/fonts/nimbus_sans_l_regular.ttf | Bin 0 -> 61660 bytes
drivers/video/fonts/rufscript010.ttf | Bin 0 -> 23080 bytes
drivers/video/sandbox_sdl.c | 2 +
drivers/video/stb_truetype.h | 3240 +++++++++++++++++++++++++
drivers/video/vidconsole-uclass.c | 84 +-
drivers/video/video-uclass.c | 29 +-
include/dm/test.h | 2 +
include/video.h | 7 +-
include/video_console.h | 70 +-
scripts/Makefile.lib | 21 +
test/dm/video.c | 90 +-
23 files changed, 4326 insertions(+), 65 deletions(-)
create mode 100644 Licenses/OFL.txt
create mode 100644 drivers/video/console_truetype.c
create mode 100644 drivers/video/fonts/Kconfig
create mode 100644 drivers/video/fonts/Makefile
create mode 100644 drivers/video/fonts/ankacoder_c75_r.ttf
create mode 100644 drivers/video/fonts/cantoraone_regular.ttf
create mode 100644 drivers/video/fonts/nimbus_sans_l_regular.ttf
create mode 100644 drivers/video/fonts/rufscript010.ttf
create mode 100644 drivers/video/stb_truetype.h
--
2.6.0.rc2.230.g3dd15c0
^ permalink raw reply [flat|nested] 33+ messages in thread* [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:50 ` Måns Rullgård 2016-01-15 1:10 ` [U-Boot] [PATCH 02/19] Makefile: Add rules to build in .ttf files Simon Glass ` (18 subsequent siblings) 19 siblings, 1 reply; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This is a header file which provides a fairly light-weight TrueType rendering implementation. It is pulled from http://nothings.org/. The code style does not comply with U-Boot but I think it is best to leave alone to permit the source to be synced later if needed. The only change is to fix a reference to fabs() which should route through a macro to allow U-Boot to provide its own version. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/stb_truetype.h | 3240 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3240 insertions(+) create mode 100644 drivers/video/stb_truetype.h diff --git a/drivers/video/stb_truetype.h b/drivers/video/stb_truetype.h new file mode 100644 index 0000000..91d8e6f --- /dev/null +++ b/drivers/video/stb_truetype.h @@ -0,0 +1,3240 @@ +// stb_truetype.h - v1.08 - public domain +// authored from 2009-2015 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// Sergey Popov +// Giumo X. Clanjor +// Higor Euripedes +// +// Misc other: +// Ryan Gordon +// +// VERSION HISTORY +// +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevocable license to copy, +// distribute, and modify this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin@top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V at Mio@@o +// :i. V at V +// :oM@@M +// :@@@MM at M +// @@o o at M +// :@@. M at M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #endif + + #ifndef STBTT_fabs + #include <math.h> + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <memory.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +typedef struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + static stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + static stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need@least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clear pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + float y1,y2; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + y1 = (x - x0) / dx + y_top; + y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer 2016-01-15 1:10 ` [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer Simon Glass @ 2016-01-15 1:50 ` Måns Rullgård 2016-01-15 1:56 ` Simon Glass 2016-01-21 16:56 ` Tom Rini 0 siblings, 2 replies; 33+ messages in thread From: Måns Rullgård @ 2016-01-15 1:50 UTC (permalink / raw) To: u-boot Simon Glass <sjg@chromium.org> writes: > This is a header file which provides a fairly light-weight TrueType > rendering implementation. It is pulled from http://nothings.org/. The code > style does not comply with U-Boot but I think it is best to leave alone to > permit the source to be synced later if needed. > > The only change is to fix a reference to fabs() which should route through > a macro to allow U-Boot to provide its own version. This seems to be using floating-point quite a bit. Unless I missed a recent change, that's not allowed in u-boot. -- M?ns Rullg?rd ^ permalink raw reply [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer 2016-01-15 1:50 ` Måns Rullgård @ 2016-01-15 1:56 ` Simon Glass 2016-01-21 16:56 ` Tom Rini 1 sibling, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:56 UTC (permalink / raw) To: u-boot Hi M?ns, On 14 January 2016 at 18:50, M?ns Rullg?rd <mans@mansr.com> wrote: > Simon Glass <sjg@chromium.org> writes: > >> This is a header file which provides a fairly light-weight TrueType >> rendering implementation. It is pulled from http://nothings.org/. The code >> style does not comply with U-Boot but I think it is best to leave alone to >> permit the source to be synced later if needed. >> >> The only change is to fix a reference to fabs() which should route through >> a macro to allow U-Boot to provide its own version. > > This seems to be using floating-point quite a bit. Unless I missed a > recent change, that's not allowed in u-boot. See the cover letter. It works OK in sandbox and I've tested it on rockchip and exynos also. Is there a fixed-point implementation somewhere? I remember Acorn had one years ago [1] but I believe it was written in ARM assembler. Perhaps there is another one somewhere. The floating point is limited to the one driver that uses this header, so it doesn't bleed into the build system, etc. and is not built at all unless that driver is enabled. Regards, Simon [1] e.g. http://www.tofla.iconbar.com/tofla/gfx/fnt01/index.htm ^ permalink raw reply [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer 2016-01-15 1:50 ` Måns Rullgård 2016-01-15 1:56 ` Simon Glass @ 2016-01-21 16:56 ` Tom Rini 2016-01-21 17:05 ` Måns Rullgård 1 sibling, 1 reply; 33+ messages in thread From: Tom Rini @ 2016-01-21 16:56 UTC (permalink / raw) To: u-boot > > style does not comply with U-Boot but I think it is best to leave alone to > > permit the source to be synced later if needed. > > > > The only change is to fix a reference to fabs() which should route through > > a macro to allow U-Boot to provide its own version. > > This seems to be using floating-point quite a bit. Unless I missed a > recent change, that's not allowed in u-boot. You are generally speaking, correct. I am wondering if we don't need to make exceptions, from time to time. For example, when we can easily correct general math problems by using something from <linux/math64.h> that's one thing and should be done. On the other hand, we have this, which is adding a nice looking font for the cases where our console is not a serial port but a screen and in some cases a rather nice high DPI one too. So under the assumption that no, we can't find a font we can borrow that doesn't also use floating point, maybe we allow this, BUT with some caveats needing to be added such as noting that hey, what happens if you 'go' some benchmark that does FP stuff? Well, it better be save/restoring, yes? -- Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160121/b04ee964/attachment.sig> ^ permalink raw reply [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer 2016-01-21 16:56 ` Tom Rini @ 2016-01-21 17:05 ` Måns Rullgård 0 siblings, 0 replies; 33+ messages in thread From: Måns Rullgård @ 2016-01-21 17:05 UTC (permalink / raw) To: u-boot Tom Rini <trini@konsulko.com> writes: >> > style does not comply with U-Boot but I think it is best to leave alone to >> > permit the source to be synced later if needed. >> > >> > The only change is to fix a reference to fabs() which should route through >> > a macro to allow U-Boot to provide its own version. >> >> This seems to be using floating-point quite a bit. Unless I missed a >> recent change, that's not allowed in u-boot. > > You are generally speaking, correct. I am wondering if we don't need to > make exceptions, from time to time. For example, when we can easily > correct general math problems by using something from <linux/math64.h> > that's one thing and should be done. > > On the other hand, we have this, which is adding a nice looking font for > the cases where our console is not a serial port but a screen and in > some cases a rather nice high DPI one too. So under the assumption that > no, we can't find a font we can borrow that doesn't also use floating > point, maybe we allow this, BUT with some caveats needing to be added > such as noting that hey, what happens if you 'go' some benchmark that > does FP stuff? Well, it better be save/restoring, yes? On some CPUs you also need to explicitly enable the FPU, and some rely on software completion of certain operations (usually involving subnormals). Of course we shouldn't let that prevent other systems having nice features. We just need to be a bit careful about when we enable them. -- M?ns Rullg?rd ^ permalink raw reply [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 02/19] Makefile: Add rules to build in .ttf files 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-21 19:11 ` Tom Rini 2016-01-15 1:10 ` [U-Boot] [PATCH 03/19] video kconfig console_normal Simon Glass ` (17 subsequent siblings) 19 siblings, 1 reply; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot Add rules to allow TrueType files to be compiled into U-Boot for use on the video console. Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/Makefile.lib | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index ed30bf5..cff84cf 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -297,6 +297,27 @@ $(obj)/%.dtb: $(src)/%.dts FORCE dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp) +# Fonts +# --------------------------------------------------------------------------- + +# Generate an assembly file to wrap the font data +quiet_cmd_S_ttf= TTF $@ +# Modified for U-Boot +cmd_S_ttf= \ +( \ + echo '.section .rodata.ttf.init,"a"'; \ + echo '.balign 16'; \ + echo '.global __ttf_$(*F)_begin'; \ + echo '__ttf_$(*F)_begin:'; \ + echo '.incbin "$<" '; \ + echo '__ttf_$(*F)_end:'; \ + echo '.global __ttf_$(*F)_end'; \ + echo '.balign 16'; \ +) > $@ + +$(obj)/%.S: $(src)/%.ttf + $(call cmd,S_ttf) + # ACPI # --------------------------------------------------------------------------- quiet_cmd_acpi_c_asl= ASL $@ -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 02/19] Makefile: Add rules to build in .ttf files 2016-01-15 1:10 ` [U-Boot] [PATCH 02/19] Makefile: Add rules to build in .ttf files Simon Glass @ 2016-01-21 19:11 ` Tom Rini 0 siblings, 0 replies; 33+ messages in thread From: Tom Rini @ 2016-01-21 19:11 UTC (permalink / raw) To: u-boot On Thu, Jan 14, 2016 at 06:10:35PM -0700, Simon Glass wrote: > Add rules to allow TrueType files to be compiled into U-Boot for use on > the video console. > > Signed-off-by: Simon Glass <sjg@chromium.org> Reviewed-by: Tom Rini <trini@konsulko.com> -- Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160121/c3953ebe/attachment.sig> ^ permalink raw reply [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 03/19] video kconfig console_normal 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 02/19] Makefile: Add rules to build in .ttf files Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-23 0:14 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 04/19] video: Use fractional units for X coordinates Simon Glass ` (16 subsequent siblings) 19 siblings, 1 reply; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot Signed-off-by: Simon Glass <sjg@chromium.org> --- configs/sandbox_defconfig | 2 +- drivers/video/Kconfig | 12 +++++++++++- drivers/video/Makefile | 5 +++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index b55d5e5..aa92726 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -77,7 +77,7 @@ CONFIG_USB_STORAGE=y CONFIG_USB_KEYBOARD=y CONFIG_SYS_USB_EVENT_POLL=y CONFIG_DM_VIDEO=y -CONFIG_VIDEO_ROTATION=y +CONFIG_CONSOLE_ROTATION=y CONFIG_VIDEO_SANDBOX_SDL=y CONFIG_SYS_VSNPRINTF=y CONFIG_CMD_DHRYSTONE=y diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 35fe192..29ddde2 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -44,7 +44,17 @@ config VIDEO_BPP32 this option, such displays will not be supported and console output will be empty. -config VIDEO_ROTATION +config CONSOLE_NORMAL + bool "Support a simple text console" + depends on DM_VIDEO + default y if DM_VIDEO + help + Support drawing text on the frame buffer console so that it can be + used as a console. Rotation is not supported by this driver (see + CONFIG_CONSOLE_ROTATION for that). A built-in 8x16 font is used + for the display. + +config CONSOLE_ROTATION bool "Support rotated displays" depends on DM_VIDEO help diff --git a/drivers/video/Makefile b/drivers/video/Makefile index f6f1106..1a76655 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -9,12 +9,13 @@ ifdef CONFIG_DM obj-$(CONFIG_DISPLAY) += display-uclass.o obj-$(CONFIG_DM_VIDEO) += backlight-uclass.o obj-$(CONFIG_DM_VIDEO) += panel-uclass.o simple_panel.o -obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o console_normal.o +obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o obj-$(CONFIG_DM_VIDEO) += video_bmp.o ifdef CONFIG_DM_VIDEO obj-$(CONFIG_DM_PWM) += pwm_backlight.o endif -obj-$(CONFIG_VIDEO_ROTATION) += console_rotate.o +obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o +obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o endif obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH v2 03/19] video kconfig console_normal 2016-01-15 1:10 ` [U-Boot] [PATCH 03/19] video kconfig console_normal Simon Glass @ 2016-01-23 0:14 ` Anatolij Gustschin 0 siblings, 0 replies; 33+ messages in thread From: Anatolij Gustschin @ 2016-01-23 0:14 UTC (permalink / raw) To: u-boot From: Simon Glass <sjg@chromium.org> Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Anatolij Gustschin <agust@denx.de> --- Changes in v2: rebased configs/sandbox_defconfig | 2 +- drivers/video/Kconfig | 12 +++++++++++- drivers/video/Makefile | 5 +++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 09ced01..2ebcba0 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -77,7 +77,7 @@ CONFIG_USB_STORAGE=y CONFIG_USB_KEYBOARD=y CONFIG_SYS_USB_EVENT_POLL=y CONFIG_DM_VIDEO=y -CONFIG_VIDEO_ROTATION=y +CONFIG_CONSOLE_ROTATION=y CONFIG_VIDEO_SANDBOX_SDL=y CONFIG_CMD_DHRYSTONE=y CONFIG_TPM=y diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index ae122da..f06ecfe 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -44,7 +44,17 @@ config VIDEO_BPP32 this option, such displays will not be supported and console output will be empty. -config VIDEO_ROTATION +config CONSOLE_NORMAL + bool "Support a simple text console" + depends on DM_VIDEO + default y if DM_VIDEO + help + Support drawing text on the frame buffer console so that it can be + used as a console. Rotation is not supported by this driver (see + CONFIG_CONSOLE_ROTATION for that). A built-in 8x16 font is used + for the display. + +config CONSOLE_ROTATION bool "Support rotated displays" depends on DM_VIDEO help diff --git a/drivers/video/Makefile b/drivers/video/Makefile index ee04629..01f4be5 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -7,9 +7,10 @@ ifdef CONFIG_DM obj-$(CONFIG_DISPLAY_PORT) += dp-uclass.o -obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o console_normal.o +obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o obj-$(CONFIG_DM_VIDEO) += video_bmp.o -obj-$(CONFIG_VIDEO_ROTATION) += console_rotate.o +obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o +obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o endif obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o -- 1.7.9.5 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 04/19] video: Use fractional units for X coordinates 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (2 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 03/19] video kconfig console_normal Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-23 0:19 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 05/19] video: Handle the 'bell' character Simon Glass ` (15 subsequent siblings) 19 siblings, 1 reply; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot With anti-aliased fonts we need a more fine-grained horizontal position than a single pixel. Characters can be positioned to start part-way through a pixel, with anti-aliasing (greyscale edges) taking care of the visual effect. To cope with this, use fractional units (1/256 pixel) for horizontal positions in the text console. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/console_normal.c | 24 ++++++++++++-- drivers/video/console_rotate.c | 66 +++++++++++++++++++++++++++++---------- drivers/video/vidconsole-uclass.c | 57 +++++++++++++++++++-------------- include/video_console.h | 40 ++++++++++++++++++------ test/dm/video.c | 4 +-- 5 files changed, 138 insertions(+), 53 deletions(-) diff --git a/drivers/video/console_normal.c b/drivers/video/console_normal.c index d1031c8..89a55dd 100644 --- a/drivers/video/console_normal.c +++ b/drivers/video/console_normal.c @@ -71,13 +71,18 @@ static int console_normal_move_rows(struct udevice *dev, uint rowdst, return 0; } -static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch) +static int console_normal_putc_xy(struct udevice *dev, uint x_frac, uint y, + char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int i, row; void *line = vid_priv->fb + y * vid_priv->line_length + - x * VNBYTES(vid_priv->bpix); + VID_TO_PIXEL(x_frac) * VNBYTES(vid_priv->bpix); + + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; for (row = 0; row < VIDEO_FONT_HEIGHT; row++) { uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row]; @@ -125,6 +130,20 @@ static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch) line += vid_priv->line_length; } + return VID_TO_POS(VIDEO_FONT_WIDTH); +} + +static int console_normal_probe(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + + vc_priv->x_charsize = VIDEO_FONT_WIDTH; + vc_priv->y_charsize = VIDEO_FONT_HEIGHT; + vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH; + vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT; + return 0; } @@ -138,4 +157,5 @@ U_BOOT_DRIVER(vidconsole_normal) = { .name = "vidconsole0", .id = UCLASS_VIDEO_CONSOLE, .ops = &console_normal_ops, + .probe = console_normal_probe, }; diff --git a/drivers/video/console_rotate.c b/drivers/video/console_rotate.c index 4c5c4ef..227141d 100644 --- a/drivers/video/console_rotate.c +++ b/drivers/video/console_rotate.c @@ -82,17 +82,22 @@ static int console_move_rows_1(struct udevice *dev, uint rowdst, uint rowsrc, return 0; } -static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch) +static int console_putc_xy_1(struct udevice *dev, uint x_frac, uint y, char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int pbytes = VNBYTES(vid_priv->bpix); int i, col; int mask = 0x80; - void *line = vid_priv->fb + (x + 1) * vid_priv->line_length - - (y + 1) * pbytes; + void *line; uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT; + line = vid_priv->fb + (VID_TO_PIXEL(x_frac) + 1) * + vid_priv->line_length - (y + 1) * pbytes; + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; + for (col = 0; col < VIDEO_FONT_HEIGHT; col++) { switch (vid_priv->bpix) { #ifdef CONFIG_VIDEO_BPP8 @@ -135,7 +140,7 @@ static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch) mask >>= 1; } - return 0; + return VID_TO_POS(VIDEO_FONT_WIDTH); } @@ -201,16 +206,21 @@ static int console_move_rows_2(struct udevice *dev, uint rowdst, uint rowsrc, return 0; } -static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch) +static int console_putc_xy_2(struct udevice *dev, uint x_frac, uint y, char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int i, row; void *line; - line = vid_priv->fb + (vid_priv->ysize - y - 1) * vid_priv->line_length + - (vid_priv->xsize - x - VIDEO_FONT_WIDTH - 1) * - VNBYTES(vid_priv->bpix); + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; + + line = vid_priv->fb + (vid_priv->ysize - y - 1) * + vid_priv->line_length + + (vid_priv->xsize - VID_TO_PIXEL(x_frac) - + VIDEO_FONT_WIDTH - 1) * VNBYTES(vid_priv->bpix); for (row = 0; row < VIDEO_FONT_HEIGHT; row++) { uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row]; @@ -258,7 +268,7 @@ static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch) line -= vid_priv->line_length; } - return 0; + return VID_TO_POS(VIDEO_FONT_WIDTH); } static int console_set_row_3(struct udevice *dev, uint row, int clr) @@ -328,17 +338,22 @@ static int console_move_rows_3(struct udevice *dev, uint rowdst, uint rowsrc, return 0; } -static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch) +static int console_putc_xy_3(struct udevice *dev, uint x_frac, uint y, char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int pbytes = VNBYTES(vid_priv->bpix); int i, col; int mask = 0x80; - void *line = vid_priv->fb + (vid_priv->ysize - x - 1) * + void *line = vid_priv->fb + + (vid_priv->ysize - VID_TO_PIXEL(x_frac) - 1) * vid_priv->line_length + y * pbytes; uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT; + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; + for (col = 0; col < VIDEO_FONT_HEIGHT; col++) { switch (vid_priv->bpix) { #ifdef CONFIG_VIDEO_BPP8 @@ -381,17 +396,35 @@ static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch) mask >>= 1; } - return 0; + return VID_TO_POS(VIDEO_FONT_WIDTH); } +static int console_probe_2(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + + vc_priv->x_charsize = VIDEO_FONT_WIDTH; + vc_priv->y_charsize = VIDEO_FONT_HEIGHT; + vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH; + vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT; + + return 0; +} + static int console_probe_1_3(struct udevice *dev) { - struct vidconsole_priv *priv = dev_get_uclass_priv(dev); - struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); - priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH; - priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT; + vc_priv->x_charsize = VIDEO_FONT_WIDTH; + vc_priv->y_charsize = VIDEO_FONT_HEIGHT; + vc_priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH; + vc_priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT; + vc_priv->xsize_frac = VID_TO_POS(vid_priv->ysize); return 0; } @@ -425,6 +458,7 @@ U_BOOT_DRIVER(vidconsole_2) = { .name = "vidconsole2", .id = UCLASS_VIDEO_CONSOLE, .ops = &console_ops_2, + .probe = console_probe_2, }; U_BOOT_DRIVER(vidconsole_3) = { diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index a4c919e..da92ee8 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -52,14 +52,14 @@ static void vidconsole_back(struct udevice *dev) { struct vidconsole_priv *priv = dev_get_uclass_priv(dev); - if (--priv->curr_col < 0) { - priv->curr_col = priv->cols - 1; - if (--priv->curr_row < 0) - priv->curr_row = 0; + priv->xcur_frac -= VID_TO_POS(priv->x_charsize); + if (priv->xcur_frac < 0) { + priv->xcur_frac = (priv->cols - 1) * + VID_TO_POS(priv->x_charsize); + priv->ycur -= priv->y_charsize; + if (priv->ycur < 0) + priv->ycur = 0; } - - vidconsole_putc_xy(dev, priv->curr_col * VIDEO_FONT_WIDTH, - priv->curr_row * VIDEO_FONT_HEIGHT, ' '); } /* Move to a newline, scrolling the display if necessary */ @@ -71,15 +71,16 @@ static void vidconsole_newline(struct udevice *dev) const int rows = CONFIG_CONSOLE_SCROLL_LINES; int i; - priv->curr_col = 0; + priv->xcur_frac = 0; + priv->ycur += priv->y_charsize; /* Check if we need to scroll the terminal */ - if (++priv->curr_row >= priv->rows) { + if ((priv->ycur + priv->y_charsize) / priv->y_charsize > priv->rows) { vidconsole_move_rows(dev, 0, rows, priv->rows - rows); for (i = 0; i < rows; i++) vidconsole_set_row(dev, priv->rows - i - 1, vid_priv->colour_bg); - priv->curr_row -= rows; + priv->ycur -= rows * priv->y_charsize; } video_sync(dev->parent); } @@ -91,16 +92,16 @@ int vidconsole_put_char(struct udevice *dev, char ch) switch (ch) { case '\r': - priv->curr_col = 0; + priv->xcur_frac = 0; break; case '\n': vidconsole_newline(dev); break; case '\t': /* Tab (8 chars alignment) */ - priv->curr_col += 8; - priv->curr_col &= ~7; + priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac) + + 1) * priv->tab_width_frac; - if (priv->curr_col >= priv->cols) + if (priv->xcur_frac >= priv->xsize_frac) vidconsole_newline(dev); break; case '\b': @@ -112,13 +113,16 @@ int vidconsole_put_char(struct udevice *dev, char ch) * colour depth. Check this and return an error to help with * diagnosis. */ - ret = vidconsole_putc_xy(dev, - priv->curr_col * VIDEO_FONT_WIDTH, - priv->curr_row * VIDEO_FONT_HEIGHT, - ch); - if (ret) + ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch); + if (ret == -EAGAIN) { + vidconsole_newline(dev); + ret = vidconsole_putc_xy(dev, priv->xcur_frac, + priv->ycur, ch); + } + if (ret < 0) return ret; - if (++priv->curr_col >= priv->cols) + priv->xcur_frac += ret; + if (priv->xcur_frac >= priv->xsize_frac) vidconsole_newline(dev); break; } @@ -149,8 +153,7 @@ static int vidconsole_pre_probe(struct udevice *dev) struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); - priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT; - priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH; + priv->xsize_frac = VID_TO_POS(vid_priv->xsize); return 0; } @@ -162,12 +165,16 @@ static int vidconsole_post_probe(struct udevice *dev) struct stdio_dev *sdev = &priv->sdev; int ret; + if (!priv->tab_width_frac) + priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8; + if (dev->seq) { snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d", dev->seq); } else { strcpy(sdev->name, "vidconsole"); } + sdev->flags = DEV_FLAGS_OUTPUT; sdev->putc = vidconsole_putc; sdev->puts = vidconsole_puts; @@ -190,9 +197,11 @@ UCLASS_DRIVER(vidconsole) = { void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row) { struct vidconsole_priv *priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); - priv->curr_col = min_t(short, col, priv->cols - 1); - priv->curr_row = min_t(short, row, priv->rows - 1); + priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1)); + priv->ycur = min_t(short, row, vid_priv->ysize - 1); } static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc, diff --git a/include/video_console.h b/include/video_console.h index d4acbc8..2f7012e 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -7,21 +7,37 @@ #ifndef __video_console_h #define __video_console_h +#define VID_FRAC_DIV 256 + +#define VID_TO_PIXEL(x) ((x) / VID_FRAC_DIV) +#define VID_TO_POS(x) ((x) * VID_FRAC_DIV) + /** * struct vidconsole_priv - uclass-private data about a console device * + * Drivers must set up @rows, @cols, @x_charsize, @y_charsize in their probe() + * method. Drivers may set up @xstart_frac if desired. + * * @sdev: stdio device, acting as an output sink - * @curr_col: Current text column (0=left) - * @curr_row: Current row (0=top) + * @xcur_frac: Current X position, in fractional units (VID_TO_POS(x)) + * @curr_row: Current Y position in pixels (0=top) * @rows: Number of text rows * @cols: Number of text columns + * @x_charsize: Character width in pixels + * @y_charsize: Character height in pixels + * @tab_width_frac: Tab width in fractional units + * @xsize_frac: Width of the display in fractional units */ struct vidconsole_priv { struct stdio_dev sdev; - int curr_col; - int curr_row; + int xcur_frac; + int ycur; int rows; int cols; + int x_charsize; + int y_charsize; + int tab_width_frac; + int xsize_frac; }; /** @@ -36,12 +52,15 @@ struct vidconsole_ops { * putc_xy() - write a single character to a position * * @dev: Device to write to - * @x: Pixel X position (0=left-most pixel) + * @x_frac: Fractional pixel X position (0=left-most pixel) which + * is the X position multipled by VID_FRAC_DIV. * @y: Pixel Y position (0=top-most pixel) * @ch: Character to write - * @return 0 if OK, -ve on error + * @return number of fractional pixels that the cursor should move, + * if all is OK, -EAGAIN if we ran out of space on this line, other -ve + * on error */ - int (*putc_xy)(struct udevice *dev, uint x, uint y, char ch); + int (*putc_xy)(struct udevice *dev, uint x_frac, uint y, char ch); /** * move_rows() - Move text rows from one place to another @@ -75,10 +94,13 @@ struct vidconsole_ops { * vidconsole_putc_xy() - write a single character to a position * * @dev: Device to write to - * @x: Pixel X position (0=left-most pixel) + * @x_frac: Fractional pixel X position (0=left-most pixel) which + * is the X position multipled by VID_FRAC_DIV. * @y: Pixel Y position (0=top-most pixel) * @ch: Character to write - * @return 0 if OK, -ve on error + * @return number of fractional pixels that the cursor should move, + * if all is OK, -EAGAIN if we ran out of space on this line, other -ve + * on error */ int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch); diff --git a/test/dm/video.c b/test/dm/video.c index 52aba2e..3540271 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -106,14 +106,14 @@ static int dm_test_video_text(struct unit_test_state *uts) ut_asserteq(46, compress_frame_buffer(dev)); for (i = 0; i < 20; i++) - vidconsole_putc_xy(con, i * 8, 0, ' ' + i); + vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); ut_asserteq(273, compress_frame_buffer(dev)); vidconsole_set_row(con, 0, WHITE); ut_asserteq(46, compress_frame_buffer(dev)); for (i = 0; i < 20; i++) - vidconsole_putc_xy(con, i * 8, 0, ' ' + i); + vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); ut_asserteq(273, compress_frame_buffer(dev)); return 0; -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH v2 04/19] video: Use fractional units for X coordinates 2016-01-15 1:10 ` [U-Boot] [PATCH 04/19] video: Use fractional units for X coordinates Simon Glass @ 2016-01-23 0:19 ` Anatolij Gustschin 0 siblings, 0 replies; 33+ messages in thread From: Anatolij Gustschin @ 2016-01-23 0:19 UTC (permalink / raw) To: u-boot From: Simon Glass <sjg@chromium.org> With anti-aliased fonts we need a more fine-grained horizontal position than a single pixel. Characters can be positioned to start part-way through a pixel, with anti-aliasing (greyscale edges) taking care of the visual effect. To cope with this, use fractional units (1/256 pixel) for horizontal positions in the text console. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Anatolij Gustschin <agust@denx.de> --- Changes in v2: rebased drivers/video/console_normal.c | 24 ++++++++++++-- drivers/video/console_rotate.c | 65 ++++++++++++++++++++++++++++--------- drivers/video/vidconsole-uclass.c | 56 ++++++++++++++++++-------------- include/video_console.h | 40 ++++++++++++++++++----- test/dm/video.c | 4 +-- 5 files changed, 136 insertions(+), 53 deletions(-) diff --git a/drivers/video/console_normal.c b/drivers/video/console_normal.c index d1031c8..89a55dd 100644 --- a/drivers/video/console_normal.c +++ b/drivers/video/console_normal.c @@ -71,13 +71,18 @@ static int console_normal_move_rows(struct udevice *dev, uint rowdst, return 0; } -static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch) +static int console_normal_putc_xy(struct udevice *dev, uint x_frac, uint y, + char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int i, row; void *line = vid_priv->fb + y * vid_priv->line_length + - x * VNBYTES(vid_priv->bpix); + VID_TO_PIXEL(x_frac) * VNBYTES(vid_priv->bpix); + + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; for (row = 0; row < VIDEO_FONT_HEIGHT; row++) { uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row]; @@ -125,6 +130,20 @@ static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch) line += vid_priv->line_length; } + return VID_TO_POS(VIDEO_FONT_WIDTH); +} + +static int console_normal_probe(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + + vc_priv->x_charsize = VIDEO_FONT_WIDTH; + vc_priv->y_charsize = VIDEO_FONT_HEIGHT; + vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH; + vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT; + return 0; } @@ -138,4 +157,5 @@ U_BOOT_DRIVER(vidconsole_normal) = { .name = "vidconsole0", .id = UCLASS_VIDEO_CONSOLE, .ops = &console_normal_ops, + .probe = console_normal_probe, }; diff --git a/drivers/video/console_rotate.c b/drivers/video/console_rotate.c index ebb31d8..227141d 100644 --- a/drivers/video/console_rotate.c +++ b/drivers/video/console_rotate.c @@ -82,17 +82,22 @@ static int console_move_rows_1(struct udevice *dev, uint rowdst, uint rowsrc, return 0; } -static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch) +static int console_putc_xy_1(struct udevice *dev, uint x_frac, uint y, char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int pbytes = VNBYTES(vid_priv->bpix); int i, col; int mask = 0x80; - void *line = vid_priv->fb + (x + 1) * vid_priv->line_length - - (y + 1) * pbytes; + void *line; uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT; + line = vid_priv->fb + (VID_TO_PIXEL(x_frac) + 1) * + vid_priv->line_length - (y + 1) * pbytes; + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; + for (col = 0; col < VIDEO_FONT_HEIGHT; col++) { switch (vid_priv->bpix) { #ifdef CONFIG_VIDEO_BPP8 @@ -135,7 +140,7 @@ static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch) mask >>= 1; } - return 0; + return VID_TO_POS(VIDEO_FONT_WIDTH); } @@ -201,17 +206,21 @@ static int console_move_rows_2(struct udevice *dev, uint rowdst, uint rowsrc, return 0; } -static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch) +static int console_putc_xy_2(struct udevice *dev, uint x_frac, uint y, char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int i, row; void *line; + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; + line = vid_priv->fb + (vid_priv->ysize - y - 1) * - vid_priv->line_length + - (vid_priv->xsize - x - VIDEO_FONT_WIDTH - 1) * - VNBYTES(vid_priv->bpix); + vid_priv->line_length + + (vid_priv->xsize - VID_TO_PIXEL(x_frac) - + VIDEO_FONT_WIDTH - 1) * VNBYTES(vid_priv->bpix); for (row = 0; row < VIDEO_FONT_HEIGHT; row++) { uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row]; @@ -259,7 +268,7 @@ static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch) line -= vid_priv->line_length; } - return 0; + return VID_TO_POS(VIDEO_FONT_WIDTH); } static int console_set_row_3(struct udevice *dev, uint row, int clr) @@ -329,17 +338,22 @@ static int console_move_rows_3(struct udevice *dev, uint rowdst, uint rowsrc, return 0; } -static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch) +static int console_putc_xy_3(struct udevice *dev, uint x_frac, uint y, char ch) { + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); int pbytes = VNBYTES(vid_priv->bpix); int i, col; int mask = 0x80; - void *line = vid_priv->fb + (vid_priv->ysize - x - 1) * + void *line = vid_priv->fb + + (vid_priv->ysize - VID_TO_PIXEL(x_frac) - 1) * vid_priv->line_length + y * pbytes; uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT; + if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac) + return -EAGAIN; + for (col = 0; col < VIDEO_FONT_HEIGHT; col++) { switch (vid_priv->bpix) { #ifdef CONFIG_VIDEO_BPP8 @@ -382,17 +396,35 @@ static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch) mask >>= 1; } - return 0; + return VID_TO_POS(VIDEO_FONT_WIDTH); } +static int console_probe_2(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + + vc_priv->x_charsize = VIDEO_FONT_WIDTH; + vc_priv->y_charsize = VIDEO_FONT_HEIGHT; + vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH; + vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT; + + return 0; +} + static int console_probe_1_3(struct udevice *dev) { - struct vidconsole_priv *priv = dev_get_uclass_priv(dev); - struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); - priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH; - priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT; + vc_priv->x_charsize = VIDEO_FONT_WIDTH; + vc_priv->y_charsize = VIDEO_FONT_HEIGHT; + vc_priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH; + vc_priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT; + vc_priv->xsize_frac = VID_TO_POS(vid_priv->ysize); return 0; } @@ -426,6 +458,7 @@ U_BOOT_DRIVER(vidconsole_2) = { .name = "vidconsole2", .id = UCLASS_VIDEO_CONSOLE, .ops = &console_ops_2, + .probe = console_probe_2, }; U_BOOT_DRIVER(vidconsole_3) = { diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index ea10189..d997186 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -52,14 +52,14 @@ static void vidconsole_back(struct udevice *dev) { struct vidconsole_priv *priv = dev_get_uclass_priv(dev); - if (--priv->curr_col < 0) { - priv->curr_col = priv->cols - 1; - if (--priv->curr_row < 0) - priv->curr_row = 0; + priv->xcur_frac -= VID_TO_POS(priv->x_charsize); + if (priv->xcur_frac < 0) { + priv->xcur_frac = (priv->cols - 1) * + VID_TO_POS(priv->x_charsize); + priv->ycur -= priv->y_charsize; + if (priv->ycur < 0) + priv->ycur = 0; } - - vidconsole_putc_xy(dev, priv->curr_col * VIDEO_FONT_WIDTH, - priv->curr_row * VIDEO_FONT_HEIGHT, ' '); } /* Move to a newline, scrolling the display if necessary */ @@ -71,15 +71,16 @@ static void vidconsole_newline(struct udevice *dev) const int rows = CONFIG_CONSOLE_SCROLL_LINES; int i; - priv->curr_col = 0; + priv->xcur_frac = 0; + priv->ycur += priv->y_charsize; /* Check if we need to scroll the terminal */ - if (++priv->curr_row >= priv->rows) { + if ((priv->ycur + priv->y_charsize) / priv->y_charsize > priv->rows) { vidconsole_move_rows(dev, 0, rows, priv->rows - rows); for (i = 0; i < rows; i++) vidconsole_set_row(dev, priv->rows - i - 1, vid_priv->colour_bg); - priv->curr_row -= rows; + priv->ycur -= rows * priv->y_charsize; } video_sync(dev->parent); } @@ -91,16 +92,16 @@ int vidconsole_put_char(struct udevice *dev, char ch) switch (ch) { case '\r': - priv->curr_col = 0; + priv->xcur_frac = 0; break; case '\n': vidconsole_newline(dev); break; case '\t': /* Tab (8 chars alignment) */ - priv->curr_col += 8; - priv->curr_col &= ~7; + priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac) + + 1) * priv->tab_width_frac; - if (priv->curr_col >= priv->cols) + if (priv->xcur_frac >= priv->xsize_frac) vidconsole_newline(dev); break; case '\b': @@ -112,13 +113,16 @@ int vidconsole_put_char(struct udevice *dev, char ch) * colour depth. Check this and return an error to help with * diagnosis. */ - ret = vidconsole_putc_xy(dev, - priv->curr_col * VIDEO_FONT_WIDTH, - priv->curr_row * VIDEO_FONT_HEIGHT, - ch); - if (ret) + ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch); + if (ret == -EAGAIN) { + vidconsole_newline(dev); + ret = vidconsole_putc_xy(dev, priv->xcur_frac, + priv->ycur, ch); + } + if (ret < 0) return ret; - if (++priv->curr_col >= priv->cols) + priv->xcur_frac += ret; + if (priv->xcur_frac >= priv->xsize_frac) vidconsole_newline(dev); break; } @@ -148,8 +152,7 @@ static int vidconsole_pre_probe(struct udevice *dev) struct udevice *vid = dev->parent; struct video_priv *vid_priv = dev_get_uclass_priv(vid); - priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT; - priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH; + priv->xsize_frac = VID_TO_POS(vid_priv->xsize); return 0; } @@ -161,6 +164,9 @@ static int vidconsole_post_probe(struct udevice *dev) struct stdio_dev *sdev = &priv->sdev; int ret; + if (!priv->tab_width_frac) + priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8; + strlcpy(sdev->name, dev->name, sizeof(sdev->name)); sdev->flags = DEV_FLAGS_OUTPUT; sdev->putc = vidconsole_putc; @@ -184,9 +190,11 @@ UCLASS_DRIVER(vidconsole) = { void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row) { struct vidconsole_priv *priv = dev_get_uclass_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); - priv->curr_col = min_t(short, col, priv->cols - 1); - priv->curr_row = min_t(short, row, priv->rows - 1); + priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1)); + priv->ycur = min_t(short, row, vid_priv->ysize - 1); } static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc, diff --git a/include/video_console.h b/include/video_console.h index c0fc792..eeba368 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -7,21 +7,37 @@ #ifndef __video_console_h #define __video_console_h +#define VID_FRAC_DIV 256 + +#define VID_TO_PIXEL(x) ((x) / VID_FRAC_DIV) +#define VID_TO_POS(x) ((x) * VID_FRAC_DIV) + /** * struct vidconsole_priv - uclass-private data about a console device * + * Drivers must set up @rows, @cols, @x_charsize, @y_charsize in their probe() + * method. Drivers may set up @xstart_frac if desired. + * * @sdev: stdio device, acting as an output sink - * @curr_col: Current text column (0=left) - * @curr_row: Current row (0=top) + * @xcur_frac: Current X position, in fractional units (VID_TO_POS(x)) + * @curr_row: Current Y position in pixels (0=top) * @rows: Number of text rows * @cols: Number of text columns + * @x_charsize: Character width in pixels + * @y_charsize: Character height in pixels + * @tab_width_frac: Tab width in fractional units + * @xsize_frac: Width of the display in fractional units */ struct vidconsole_priv { struct stdio_dev sdev; - int curr_col; - int curr_row; + int xcur_frac; + int ycur; int rows; int cols; + int x_charsize; + int y_charsize; + int tab_width_frac; + int xsize_frac; }; /** @@ -36,12 +52,15 @@ struct vidconsole_ops { * putc_xy() - write a single character to a position * * @dev: Device to write to - * @x: Pixel X position (0=left-most pixel) + * @x_frac: Fractional pixel X position (0=left-most pixel) which + * is the X position multipled by VID_FRAC_DIV. * @y: Pixel Y position (0=top-most pixel) * @ch: Character to write - * @return 0 if OK, -ve on error + * @return number of fractional pixels that the cursor should move, + * if all is OK, -EAGAIN if we ran out of space on this line, other -ve + * on error */ - int (*putc_xy)(struct udevice *dev, uint x, uint y, char ch); + int (*putc_xy)(struct udevice *dev, uint x_frac, uint y, char ch); /** * move_rows() - Move text rows from one place to another @@ -75,10 +94,13 @@ struct vidconsole_ops { * vidconsole_putc_xy() - write a single character to a position * * @dev: Device to write to - * @x: Pixel X position (0=left-most pixel) + * @x_frac: Fractional pixel X position (0=left-most pixel) which + * is the X position multipled by VID_FRAC_DIV. * @y: Pixel Y position (0=top-most pixel) * @ch: Character to write - * @return 0 if OK, -ve on error + * @return number of fractional pixels that the cursor should move, + * if all is OK, -EAGAIN if we ran out of space on this line, other -ve + * on error */ int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch); diff --git a/test/dm/video.c b/test/dm/video.c index 9f5e7fc..be94633 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -106,14 +106,14 @@ static int dm_test_video_text(struct unit_test_state *uts) ut_asserteq(46, compress_frame_buffer(dev)); for (i = 0; i < 20; i++) - vidconsole_putc_xy(con, i * 8, 0, ' ' + i); + vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); ut_asserteq(273, compress_frame_buffer(dev)); vidconsole_set_row(con, 0, WHITE); ut_asserteq(46, compress_frame_buffer(dev)); for (i = 0; i < 20; i++) - vidconsole_putc_xy(con, i * 8, 0, ' ' + i); + vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i); ut_asserteq(273, compress_frame_buffer(dev)); return 0; -- 1.7.9.5 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 05/19] video: Handle the 'bell' character 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (3 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 04/19] video: Use fractional units for X coordinates Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 06/19] video: Provide a left margin for the text console Simon Glass ` (14 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This can be sent when to many characters are entered. Make sure it is ignored and does not cause a character to be displayed. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/vidconsole-uclass.c | 3 +++ test/dm/video.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index da92ee8..d9a9615 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -91,6 +91,9 @@ int vidconsole_put_char(struct udevice *dev, char ch) int ret; switch (ch) { + case '\a': + /* beep */ + break; case '\r': priv->xcur_frac = 0; break; diff --git a/test/dm/video.c b/test/dm/video.c index 3540271..cd00c96 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -124,7 +124,7 @@ DM_TEST(dm_test_video_text, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); static int dm_test_video_chars(struct unit_test_state *uts) { struct udevice *dev, *con; - const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very modest \bman\n\t\tand Has much to\b\bto be modest about."; + const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very \amodest \bman\n\t\tand Has much to\b\bto be modest about."; const char *s; ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 06/19] video: Provide a left margin for the text console 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (4 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 05/19] video: Handle the 'bell' character Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 07/19] video: Provide a signal when a new console line is started Simon Glass ` (13 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot Allow the left margin to be set so that text does not have to be right up against the left side. On some panels this makes it hard to read. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/vidconsole-uclass.c | 6 +++--- include/video_console.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index d9a9615..fec4a60 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -53,7 +53,7 @@ static void vidconsole_back(struct udevice *dev) struct vidconsole_priv *priv = dev_get_uclass_priv(dev); priv->xcur_frac -= VID_TO_POS(priv->x_charsize); - if (priv->xcur_frac < 0) { + if (priv->xcur_frac < priv->xstart_frac) { priv->xcur_frac = (priv->cols - 1) * VID_TO_POS(priv->x_charsize); priv->ycur -= priv->y_charsize; @@ -71,7 +71,7 @@ static void vidconsole_newline(struct udevice *dev) const int rows = CONFIG_CONSOLE_SCROLL_LINES; int i; - priv->xcur_frac = 0; + priv->xcur_frac = priv->xstart_frac; priv->ycur += priv->y_charsize; /* Check if we need to scroll the terminal */ @@ -95,7 +95,7 @@ int vidconsole_put_char(struct udevice *dev, char ch) /* beep */ break; case '\r': - priv->xcur_frac = 0; + priv->xcur_frac = priv->xstart_frac; break; case '\n': vidconsole_newline(dev); diff --git a/include/video_console.h b/include/video_console.h index 2f7012e..3bfc37f 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -27,6 +27,7 @@ * @y_charsize: Character height in pixels * @tab_width_frac: Tab width in fractional units * @xsize_frac: Width of the display in fractional units + * @xstart_frac: Left margin for the text console in fractional units */ struct vidconsole_priv { struct stdio_dev sdev; @@ -38,6 +39,7 @@ struct vidconsole_priv { int y_charsize; int tab_width_frac; int xsize_frac; + int xstart_frac; }; /** -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 07/19] video: Provide a signal when a new console line is started 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (5 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 06/19] video: Provide a left margin for the text console Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 08/19] video: Provide a backspace method Simon Glass ` (12 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot When we start a new line (due to the user pressing return), signal this to the driver so that it can flush its buffer of character positions. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/vidconsole-uclass.c | 14 ++++++++++++++ include/video_console.h | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index fec4a60..182aaed 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -47,6 +47,15 @@ int vidconsole_set_row(struct udevice *dev, uint row, int clr) return ops->set_row(dev, row, clr); } +static int vidconsole_entry_start(struct udevice *dev) +{ + struct vidconsole_ops *ops = vidconsole_get_ops(dev); + + if (!ops->entry_start) + return -ENOSYS; + return ops->entry_start(dev); +} + /* Move backwards one space */ static void vidconsole_back(struct udevice *dev) { @@ -82,6 +91,8 @@ static void vidconsole_newline(struct udevice *dev) vid_priv->colour_bg); priv->ycur -= rows * priv->y_charsize; } + priv->last_ch = 0; + video_sync(dev->parent); } @@ -99,6 +110,7 @@ int vidconsole_put_char(struct udevice *dev, char ch) break; case '\n': vidconsole_newline(dev); + vidconsole_entry_start(dev); break; case '\t': /* Tab (8 chars alignment) */ priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac) @@ -109,6 +121,7 @@ int vidconsole_put_char(struct udevice *dev, char ch) break; case '\b': vidconsole_back(dev); + priv->last_ch = 0; break; default: /* @@ -125,6 +138,7 @@ int vidconsole_put_char(struct udevice *dev, char ch) if (ret < 0) return ret; priv->xcur_frac += ret; + priv->last_ch = ch; if (priv->xcur_frac >= priv->xsize_frac) vidconsole_newline(dev); break; diff --git a/include/video_console.h b/include/video_console.h index 3bfc37f..80bc948 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -28,6 +28,7 @@ * @tab_width_frac: Tab width in fractional units * @xsize_frac: Width of the display in fractional units * @xstart_frac: Left margin for the text console in fractional units + * @last_ch: Last character written to the text console on this line */ struct vidconsole_priv { struct stdio_dev sdev; @@ -40,6 +41,7 @@ struct vidconsole_priv { int tab_width_frac; int xsize_frac; int xstart_frac; + int last_ch; }; /** @@ -87,6 +89,18 @@ struct vidconsole_ops { * @return 0 if OK, -ve on error */ int (*set_row)(struct udevice *dev, uint row, int clr); + + /** + * entry_start() - Indicate that text entry is starting afresh + * + * Consoles which use proportional fonts need to track the position of + * each character output so that backspace will return to the correct + * place. This method signals to the console driver that a new entry + * line is being start (e.g. the user pressed return to start a new + * command). The driver can use this signal to empty its list of + * positions. + */ + int (*entry_start)(struct udevice *dev); }; /* Get a pointer to the driver operations for a video console device */ -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 08/19] video: Provide a backspace method 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (6 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 07/19] video: Provide a signal when a new console line is started Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 09/19] video: Add a console driver that uses TrueType fonts Simon Glass ` (11 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot With proportional fonts the vidconsole uclass cannot itself erase the previous character. Provide an optional method so that the driver can handle this operation. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/vidconsole-uclass.c | 12 +++++++++++- include/video_console.h | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c index 182aaed..832e90a 100644 --- a/drivers/video/vidconsole-uclass.c +++ b/drivers/video/vidconsole-uclass.c @@ -57,9 +57,17 @@ static int vidconsole_entry_start(struct udevice *dev) } /* Move backwards one space */ -static void vidconsole_back(struct udevice *dev) +static int vidconsole_back(struct udevice *dev) { struct vidconsole_priv *priv = dev_get_uclass_priv(dev); + struct vidconsole_ops *ops = vidconsole_get_ops(dev); + int ret; + + if (ops->backspace) { + ret = ops->backspace(dev); + if (ret != -ENOSYS) + return ret; + } priv->xcur_frac -= VID_TO_POS(priv->x_charsize); if (priv->xcur_frac < priv->xstart_frac) { @@ -69,6 +77,8 @@ static void vidconsole_back(struct udevice *dev) if (priv->ycur < 0) priv->ycur = 0; } + + return 0; } /* Move to a newline, scrolling the display if necessary */ diff --git a/include/video_console.h b/include/video_console.h index 80bc948..1345748 100644 --- a/include/video_console.h +++ b/include/video_console.h @@ -101,6 +101,20 @@ struct vidconsole_ops { * positions. */ int (*entry_start)(struct udevice *dev); + + /** + * backspace() - Handle erasing the last character + * + * With proportional fonts the vidconsole uclass cannot itself erase + * the previous character. This optional method will be called when + * a backspace is needed. The driver should erase the previous + * character and update the cursor position (xcur_frac, ycur) to the + * start of the previous character. + * + * If not implement, default behaviour will work for fixed-width + * characters. + */ + int (*backspace)(struct udevice *dev); }; /* Get a pointer to the driver operations for a video console device */ -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 09/19] video: Add a console driver that uses TrueType fonts 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (7 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 08/19] video: Provide a backspace method Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-23 0:21 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 10/19] video: Add the Nimbus sans font Simon Glass ` (10 subsequent siblings) 19 siblings, 1 reply; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot The existing 8x16 font is adequate for most purposes. It is small and fast. However for boot screens where information must be presented to the user, the console font is not ideal. Common requirements are larger and better-looking fonts. This console driver can use TrueType fonts built into U-Boot, and render them at any size. This can be used in scripts to place text as needed on the display. This driver is not really designed to operate with the command line. Much of U-Boot expects a fixed-width font. But to keep things working correctly, rudimentary support for the console is provided. The main missing feature is support for command-line editing. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/Kconfig | 24 ++ drivers/video/Makefile | 1 + drivers/video/console_truetype.c | 533 +++++++++++++++++++++++++++++++++++++++ drivers/video/fonts/Kconfig | 7 + drivers/video/fonts/Makefile | 6 + drivers/video/video-uclass.c | 11 +- 6 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 drivers/video/console_truetype.c create mode 100644 drivers/video/fonts/Kconfig create mode 100644 drivers/video/fonts/Makefile diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 29ddde2..2826082 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -67,6 +67,30 @@ config CONSOLE_ROTATION struct video_priv: 0=unrotated, 1=90 degrees clockwise, 2=180 degrees, 3=270 degrees. +config CONSOLE_TRUETYPE + bool "Support a console that uses TrueType fonts" + depends on DM_VIDEO + help + TrueTrype fonts can provide outline-drawing capability rather than + needing to provide a bitmap for each font and size that is needed. + With this option you can adjust the text size and use a variety of + fonts. Note that this is noticeably slower than with normal console. + +config CONSOLE_TRUETYPE_SIZE + int "TrueType font size" + depends on CONSOLE_TRUETYPE + default 18 + help + This sets the font size for the console. The size is measured in + pixels and is the nominal height of a character. Note that fonts + are commonly measured in 'points', being 1/72 inch (about 3.52mm). + However that measurement depends on the size of your display and + there is no standard display density. At present there is not a + method to select the display's physical size, which would allow + U-Boot to calculate the correct font size. + +source "drivers/video/fonts/Kconfig" + config VIDEO_VESA bool "Enable VESA video driver support" default n diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 1a76655..c9056cb 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DM_PWM) += pwm_backlight.o endif obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o +obj-$(CONFIG_CONSOLE_TRUETYPE) += console_truetype.o fonts/ endif obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c new file mode 100644 index 0000000..b770ad4 --- /dev/null +++ b/drivers/video/console_truetype.c @@ -0,0 +1,533 @@ +/* + * Copyright (c) 2016 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <video.h> +#include <video_console.h> + +/* Functions needed by stb_truetype.h */ +static int tt_floor(double val) +{ + if (val < 0) + return (int)(val - 0.999); + + return (int)val; +} + +static int tt_ceil(double val) +{ + if (val < 0) + return (int)val; + + return (int)(val + 0.999); +} + +static double frac(double val) +{ + return val - tt_floor(val); +} + +static double tt_fabs(double x) +{ + return x < 0 ? -x : x; +} + + /* + * Simple square root algorithm. This is from: + * http://stackoverflow.com/questions/1623375/writing-your-own-square-root-function + * Written by Chihung Yu + * Creative Commons license + * http://creativecommons.org/licenses/by-sa/3.0/legalcode + * It has been modified to compile correctly, and for U-Boot style. + */ +static double tt_sqrt(double value) +{ + double lo = 1.0; + double hi = value; + + while (hi - lo > 0.00001) { + double mid = lo + (hi - lo) / 2; + + if (mid * mid - value > 0.00001) + hi = mid; + else + lo = mid; + } + + return lo; +} + +#define STBTT_ifloor tt_floor +#define STBTT_iceil tt_ceil +#define STBTT_fabs tt_fabs +#define STBTT_sqrt tt_sqrt +#define STBTT_malloc(size, u) ((void)(u), malloc(size)) +#define STBTT_free(size, u) ((void)(u), free(size)) +#define STBTT_assert(x) +#define STBTT_strlen(x) strlen(x) +#define STBTT_memcpy memcpy +#define STBTT_memset memset + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +/** + * struct pos_info - Records a cursor position + * + * @xpos_frac: Fractional X position in pixels (multiplied by VID_FRAC_DIV) + * @ypos: Y position (pixels from the top) + */ +struct pos_info { + int xpos_frac; + int ypos; +}; + +/* + * Allow one for each character on the command line plus one for each newline. + * This is just an estimate, but it should not be exceeded. + */ +#define POS_HISTORY_SIZE (CONFIG_SYS_CBSIZE * 11 / 10) + +/** + * struct console_tt_priv - Private data for this driver + * + * @font_size: Vertical font size in pixels + * @font_data: Pointer to TrueType font file contents + * @font: TrueType font information for the current font + * @pos: List of cursor positions for each character written. This is + * used to handle backspace. We clear the frame buffer between + * the last position and the current position, thus erasing the + * last character. We record enough characters to go back to the + * start of the current command line. + * @pos_ptr: Current position in the position history + * @baseline: Pixel offset of the font's baseline from the cursor position. + * This is the 'ascent' of the font, scaled to pixel coordinates. + * It measures the distance from the baseline to the top of the + * font. + * @scale: Scale of the font. This is calculated from the pixel height + * of the font. It is used by the STB library to generate images + * of the correct size. + */ +struct console_tt_priv { + int font_size; + u8 *font_data; + stbtt_fontinfo font; + struct pos_info pos[POS_HISTORY_SIZE]; + int pos_ptr; + int baseline; + double scale; +}; + +static int console_truetype_set_row(struct udevice *dev, uint row, int clr) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + struct console_tt_priv *priv = dev_get_priv(dev); + void *line; + int pixels = priv->font_size * vid_priv->line_length; + int i; + + line = vid_priv->fb + row * priv->font_size * vid_priv->line_length; + switch (vid_priv->bpix) { +#ifdef CONFIG_VIDEO_BPP8 + case VIDEO_BPP8: { + uint8_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP16 + case VIDEO_BPP16: { + uint16_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP32 + case VIDEO_BPP32: { + uint32_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif + default: + return -ENOSYS; + } + + return 0; +} + +static int console_truetype_move_rows(struct udevice *dev, uint rowdst, + uint rowsrc, uint count) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + struct console_tt_priv *priv = dev_get_priv(dev); + void *dst; + void *src; + int i, diff; + + dst = vid_priv->fb + rowdst * priv->font_size * vid_priv->line_length; + src = vid_priv->fb + rowsrc * priv->font_size * vid_priv->line_length; + memmove(dst, src, priv->font_size * vid_priv->line_length * count); + + /* Scroll up our position history */ + diff = (rowsrc - rowdst) * priv->font_size; + for (i = 0; i < priv->pos_ptr; i++) + priv->pos[i].ypos -= diff; + + return 0; +} + +static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y, + char ch) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid); + struct console_tt_priv *priv = dev_get_priv(dev); + stbtt_fontinfo *font = &priv->font; + int width, height, xoff, yoff; + double xpos, x_shift; + int lsb; + int width_frac, linenum; + struct pos_info *pos; + u8 *bits, *data; + int advance; + void *line; + int row; + + /* First get some basic metrics about this character */ + stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb); + + /* + * First out our current X position in fractional pixels. If we wrote + * a character previously, using kerning to fine-tune the position of + * this character */ + xpos = frac(VID_TO_PIXEL((double)x)); + if (vc_priv->last_ch) { + xpos += priv->scale * stbtt_GetCodepointKernAdvance(font, + vc_priv->last_ch, ch); + } + + /* + * Figure out where the cursor will move to after this character, and + * abort if we are out of space on this line. Also calculate the + * effective width of this character, which will be our return value: + * it dictates how much the cursor will move forward on the line. + */ + x_shift = xpos - (double)tt_floor(xpos); + xpos += advance * priv->scale; + width_frac = (int)VID_TO_POS(xpos); + if (x + width_frac >= vc_priv->xsize_frac) + return -EAGAIN; + + /* Write the current cursor position into history */ + if (priv->pos_ptr < POS_HISTORY_SIZE) { + pos = &priv->pos[priv->pos_ptr]; + pos->xpos_frac = vc_priv->xcur_frac; + pos->ypos = vc_priv->ycur; + priv->pos_ptr++; + } + + /* + * Figure out how much past the start of a pixel we are, and pass this + * information into the render, which will return a 8-bit-per-pixel + * image of the character. For empty characters, like ' ', data will + * return NULL; + */ + data = stbtt_GetCodepointBitmapSubpixel(font, priv->scale, priv->scale, + x_shift, 0, ch, &width, &height, + &xoff, &yoff); + if (!data) + return width_frac; + + /* Figure out where to write the character in the frame buffer */ + bits = data; + line = vid_priv->fb + y * vid_priv->line_length + + VID_TO_PIXEL(x) * VNBYTES(vid_priv->bpix); + linenum = priv->baseline + yoff; + if (linenum > 0) + line += linenum * vid_priv->line_length; + + /* + * Write a row at a time, converting the 8bpp image into the colour + * depth of the display. We only expect white-on-black or the reverse + * so the code only handles this simple case. + */ + for (row = 0; row < height; row++) { + switch (vid_priv->bpix) { +#ifdef CONFIG_VIDEO_BPP16 + case VIDEO_BPP16: { + uint16_t *dst = (uint16_t *)line + xoff; + int i; + + for (i = 0; i < width; i++) { + int val = *bits; + int out; + + if (vid_priv->colour_bg) + val = 255 - val; + out = val >> 3 | + (val >> 2) << 5 | + (val >> 3) << 11; + if (vid_priv->colour_fg) + *dst++ |= out; + else + *dst++ &= out; + bits++; + } + break; + } +#endif + default: + return -ENOSYS; + } + + line += vid_priv->line_length; + } + free(data); + + return width_frac; +} + +/** + * console_truetype_erase() - Erase a character + * + * This is used for backspace. We erase a square of the display within the + * given bounds. + * + * @dev: Device to update + * @xstart: X start position in pixels from the left + * @ystart: Y start position in pixels from the top + * @xend: X end position in pixels from the left + * @yend: Y end position in pixels from the top + * @clr: Value to write + * @return 0 if OK, -ENOSYS if the display depth is not supported + */ +static int console_truetype_erase(struct udevice *dev, int xstart, int ystart, + int xend, int yend, int clr) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + void *line; + int pixels = xend - xstart; + int row, i; + + line = vid_priv->fb + ystart * vid_priv->line_length; + line += xstart * VNBYTES(vid_priv->bpix); + for (row = ystart; row < yend; row++) { + switch (vid_priv->bpix) { +#ifdef CONFIG_VIDEO_BPP8 + case VIDEO_BPP8: { + uint8_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP16 + case VIDEO_BPP16: { + uint16_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP32 + case VIDEO_BPP32: { + uint32_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif + default: + return -ENOSYS; + } + line += vid_priv->line_length; + } + + return 0; +} + +/** + * console_truetype_backspace() - Handle a backspace operation + * + * This clears the previous character so that the console looks as if it had + * not been entered. + * + * @dev: Device to update + * @return 0 if OK, -ENOSYS if not supported + */ +static int console_truetype_backspace(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct console_tt_priv *priv = dev_get_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + struct pos_info *pos; + int xend; + + /* + * This indicates a very strange error higher in the stack. The caller + * has sent out n character and n + 1 backspaces. + */ + if (!priv->pos_ptr) + return -ENOSYS; + + /* Pop the last cursor position off the stack */ + pos = &priv->pos[--priv->pos_ptr]; + + /* + * Figure out the end position for clearing. Normlly it is the current + * cursor position, but if we are clearing a character on the previous + * line, we clear from the end of the line. + */ + if (pos->ypos == vc_priv->ycur) + xend = VID_TO_PIXEL(vc_priv->xcur_frac); + else + xend = vid_priv->xsize; + + console_truetype_erase(dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos, + xend, pos->ypos + vc_priv->y_charsize, + vid_priv->colour_bg); + + /* Move the cursor back to where it was when we pushed this record */ + vc_priv->xcur_frac = pos->xpos_frac; + vc_priv->ycur = pos->ypos; + + return 0; +} + +static int console_truetype_entry_start(struct udevice *dev) +{ + struct console_tt_priv *priv = dev_get_priv(dev); + + /* A new input line has start, so clear our history */ + priv->pos_ptr = 0; + + return 0; +} + +/* + * Provides a list of fonts which can be obtained at run-time in U-Boot. These + * are compiled in by the Makefile. + * + * At present there is no mechanism to select a particular font - the first + * one found is the one that is used. But the build system and the code here + * supports multiple fonts, which may be useful for certain firmware screens. + */ +struct font_info { + char *name; + u8 *begin; + u8 *end; +}; + +#define FONT_DECL(_name) \ + extern u8 __ttf_ ## _name ## _begin[]; \ + extern u8 __ttf_ ## _name ## _end[]; + +#define FONT_ENTRY(_name) { \ + .name = #_name, \ + .begin = __ttf_ ## _name ## _begin, \ + .end = __ttf_ ## _name ## _end, \ + } + +static struct font_info font_table[] = { + {} /* sentinel */ +}; + +#define FONT_BEGIN(name) __ttf_ ## name ## _begin +#define FONT_END(name) __ttf_ ## name ## _end +#define FONT_IS_VALID(name) (abs(FONT_END(name) - FONT_BEGIN) > 4) + +/** + * console_truetype_find_font() - Find a suitable font + * + * This searched for the first available font. + * + * @return pointer to the font, or NULL if none is found + */ +static u8 *console_truetype_find_font(void) +{ + struct font_info *tab; + + for (tab = font_table; tab->begin; tab++) { + if (abs(tab->begin - tab->end) > 4) { + debug("%s: Font '%s', at %p, size %lx\n", __func__, + tab->name, tab->begin, + (ulong)(tab->end - tab->begin)); + return tab->begin; + } + } + + return NULL; +} + +static int console_truetype_probe(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct console_tt_priv *priv = dev_get_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + stbtt_fontinfo *font = &priv->font; + int ascent; + + debug("%s: start\n", __func__); + if (vid_priv->font_size) + priv->font_size = vid_priv->font_size; + else + priv->font_size = CONFIG_CONSOLE_TRUETYPE_SIZE; + priv->font_data = console_truetype_find_font(); + if (!priv->font_data) { + debug("%s: Could not find any fonts\n", __func__); + return -EBFONT; + } + + vc_priv->x_charsize = priv->font_size; + vc_priv->y_charsize = priv->font_size; + vc_priv->xstart_frac = VID_TO_POS(2); + vc_priv->cols = vid_priv->xsize / priv->font_size; + vc_priv->rows = vid_priv->ysize / priv->font_size; + vc_priv->tab_width_frac = VID_TO_POS(priv->font_size) * 8 / 2; + + if (!stbtt_InitFont(font, priv->font_data, 0)) { + debug("%s: Font init failed\n", __func__); + return -EPERM; + } + + /* Pre-calculate some things we will need regularly */ + priv->scale = stbtt_ScaleForPixelHeight(font, priv->font_size); + stbtt_GetFontVMetrics(font, &ascent, 0, 0); + priv->baseline = (int)(ascent * priv->scale); + debug("%s: ready\n", __func__); + + return 0; +} + +struct vidconsole_ops console_truetype_ops = { + .putc_xy = console_truetype_putc_xy, + .move_rows = console_truetype_move_rows, + .set_row = console_truetype_set_row, + .backspace = console_truetype_backspace, + .entry_start = console_truetype_entry_start, +}; + +U_BOOT_DRIVER(vidconsole_truetype) = { + .name = "vidconsole_tt", + .id = UCLASS_VIDEO_CONSOLE, + .ops = &console_truetype_ops, + .probe = console_truetype_probe, + .priv_auto_alloc_size = sizeof(struct console_tt_priv), +}; diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig new file mode 100644 index 0000000..ad16ce6 --- /dev/null +++ b/drivers/video/fonts/Kconfig @@ -0,0 +1,7 @@ +# +# Video fonts +# + +menu "TrueType Fonts" + +endmenu diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile new file mode 100644 index 0000000..6ab4647 --- /dev/null +++ b/drivers/video/fonts/Makefile @@ -0,0 +1,6 @@ +# +# (C) Copyright 2000-2007 +# Wolfgang Denk, DENX Software Engineering, wd at denx.de. +# +# SPDX-License-Identifier: GPL-2.0+ +# diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 24d537e..2189fce 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -201,11 +201,18 @@ static int video_post_probe(struct udevice *dev) * it might be useful to support only bitmap drawing on the device * for boards that don't need to display text. */ - snprintf(name, sizeof(name), "%s.vidconsole", dev->name); + if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) { + snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name); + strcpy(drv, "vidconsole_tt"); + } else { + snprintf(name, sizeof(name), "%s.vidconsole%d", dev->name, + priv->rot); + snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot); + } + str = strdup(name); if (!str) return -ENOMEM; - snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot); ret = device_bind_driver(dev, drv, str, &cons); if (ret) { debug("%s: Cannot bind console driver\n", __func__); -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH v2 09/19] video: Add a console driver that uses TrueType fonts 2016-01-15 1:10 ` [U-Boot] [PATCH 09/19] video: Add a console driver that uses TrueType fonts Simon Glass @ 2016-01-23 0:21 ` Anatolij Gustschin 0 siblings, 0 replies; 33+ messages in thread From: Anatolij Gustschin @ 2016-01-23 0:21 UTC (permalink / raw) To: u-boot From: Simon Glass <sjg@chromium.org> The existing 8x16 font is adequate for most purposes. It is small and fast. However for boot screens where information must be presented to the user, the console font is not ideal. Common requirements are larger and better-looking fonts. This console driver can use TrueType fonts built into U-Boot, and render them at any size. This can be used in scripts to place text as needed on the display. This driver is not really designed to operate with the command line. Much of U-Boot expects a fixed-width font. But to keep things working correctly, rudimentary support for the console is provided. The main missing feature is support for command-line editing. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Anatolij Gustschin <agust@denx.de> --- Changes in v2: rebased drivers/video/Kconfig | 24 ++ drivers/video/Makefile | 1 + drivers/video/console_truetype.c | 533 ++++++++++++++++++++++++++++++++++++++ drivers/video/fonts/Kconfig | 7 + drivers/video/fonts/Makefile | 6 + drivers/video/video-uclass.c | 11 +- 6 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 drivers/video/console_truetype.c create mode 100644 drivers/video/fonts/Kconfig create mode 100644 drivers/video/fonts/Makefile diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index f06ecfe..279c5f9 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -67,6 +67,30 @@ config CONSOLE_ROTATION struct video_priv: 0=unrotated, 1=90 degrees clockwise, 2=180 degrees, 3=270 degrees. +config CONSOLE_TRUETYPE + bool "Support a console that uses TrueType fonts" + depends on DM_VIDEO + help + TrueTrype fonts can provide outline-drawing capability rather than + needing to provide a bitmap for each font and size that is needed. + With this option you can adjust the text size and use a variety of + fonts. Note that this is noticeably slower than with normal console. + +config CONSOLE_TRUETYPE_SIZE + int "TrueType font size" + depends on CONSOLE_TRUETYPE + default 18 + help + This sets the font size for the console. The size is measured in + pixels and is the nominal height of a character. Note that fonts + are commonly measured in 'points', being 1/72 inch (about 3.52mm). + However that measurement depends on the size of your display and + there is no standard display density. At present there is not a + method to select the display's physical size, which would allow + U-Boot to calculate the correct font size. + +source "drivers/video/fonts/Kconfig" + config VIDEO_VESA bool "Enable VESA video driver support" default n diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 01f4be5..b2ee40d 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o obj-$(CONFIG_DM_VIDEO) += video_bmp.o obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o +obj-$(CONFIG_CONSOLE_TRUETYPE) += console_truetype.o fonts/ endif obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c new file mode 100644 index 0000000..b770ad4 --- /dev/null +++ b/drivers/video/console_truetype.c @@ -0,0 +1,533 @@ +/* + * Copyright (c) 2016 Google, Inc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <video.h> +#include <video_console.h> + +/* Functions needed by stb_truetype.h */ +static int tt_floor(double val) +{ + if (val < 0) + return (int)(val - 0.999); + + return (int)val; +} + +static int tt_ceil(double val) +{ + if (val < 0) + return (int)val; + + return (int)(val + 0.999); +} + +static double frac(double val) +{ + return val - tt_floor(val); +} + +static double tt_fabs(double x) +{ + return x < 0 ? -x : x; +} + + /* + * Simple square root algorithm. This is from: + * http://stackoverflow.com/questions/1623375/writing-your-own-square-root-function + * Written by Chihung Yu + * Creative Commons license + * http://creativecommons.org/licenses/by-sa/3.0/legalcode + * It has been modified to compile correctly, and for U-Boot style. + */ +static double tt_sqrt(double value) +{ + double lo = 1.0; + double hi = value; + + while (hi - lo > 0.00001) { + double mid = lo + (hi - lo) / 2; + + if (mid * mid - value > 0.00001) + hi = mid; + else + lo = mid; + } + + return lo; +} + +#define STBTT_ifloor tt_floor +#define STBTT_iceil tt_ceil +#define STBTT_fabs tt_fabs +#define STBTT_sqrt tt_sqrt +#define STBTT_malloc(size, u) ((void)(u), malloc(size)) +#define STBTT_free(size, u) ((void)(u), free(size)) +#define STBTT_assert(x) +#define STBTT_strlen(x) strlen(x) +#define STBTT_memcpy memcpy +#define STBTT_memset memset + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +/** + * struct pos_info - Records a cursor position + * + * @xpos_frac: Fractional X position in pixels (multiplied by VID_FRAC_DIV) + * @ypos: Y position (pixels from the top) + */ +struct pos_info { + int xpos_frac; + int ypos; +}; + +/* + * Allow one for each character on the command line plus one for each newline. + * This is just an estimate, but it should not be exceeded. + */ +#define POS_HISTORY_SIZE (CONFIG_SYS_CBSIZE * 11 / 10) + +/** + * struct console_tt_priv - Private data for this driver + * + * @font_size: Vertical font size in pixels + * @font_data: Pointer to TrueType font file contents + * @font: TrueType font information for the current font + * @pos: List of cursor positions for each character written. This is + * used to handle backspace. We clear the frame buffer between + * the last position and the current position, thus erasing the + * last character. We record enough characters to go back to the + * start of the current command line. + * @pos_ptr: Current position in the position history + * @baseline: Pixel offset of the font's baseline from the cursor position. + * This is the 'ascent' of the font, scaled to pixel coordinates. + * It measures the distance from the baseline to the top of the + * font. + * @scale: Scale of the font. This is calculated from the pixel height + * of the font. It is used by the STB library to generate images + * of the correct size. + */ +struct console_tt_priv { + int font_size; + u8 *font_data; + stbtt_fontinfo font; + struct pos_info pos[POS_HISTORY_SIZE]; + int pos_ptr; + int baseline; + double scale; +}; + +static int console_truetype_set_row(struct udevice *dev, uint row, int clr) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + struct console_tt_priv *priv = dev_get_priv(dev); + void *line; + int pixels = priv->font_size * vid_priv->line_length; + int i; + + line = vid_priv->fb + row * priv->font_size * vid_priv->line_length; + switch (vid_priv->bpix) { +#ifdef CONFIG_VIDEO_BPP8 + case VIDEO_BPP8: { + uint8_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP16 + case VIDEO_BPP16: { + uint16_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP32 + case VIDEO_BPP32: { + uint32_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif + default: + return -ENOSYS; + } + + return 0; +} + +static int console_truetype_move_rows(struct udevice *dev, uint rowdst, + uint rowsrc, uint count) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + struct console_tt_priv *priv = dev_get_priv(dev); + void *dst; + void *src; + int i, diff; + + dst = vid_priv->fb + rowdst * priv->font_size * vid_priv->line_length; + src = vid_priv->fb + rowsrc * priv->font_size * vid_priv->line_length; + memmove(dst, src, priv->font_size * vid_priv->line_length * count); + + /* Scroll up our position history */ + diff = (rowsrc - rowdst) * priv->font_size; + for (i = 0; i < priv->pos_ptr; i++) + priv->pos[i].ypos -= diff; + + return 0; +} + +static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y, + char ch) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct udevice *vid = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid); + struct console_tt_priv *priv = dev_get_priv(dev); + stbtt_fontinfo *font = &priv->font; + int width, height, xoff, yoff; + double xpos, x_shift; + int lsb; + int width_frac, linenum; + struct pos_info *pos; + u8 *bits, *data; + int advance; + void *line; + int row; + + /* First get some basic metrics about this character */ + stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb); + + /* + * First out our current X position in fractional pixels. If we wrote + * a character previously, using kerning to fine-tune the position of + * this character */ + xpos = frac(VID_TO_PIXEL((double)x)); + if (vc_priv->last_ch) { + xpos += priv->scale * stbtt_GetCodepointKernAdvance(font, + vc_priv->last_ch, ch); + } + + /* + * Figure out where the cursor will move to after this character, and + * abort if we are out of space on this line. Also calculate the + * effective width of this character, which will be our return value: + * it dictates how much the cursor will move forward on the line. + */ + x_shift = xpos - (double)tt_floor(xpos); + xpos += advance * priv->scale; + width_frac = (int)VID_TO_POS(xpos); + if (x + width_frac >= vc_priv->xsize_frac) + return -EAGAIN; + + /* Write the current cursor position into history */ + if (priv->pos_ptr < POS_HISTORY_SIZE) { + pos = &priv->pos[priv->pos_ptr]; + pos->xpos_frac = vc_priv->xcur_frac; + pos->ypos = vc_priv->ycur; + priv->pos_ptr++; + } + + /* + * Figure out how much past the start of a pixel we are, and pass this + * information into the render, which will return a 8-bit-per-pixel + * image of the character. For empty characters, like ' ', data will + * return NULL; + */ + data = stbtt_GetCodepointBitmapSubpixel(font, priv->scale, priv->scale, + x_shift, 0, ch, &width, &height, + &xoff, &yoff); + if (!data) + return width_frac; + + /* Figure out where to write the character in the frame buffer */ + bits = data; + line = vid_priv->fb + y * vid_priv->line_length + + VID_TO_PIXEL(x) * VNBYTES(vid_priv->bpix); + linenum = priv->baseline + yoff; + if (linenum > 0) + line += linenum * vid_priv->line_length; + + /* + * Write a row at a time, converting the 8bpp image into the colour + * depth of the display. We only expect white-on-black or the reverse + * so the code only handles this simple case. + */ + for (row = 0; row < height; row++) { + switch (vid_priv->bpix) { +#ifdef CONFIG_VIDEO_BPP16 + case VIDEO_BPP16: { + uint16_t *dst = (uint16_t *)line + xoff; + int i; + + for (i = 0; i < width; i++) { + int val = *bits; + int out; + + if (vid_priv->colour_bg) + val = 255 - val; + out = val >> 3 | + (val >> 2) << 5 | + (val >> 3) << 11; + if (vid_priv->colour_fg) + *dst++ |= out; + else + *dst++ &= out; + bits++; + } + break; + } +#endif + default: + return -ENOSYS; + } + + line += vid_priv->line_length; + } + free(data); + + return width_frac; +} + +/** + * console_truetype_erase() - Erase a character + * + * This is used for backspace. We erase a square of the display within the + * given bounds. + * + * @dev: Device to update + * @xstart: X start position in pixels from the left + * @ystart: Y start position in pixels from the top + * @xend: X end position in pixels from the left + * @yend: Y end position in pixels from the top + * @clr: Value to write + * @return 0 if OK, -ENOSYS if the display depth is not supported + */ +static int console_truetype_erase(struct udevice *dev, int xstart, int ystart, + int xend, int yend, int clr) +{ + struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent); + void *line; + int pixels = xend - xstart; + int row, i; + + line = vid_priv->fb + ystart * vid_priv->line_length; + line += xstart * VNBYTES(vid_priv->bpix); + for (row = ystart; row < yend; row++) { + switch (vid_priv->bpix) { +#ifdef CONFIG_VIDEO_BPP8 + case VIDEO_BPP8: { + uint8_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP16 + case VIDEO_BPP16: { + uint16_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif +#ifdef CONFIG_VIDEO_BPP32 + case VIDEO_BPP32: { + uint32_t *dst = line; + + for (i = 0; i < pixels; i++) + *dst++ = clr; + break; + } +#endif + default: + return -ENOSYS; + } + line += vid_priv->line_length; + } + + return 0; +} + +/** + * console_truetype_backspace() - Handle a backspace operation + * + * This clears the previous character so that the console looks as if it had + * not been entered. + * + * @dev: Device to update + * @return 0 if OK, -ENOSYS if not supported + */ +static int console_truetype_backspace(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct console_tt_priv *priv = dev_get_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + struct pos_info *pos; + int xend; + + /* + * This indicates a very strange error higher in the stack. The caller + * has sent out n character and n + 1 backspaces. + */ + if (!priv->pos_ptr) + return -ENOSYS; + + /* Pop the last cursor position off the stack */ + pos = &priv->pos[--priv->pos_ptr]; + + /* + * Figure out the end position for clearing. Normlly it is the current + * cursor position, but if we are clearing a character on the previous + * line, we clear from the end of the line. + */ + if (pos->ypos == vc_priv->ycur) + xend = VID_TO_PIXEL(vc_priv->xcur_frac); + else + xend = vid_priv->xsize; + + console_truetype_erase(dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos, + xend, pos->ypos + vc_priv->y_charsize, + vid_priv->colour_bg); + + /* Move the cursor back to where it was when we pushed this record */ + vc_priv->xcur_frac = pos->xpos_frac; + vc_priv->ycur = pos->ypos; + + return 0; +} + +static int console_truetype_entry_start(struct udevice *dev) +{ + struct console_tt_priv *priv = dev_get_priv(dev); + + /* A new input line has start, so clear our history */ + priv->pos_ptr = 0; + + return 0; +} + +/* + * Provides a list of fonts which can be obtained at run-time in U-Boot. These + * are compiled in by the Makefile. + * + * At present there is no mechanism to select a particular font - the first + * one found is the one that is used. But the build system and the code here + * supports multiple fonts, which may be useful for certain firmware screens. + */ +struct font_info { + char *name; + u8 *begin; + u8 *end; +}; + +#define FONT_DECL(_name) \ + extern u8 __ttf_ ## _name ## _begin[]; \ + extern u8 __ttf_ ## _name ## _end[]; + +#define FONT_ENTRY(_name) { \ + .name = #_name, \ + .begin = __ttf_ ## _name ## _begin, \ + .end = __ttf_ ## _name ## _end, \ + } + +static struct font_info font_table[] = { + {} /* sentinel */ +}; + +#define FONT_BEGIN(name) __ttf_ ## name ## _begin +#define FONT_END(name) __ttf_ ## name ## _end +#define FONT_IS_VALID(name) (abs(FONT_END(name) - FONT_BEGIN) > 4) + +/** + * console_truetype_find_font() - Find a suitable font + * + * This searched for the first available font. + * + * @return pointer to the font, or NULL if none is found + */ +static u8 *console_truetype_find_font(void) +{ + struct font_info *tab; + + for (tab = font_table; tab->begin; tab++) { + if (abs(tab->begin - tab->end) > 4) { + debug("%s: Font '%s', at %p, size %lx\n", __func__, + tab->name, tab->begin, + (ulong)(tab->end - tab->begin)); + return tab->begin; + } + } + + return NULL; +} + +static int console_truetype_probe(struct udevice *dev) +{ + struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev); + struct console_tt_priv *priv = dev_get_priv(dev); + struct udevice *vid_dev = dev->parent; + struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev); + stbtt_fontinfo *font = &priv->font; + int ascent; + + debug("%s: start\n", __func__); + if (vid_priv->font_size) + priv->font_size = vid_priv->font_size; + else + priv->font_size = CONFIG_CONSOLE_TRUETYPE_SIZE; + priv->font_data = console_truetype_find_font(); + if (!priv->font_data) { + debug("%s: Could not find any fonts\n", __func__); + return -EBFONT; + } + + vc_priv->x_charsize = priv->font_size; + vc_priv->y_charsize = priv->font_size; + vc_priv->xstart_frac = VID_TO_POS(2); + vc_priv->cols = vid_priv->xsize / priv->font_size; + vc_priv->rows = vid_priv->ysize / priv->font_size; + vc_priv->tab_width_frac = VID_TO_POS(priv->font_size) * 8 / 2; + + if (!stbtt_InitFont(font, priv->font_data, 0)) { + debug("%s: Font init failed\n", __func__); + return -EPERM; + } + + /* Pre-calculate some things we will need regularly */ + priv->scale = stbtt_ScaleForPixelHeight(font, priv->font_size); + stbtt_GetFontVMetrics(font, &ascent, 0, 0); + priv->baseline = (int)(ascent * priv->scale); + debug("%s: ready\n", __func__); + + return 0; +} + +struct vidconsole_ops console_truetype_ops = { + .putc_xy = console_truetype_putc_xy, + .move_rows = console_truetype_move_rows, + .set_row = console_truetype_set_row, + .backspace = console_truetype_backspace, + .entry_start = console_truetype_entry_start, +}; + +U_BOOT_DRIVER(vidconsole_truetype) = { + .name = "vidconsole_tt", + .id = UCLASS_VIDEO_CONSOLE, + .ops = &console_truetype_ops, + .probe = console_truetype_probe, + .priv_auto_alloc_size = sizeof(struct console_tt_priv), +}; diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig new file mode 100644 index 0000000..ad16ce6 --- /dev/null +++ b/drivers/video/fonts/Kconfig @@ -0,0 +1,7 @@ +# +# Video fonts +# + +menu "TrueType Fonts" + +endmenu diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile new file mode 100644 index 0000000..6ab4647 --- /dev/null +++ b/drivers/video/fonts/Makefile @@ -0,0 +1,6 @@ +# +# (C) Copyright 2000-2007 +# Wolfgang Denk, DENX Software Engineering, wd at denx.de. +# +# SPDX-License-Identifier: GPL-2.0+ +# diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 63d0d9d..13e8a88 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -194,11 +194,18 @@ static int video_post_probe(struct udevice *dev) * it might be useful to support only bitmap drawing on the device * for boards that don't need to display text. */ - snprintf(name, sizeof(name), "%s.vidconsole", dev->name); + if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) { + snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name); + strcpy(drv, "vidconsole_tt"); + } else { + snprintf(name, sizeof(name), "%s.vidconsole%d", dev->name, + priv->rot); + snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot); + } + str = strdup(name); if (!str) return -ENOMEM; - snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot); ret = device_bind_driver(dev, drv, str, &cons); if (ret) { debug("%s: Cannot bind console driver\n", __func__); -- 1.7.9.5 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 10/19] video: Add the Nimbus sans font 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (8 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 09/19] video: Add a console driver that uses TrueType fonts Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 11/19] video: Add the AnkaCoder mono-spaced font Simon Glass ` (9 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This provides a good-looking font for user prompts. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/console_truetype.c | 5 +++++ drivers/video/fonts/Kconfig | 13 +++++++++++++ drivers/video/fonts/Makefile | 2 ++ drivers/video/fonts/nimbus_sans_l_regular.ttf | Bin 0 -> 61660 bytes 4 files changed, 20 insertions(+) create mode 100644 drivers/video/fonts/nimbus_sans_l_regular.ttf diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index b770ad4..46c5205 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -444,7 +444,12 @@ struct font_info { .end = __ttf_ ## _name ## _end, \ } +FONT_DECL(nimbus_sans_l_regular); + static struct font_info font_table[] = { +#ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS + FONT_ENTRY(nimbus_sans_l_regular), +#endif {} /* sentinel */ }; diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig index ad16ce6..ded3e5e 100644 --- a/drivers/video/fonts/Kconfig +++ b/drivers/video/fonts/Kconfig @@ -4,4 +4,17 @@ menu "TrueType Fonts" +config CONSOLE_TRUETYPE_NIMBUS + bool "Nimbus Sans Regular" + depends on CONSOLE_TRUETYPE + help + Nimbus Sans L is a version of Nimbus Sans using Adobe font sources. + It was designed in 1987. A subset of Nimbus Sans L were released + under the GPL. Although the characters are not exactly the same, + Nimbus Sans L has metrics almost identical to Helvetica and Arial. + (From Wikipedia, the free encyclopedia) + From: https://fontlibrary.org/en/font/nimbus-sans-l + License: GNU GPL v3 + http://www.gnu.org/copyleft/gpl.html + endmenu diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile index 6ab4647..68f4c3b 100644 --- a/drivers/video/fonts/Makefile +++ b/drivers/video/fonts/Makefile @@ -4,3 +4,5 @@ # # SPDX-License-Identifier: GPL-2.0+ # + +obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o diff --git a/drivers/video/fonts/nimbus_sans_l_regular.ttf b/drivers/video/fonts/nimbus_sans_l_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3bd694d8ee55d8eaf468abad40bf072896a757d1 GIT binary patch literal 61660 zcmdSC2Ygh=@dv*9P7O(@OQ+sWcc<P?cc<QU>ID^$1QJaWqKINJ%@k7%F1W-7j2rH; zgNg09_u$@3Z0vxI8!?IPI2b#Q<AR?4-`V%>bSlOqzu)iw`Tqs)-j&_i+1Z)d+1c57 zj5Ef3+4)SvmfLLYjkBIePGjuUA$%KIU0G!dc-(IjV;{BPb6<5!bK8M{CmI<0csXPK zP1S83<r(^<m+|`p`2C6Iwv6ocadkH`#@FKW^!Y<`m%sAw1J5z$c^hMHuPvCna{0&z zT4Ep7;CGJ&OV$mzw>@?ZW5LH5d$47&e{P>fpZGR at JB`aSh%ej+Tz&_5Iry9~IJ9c5 z+suWi%iluv!%LRUpZo2i*ka%Ud|Z|c&0V`(+Qb_1dnd}rE}c8n|M1A}9~pZ!l`+l5 z%a^TO6*IK)MaJHE1f9N=QQx`mtfJ<tADHc1_%G(A`5fP{x4OUl$$lScl|R$Snn&^3 zgGux){!mZzsQel0cF7%S9hsq#1tjG+@hX|=8@$#rcjXIFi`pv{p8<nE%CBIqD0Q*) zIKJDZ-1)~Wi=!Qhxq?pgcRrKuX<@N-$|uHJ2CDi1CH7}Y6FZX2x;2mT$XLd%R(|Ip z>Yb>7_qaCj`7B3DWj?s#H0xNDbRElKAF@*3$ZByJ_ysIcs$tnEI|HB7#XV%?2b532 zHGnG~S1hhbTsmCzeL5~ZE(6Lhr3*092#&Z?S)=CPsCOOn$JH#o%`$Oy;eEF>A6R>` zRD5njy*~b(S|*jXXv$bUzHh+q29%vC?r*amyw60vVEopB_tBbrSqScQO~v<-fK$qW zGwxdcE?}pQe91myWxy{Pmj!La;2w@E5oH2!kHTd`nR52g$VvR3kN1^m-$vi#O2B2o z6%LwI;rDpdBN~?By(jQ+!`&ZO5Uv>B#8S8m`qBirHLM<W)}buXJ)GS!(u%v6dJ*j^ z*dJLY%4FjGJltpE>Hr*Xf(hJz#`gxg(B=#&3}t&+Ht?e{%fOXI^}wwwaZN%0XXCpN zT(fWm;c~?liOUz)N?g&nnsFI<<H(1&=iv&}d_MAT)c4}&h;d|~bu>Pw at FA86KB4~h zqD=<)0ibsx?wdew8qZ(8lyRo99vyEQ^GPn^0kkm%S2Zq<@f*Q?7Wzi?Zy`RgU&IHK zUHH96T*M#t%g!gnE90+g6@DR}5f}A^$`c;}N3<V=s~qo}?Kn`o)CS?}fT!>k at z!Kl zir at +23*x);6uv{*#9z3g(541g8EEh`t{*}75}v>+QQppvcAg}@9CKkT75*e1r7K6_ zQ<NkAA9LY%g<n<N#Ak)CsUL}ug;V&BWT6C~MVo-{fPwd!)CPWwLYXi@^S4<L=n^Dd z%Vy$xlCcCSMaYiCXR{()fw=snUUoHPgLKVo(h=ab5^?WmMH=Pu1N>Ferz8KQi+_PS zFGAK5pnIa3JIa!Np(_#J`|~fs^N=~TR|Y&3JwxL*pEYYpXVH6Hnh~@^ym17d>7ws% zhAwQt{Z0H%<BPlap7 at sd;bIo9xs#RRyAbIFe!CFm3URgK+QO!321m=gc(69<DWM;U zh6$1<v+=t@yKWYZ&tl9qRp51^n-$;V;xg`OxEA3`$3^w%bE%@M_$gLMW23nO^dp^6 zj^8|RJ%H;aTpzJK=|0>O0gHI-d%TaqxRab62ma8p7!x5A;Hy4d9t6iFm$hlW2fkkk zo+rKBKz+r%P2g&S&H(NN6IVROxSV*Cbfjh#vuVynJ5&Zdg7Q=@i0;yLBi{oT542A_ zV4?N}4dyc!+&#qSFHs+30a_5A3LjxC1TE-JFs~c=iGMdDYqqm#(p~7If|m`v(k7i| zHWxkc&qsUc8_LHx?ie$o8Q_u}o1N|<DCe)a8~r_o-!YDYj^pl{8(D_*8cVj{iDpi? zD!8e*IA9RZk^HN7;OIqq6Q8^BeIjtnaK1aiv-e%O(-;s9)w|$F@Wc4Kqpuh*LHF@@ zwNIMW=;K!y8{$FYE8;`&mY~mgx~aGd-3FPq-_aKMVdN8yznF(Ypp(AG^><vSaeXU2 z0iIe7-T-aE8=v4#{LsR83!POuzAvDO(pMU%5R4J&Ws+O<j`<}7@7-wJsBb6}pg9G) ze8&v1Xvh|#D^jtvT%N%vvZf-Li_33uZ-7PQ%DmYoc91>5H}fl{FzH3<L(Oi at 6E3q| zo^yH0<u#Xox%?O#6dN8J9UC8Oh|P at 6kFAV77#9*3A1}oR#D~X6$IprH*T3@pOFv5^ z!?05si)A;nyV*m47An0U9TCu&%PyA}U0!v0%jJjIz}V2($k<o`Ezb#>zY47n(4PC5 ztIz<DG5K9_W5YKMZ((dWVAyZiXV`0ait;UEpLBfE@JY@mai6UDH0qNnALqf=j{GF# zqjx`g=Yxe_C9X?FzZ<v*kLJmknIDs$fabxU1S{MAr&+66ui2#8YX5A%gD)ojX_yNr z=LS2~gH2(Jm=~xxmHA*z;D?b7V1X=%1v6MxER==8mWcq(qgXVH0d?Y_A9O5%=`lt| z%pFNAnWeB)md4Uq2Fqkwuvtv7HO<VztSp!1VO}X<g{+7bvl5J08Emgjtddp14y$H0 ztQPiiJ;t(;HL+&a!ltoS*2dad2b<1juuj&+y4g%Ni}k=Rna$>~xojSr&-&OxHoyzn zAiIEPu}j$!p2xG<R&L~}T+a<`Gdq{9VmmRL{)VmR={${Ru#N0mb`86Z-N5#-{cIPz ziT#${&hB8hVZ49O?qLUc6nl_8!X9Rivd6#yN$hg=9D9~M&z@l~@fcX!$Jht3p+9Et zvrpKS>@)T``y2a${gs_!U$bx6Ngl_(XFswZ*iY<d_HT^U9`3?5+?7ihyLj%+UgJLO zdT7@h*{9&L<C2FoMe>xq*e&cY>{fOH;~UG~<i3)(G?o31{hb|PU$Hycmy(a<%kE<T zko+Wn_Gcb01xSHXkQ6M1NLuz5_XDSfu|M$$b}ze13YQ|JNGXcF&HXWd{(*hV-r)i4 zKK4&3Mv7&ZvEA%`_8mLS1K9)YUs9YDFX`C3JV;8A^z3o0!LDF?*%NG-y~l&ulRT6? z#bn7K8QCEo2K#k{z0Rkyr#Y7rr6egCGyV}CBBespyujVqi`<<Z<yt9S%8)XpEcOO_ znNMM_axeA at _mr}EB*gc8b}`$=wzD1VBDRHH$j|4Md<);oxAE=#0=|Rq<QMXb_{IDZ zzKdVVFXP3$gqQL%Ud}6c6}RzfUc+m79k1sNypcEYX5PZ5 at mAi(+j$3{&S&sW-o?B5 zOg at YE@LoQf&*5|VJU*ZI at d3Vo5Aub45ns%g@FBjGFXPMk3Vse>$yf1n`D(s~ujT9b zdVU_?z&G+u&?=Ynetrf24Zo6K#dq^Pd at sM6@8j3-Yx#BjdVT|2!7hPi at 5$D%#cUm0 z%g$jd*=nB86QS2UN%YWX{2%kj9ji)d%=Z&NC^i58Ew4<-+f9(Q2iZfAr4JxSA3}yc zh7^4U3Hn^f(ASV6$Pgsx2guM*?BDEX$c_t14?Bk&h0G7K)sX9}c{b1DDUk6CxEXSM zxs(H`xAI&z08$t5e3gusLiQQtbPptd9`u8SEfBvfVe8p>cy}!{!DE8oX$)!fFpfN# zn;@xguzs#%Lu at Hq#+GA5S7ChDvJKE4cR+{S2F)Qf1~dn03kI!0dg3{D75IA#+p2Q- zPIe)<{9^p>q5g}8RvdtCVc_05;J01;06)YJ^W*#^KMh&5NQ2UC(jn=TCRtMseg2T< zCCzcocP<_-kuFUxce}jeD!FF5Hn|SCZg9QV^(EK$T)%e<aEo^<bZd9p;I`lG4R;rJ zn|r7GTK9|Hf9w97`}^+SdboH5cqDjOJSsgpJ#O=O!{hrY@l)ETteW!3l=r8c^z`;L zc;<Kxc<%5#==q)(^NRGE?zPrypVtAe7rZ|4cJYq%Zt(8*zR>$V at 0Yw!Pi>gmJ$2L6 z`+WR-8hmE?Z1UOXbEnT^K7aM~@s0G&_HFT<<-6YZGT)ngpYi?7kNJiAZSuR<?<v36 z{66xN{k{DY{4@Q_{RjMa`QPOKl>b})9|gDs)CKef><>5+=n^;>xH|B%z?Xvjf*OL( z3ECa>P|&epkKn at KRl&Cee;Z;5=?&Qtav<cTkk7TA+Ei_wcD42i?P2Zb+S8#yp^2fz zp|e8Qhh880S{MsU51SQsS=bxlQh0IrrtlXc0wPvLToLg^#PLYa$ehS?B5#U(D)LyQ z92FT=9Mv0je$@V``=Sm<$<dL~<<ZNdABg@wCNicy=0MEp*rwRq;-t8NxHsYx<2&Oo zjz6sP(beg;>K at a5pOBrfDB(W6hdx%Hqp#7=(XY~9rhiC(+z at GKFswD)W;kpNFjg7| zjK4L0nV6Y4CvjKeeThesq@>cMB}sde9!PpG>AU3k<ksZdl8+^yN(o8HPMMRkE9HTd z_frE>b5pNO{VdHpZC2X$w1a7Hq_gzm^uhE4>4(x!W*9Q+GCDKP$=H?gK*n2{L7A1A zdovGYew$UAH7o0!tShtb%sQ0ye%6=SvDx#oS7l$H{Yds_rbtt&smU~8y4ZA|>AM{7 zoSdAdoJBd?bFR;MF6T(jPv%5(zPZV~*ZhF_HS^~dW{I?vS{7M$Sq at pgv&LGNSPxh~ zxBieDp4*Unaqhv~&vJjti_go?o1S-0-i3L0=N-!XGCv?cA>WpNMgD>OL;3ILe_P;F zkXg`Ku)g581 at 9NS6&ea#3I_`J6~15ic at Zm$Et*xdtLR|SYeioc`xP6CdyB6uzO(pn ziBCy<Nq$LZ$z>%Elzdcjx-`7BskE>3iqfY_zbcC@%Py-dn^m@|?Ao#y%1)JslrJiO zp#1IfuPXd1GAmju&Z)Sf;y}d<6`xf6R2fiNT)DLJfyy7M@~c)??W($^>XE89s=l(h z*$lQy+Z@{l+x50bY{zV;t3#_ZtG8CaR+C+GWz9`B at 7Ma(=GXSs?x?-3_PN?m>U`>! z)a|bOvfi(LRsH_@;|-w=bq$v_yw=DXGaENFKGFDnQ+!itQ*YDOrrVm{Yj$h4G|y?i zzWMDIzm}Soc`dtI4zwI;`C(e(w3cbBrX8I2*J<Ck=C{sjUD|qn>tn57wZ*ozw5 at M@ zs!eWhXy4KPR{M7yu^qOK<sExFp6EE%ae8|E^oHrHr|+A7@AQ|ZADiJhBX!2~8JEp? zuG6oxy>n^jzRtJ0Ji7|JmUr#zI at EQ%+pjyhd!YNyv2#rpj6`THxHqMvu=rqc2Ku|} zjQ)YaI`dm0#*nsl>8O17#~<_7;mZdHQ8tIa#j~X6QPvA(O~EK^6|Xrx5yps{=tX*d z*^V8{a1m^uk*}m^=}}mSG0?s4reK}8bXJ`^F0)BoCU=ungCF>bgq}q)3(qw+7&hSQ zKPP6%x`c*=jk?SYu^XjFpR2sP@*Vu)_S|#N$v at l;yEkrRtK_DUU~9s!CNPTT!Mb#A z3=YwT1P8gfySbw)>D+3<XRW`%XfWqkto~*n9vr0gw^%La9K$~gW-e&vrP=+_9a$+^ z^#zNDT$|kkbk#j^)%|mF3-2k4NzT(q7Wai6R-5!=jDO~$g1He<ZVMK;uN`cP$ehCc zB128HI~I7$FQ4)#&g$MQ|L7i_SeRu2m7+$z(#!<CrodhfgH at af9J2|rctegw8}Akz z6k^IIB=jb(<Kh?H_@7>^ncugozt5IbSeTqtSiqN+%jcH!_iN_&S5@~pzLtL9S6kKB zzpLL?K&6sW3VMf)e0pCEec5L#NJ=g!NK7dJ&2mP*kTwFRDA?xN7!^~HyH;lesn9Y} zC!OO}pyIBz7>n`Apc9w|8i{N`nE#_CfBjH#R%C9&tQA!~8S#CW%_?lVAnxsg)GUKB zBkaDLDlLtcTHe&#b#+swf5K3A<xERKm9BhdXO*QP_13%_^*QnS%nV~hiu~J`QmT^8 z_2A1={tdeUd`Mhq3J!8N7jvVow5KOr%QK~;{pWcYT#{2!u687q>rf8XQ=kbH)andU zj^!)sMHgA?J3FPLAO85m$Me|8SLA^=pe$3$YIUHq(G<;t+<Mfqhec5;Nj6v_yzetm z)`#G_Tea?5ccZ(}YP4!^Dsf#DxWr~EF3GN161Ye at y4+9@-IdVOlh74ifTbvB+3<9P z at b^h7z<*#l at V40m3;a#nn{V2<7rnmuCYnLn86!jN5LPC@AF>mqLqChSZpOley0F5* zwc8BbkP|@V(%C6k#JLzTf@$WFCwXoSZITcSy#5g|@S7FC{c#X~_<rPvk*|0p`hf2P zO&Z+^>p!g0(cwUpmRQQj7yM_8`&95E<Jm$!#E at GpA+i2`#D_d^VSd$`73Zw3D)?Uh zY*(F4dcLXmrF$NI{QhTqnwsQ~<zrXSo&pz^6!EnOK2Qg&CVw5*+%QnA&F|ztmP}1= z9WJ19@pu_QxoF1p!Et1(6SM2$eYm?0(q|G__;P&YKgo%1t;=T3X(>!Bi+AbN=n5~K zUtGSouPoPn#*A|}m*iMG@*4D|m3@|3xrMW~w^|IP*ug;?vFO)NXd at AQHY*~nHeezy zc85j?0&g1)H26Z=bnYLv$K_SzCzV9=PS>OTE9aNyZX4Qd4R`Hyt*NPMx=PQh>bZ}$ zHn%K3zwpH1%q7N<=F0xH3o@G;>*_1Ekku_=&C9StdmiIB6}^iybCW;x8%D!l!>i?Y zd3o8wg`FQA=;2SvIn4)t<QDlAfv*no#}%6J;5c^>ffNEpf-qFo0R+xn7s!vvM<WYr z1~y-~xNsmk!ml&toSL@w>ZJUbX=ds9884)4Te$qPw8XgPHJuI3Go$nz%X=}-0)9BO zB1R{j6EH_BqyeqPphJkelltM`c6t at 3H%#OC3umw2*1x8pur#(Wf7;@g%352Ci(2X= zZ&U6iTbnn{AKcm5ednWn8J2lJw3S!RoKaETMq`lfpapo<O~T*?SmVr+$J$GxdJ{Kn zmbXaH*WDz)dEg%I`QQw2r(UH&AUFf##ub$a%tn=uVH7<Wu&SQqJJ(<E_66%K$!nfi zle}_yMMbW)GH-k9w6cmees2Dnx$DoHyE;F<G^=gVqPFbPPm4_1MTOZpUo~!MYAh)R zZ4BU*VbGp*F{DrMgB8jc8c*RxcOQ_;5GdLfirK6Zru-1mrLeFprXO^96*M`&t at V}( zRFTp;L6Y{es+odEmg4n2$s0Y!o*zjn&8+W{v<?pP+el_5wiNo{&!P-z9{jEu)e3*c za+LIf<UKej&$25A&eBHyE%c2Wpqll;Q+aS2|3Y5PFMgrw&E}hLYbLnaY#GYTRoVup znO$*7A#x=D4A)$n?V!y@W0wtB5zt+L#e-eVY1}MD$Xb3ez<JrB5j@&G9ERUJ3O*jD z*9kTf6R0lU-7Q#06r^DU1*0UWtGe#Use!5Ww*1y;|Bhu#=B`T3HAQy6XwBC92PRgO znMyEDE7uHmW;dBKQsXXDXc0ehj^<&sVa8gEag%0rf>fp;Q?^wLQ4w6Ca4`g&gq!3? z8APMXH~86VF-X_6b$dq#l at 9*t^8J55a%1y3k;~87 at b<d)y7K$F?)iCo!_qlE<qfu; z>-*ac=a<aOPs&dXjOUVt|4aT{=Nk}yS?9)wzP#b`XJadp7e6vI@5ze7s$=`k>+5Q) zS=cmm=M3%E_N=P1$VK`jD+U at g^2f`i(7*1a-Su(qI;<9TUJ`#_{#)aG?y{D%!T#lI zKllJj*~pW4gG`&`7$F+4WvJ(?aHh#WPH0A<yU;%k`fbTE8dA7Fy~8|)x6M3pfi=J1 zR+5`H@YMq~@rjA?H4pryEH*wqw(Kv`QBzLq@{EY~^3H-Heq(sd>X`81S9J*%Yl04A zo(cK89dw2c<2ooFJ9VKxttKfB$`Zqi0hRX7_BCY9UUc61Lj_$SAzpLh<bPe-x at e$v z&q@C8dR_W0yDqvpD=oQN{<vf4(TA6T=_PpNm~@?>fgfYklMp{Qol!A!Qs8?DyJY{3 z7e8 at u$@1k(4oXL#kq<uGdd0<;Tmd?8`0|+4kGf=C2FC@*`QyUBhxy&Z|KdN&eQ^0S z{M0HPZT+IvQN9A@h2^TVm+pZqtb%_B<h2(C>{RsmdGwj)B!3t at 3dc|{L$r#PgmI${ z0iu|Wp<fHMgTLARSXD{K1siV;ob9`!ZPl)%gvwQYTR!4j<n!jPI(+B6Tg|rh%WTtb z-BG#TShs0j5Rl^VPBLk`(gz;ruk!~9aOotYd~t6NU(?g0;3YrL3x<s---n(&gYq<I zqxdtu#2olcu>7H~4&Xf*1SWqmOB8Xa2Ve;{;Eg at YX7>)w&NUZCX;JIL*-HnzN;66; z)8dIds84)#rGg7I3((-EDKIn-0*S?I<v;V(rSb?H8G)65IbR|FS#E*<p6a!rUNGt* z79o(R>i>H_C>NlKbs#=|ugC8{;+;2RaXJlkG6MMN{Q2Q|Z+31ua(GLpG=CVz-0%g` zdFaf1f{7RrX$R_HtU!PK_cy@@xnrTcv$LOX6rFA!{sZm at rFIl0T`m0;<K+!cGwc<i zTWHYy$Z+-x5!#twaMRNj1v7W<f99DRR$X@a=a*k5oj81N_l?<&@9^pGwB9a%d;4wM zFa9n1B*9C}q=jhT3;t_CX>g{{9>kkD5U59bc-oS4OY+-JJX29$U-1k#%dbjDmo1o^ zm(yCgwo<|Ad4ZD;Nsg<#U?M-_AOJFI$+jWXFgQl(!;FQ~7Xq*SH$1ywe^)c%wyLnO zj_1!X5pHiEIJja<ZPz6`ZYLxoi)p+hc(Ivu2XG7}E+uXdIJ!X(Xz at Q-XE0k~J}JUP zI^eM$-m-Y5rKrHjCBN_$!92hC)Teb-hS=IqdA|Gt%;w(W*68rM?%Lb{ZDw^@MM-O6 zS$Qmpz0d(epl=F3IQTyXPG(Z#AT*gHZX^%Tq~@FquISegslAi;&Zz6Mls{ZIEk$3u z{dWz?@TnS2=XRxOqjo%a`6X*N?UFRrjg{8q%z&(*ITaZ*qjCrHddo73V*IU9ZMsbT z1@l*~5qt(qRGNnVVs<2k(3#_c`MjP}QlYeSc%8JfwH0`FfDbM}87CPqgOh`VsG6n3 zo@>tEx~Io%Zt3g;AG4)fw=9#G%$k!7?RC^o*q)+3=C?TLV=yYeuuuM}@4Z*%gW0Z@ z7NQI|Omj2Z5d7 at Lp>R<Q|E13d?!0y2@xhC3>HF=DL21VD?@;Alsf8|rje62Ws0V!o zxk5!QYQjq`t$gvN6<qpu#p6#b<E!P1d8YgpU(M&s$2kz9y099hJ*Z1IG>MMDY+H}~ zx0Ua`wTwqoLH at XW1D}twWNTKVEXhtVhd`P+=JG1Jfj>0-E`LZKkdC$wzuwv^nMv at m zmKsTAKcPJ6D&!ur|I>m6k4Z;sPXDDw^Lr}eyjF-nk2C&Z{RcW1DE$NVwW$9mrM}h- z!N#RU{lhhysG8GIaMVUB{OrGjEd{y;ngXfw&(~g8wSHZd^t`;Cr^<)X#+M^4Qu)YM z(rv=}C?CFaQ+Bpezl?v#_M<-OZdel*Yp|Qvw7(}aEsJOJ5Bm!$T@3DRkcniBzgCKX zHEY708cZrcRo*nk)8a`iP}K!RNU(p9Dcfi!J4Dr!l2#XznB>~t+uSoNEO$nhp(r=M z(6wC?XI(S5FKx<2as3qqmaLkbxU7;X`ozZe>H&kNmzP_3c!(h}Ewe^nRBMx8OIqFQ z>*JZ0l$e?!#!q4v;N=Ax`C!fl1^g8?K!Ovjg8^w@*<0H377ni6Fu1s&{zQ38OL=8S z8`$)#>AN>?y`j5;zrO5|Wy`vHmn$@hfxiC`eFel(nS=zIgjuRusr=9F9;G>zjZIaH zgWElp_b%SJdC|(eC_c+tuxwt>qD^xa at 7cWd`lb}nE*$W_29Cb)zz~<g@!>F({57K% zwt;_L7vF4IwEnz-CFZLt+uN&vD!9Q|m6dYM1skuP#(m}Qmd~8EZ0XG2Wq_NAzH{Ii z1Zxm>j+i!8B!xQEhPcKlD?K-f%bR#aO51?0-EVdG((|_s)vX8%me1z~-d<GF`ejXG z<-+N6_HgOCx>9SZ<Wkl}i(Bv)$-9L8`5@fEzoZ>Z3I&1<y7(yUG0kYR*~;>@p at E+H zl6R$?%U`PO$}MrN;*bIfz83 at k0yxHD?~!445~e9J5BY*<{w%_}lhoW;yOGf5*Efbm zhs`dSwbZ@CEj*!Mnl4&bpVe7heqP_a;;So~nkvg1>LqWvu4hR?OhS8IVo+pO(m+IZ zs;zO$`TJ-3EbW~=w77TPkm!dEt^XBcfPR2W#L_Dmy at 3r!R+qod)~2t|ZRl!?k4-LZ zBHs9Wrm?Xc2Fzk>Xz1J)`D65 at 9FX<`hFC|?yg+M-y>0N%c at KXldB47JA<;}4IXPlr z*Pxs?c511$AoS0S&RWF6!`62BR9}CMA-b&%^>z+vs(G|gqZgGq_F)<S9(Bp~^0%7Y z19ciW&-^d_Xz@euE%?JU!mQ(?2j$=K$d=bo41XN^wAj57c!}9+AN++5+XelWeiHNt zCc)Hi?P%xKzWWQ&B_cG&*XPVl)z{?IHn*G7O*P7k<XxAM+>)A5UoEfU3$v{)dGf>d zJGD=={Tte#8C_`u@(<eDN!zHKmfz6bmY-eP1X6WcGn>kYuo<~3T@xT5GU|IOJ`u7p z>=uSye!1U at jhmM)-?(X+t)<0QHLY2S*mL2Q-Fvvlp2nf4hK6R%r9~db$TDJpK8R5C zffl}iQH9{|tPdIsmJF^tuYYdol|mh~x4kd@wYm4&?HjLdlE0SjaMA}PzmQA;GaMRJ z=TF!v>}ocRS-*d6*D{55C#r<_Hown9+DOTC*h+-tb_*rLv9r#2nSg1hnKl>`KIm4} zm{5`E6<i-*0AkH9Fljn?SjtlQ6!j0j9MI}j)YIdGP7pXKL^T({Wr|Rd at MK^=9XMu( z at P|4z1E#`sQ%jTW-1)D%b-D$_Ra+|zDNQ}KOCOrY*SDu6MC!Z)1FJAb_h$s@()3aB zo`E41#kDhF0aBYjBVS;D`Fa{1n#Y9QD`o(*&<3Vpd3s<(n6EJ^E2p{|OkF8o6B+1Y zb5BX+yDG1^Lck~k-p8 at N29Lzq6(HFULM&i|9<SH8<TuO&law{J)#ju&mGRy3rNkmU ziEzM54)W_w^BinlF-u3ye57~R?p|ns8T at xt2ER$qq09vPA$j(q{@#7F7WWM-80_WO z$^ED^$oJ7j^{`&#M^O(l#{+dn_<S at bt<}U69-O=Fg4vJWGkepfUZ~!0UVfQ-asJd( zR3`{L@e%3>dq|_0YC^_KV0;Af?cHC5=;Bjj!Xo^`V?1O3(6s}yz9TO at Y-+umHnL6f zk>Pb9IOLIk8E`QFj at lHkI9}#e@^qdjAA|78J8SFt+FDUQ1-{fE$U0X4(8uv^QShX? zhu}4&fIb^&9ySVRCR_nL$X6epklq at u_f5~qi%(6B&&!dHmPHyLMEtm6?Ssb1vZ~@c zstTu6O)0S5QCtOl`e1Y1r0Now9DRj_(owsu5rwwh at LMcnut#WxmSYtSrf_@|NN9uM zcmPVeC_jBfYioqwFC!;EE+r){Kj$#DyH-E8*}^Jn7VXw^Z+ at CTB<5T!tsr8sb;+mU zt|NUF!--M&i{`vcWAZ$0P?Vpm#_VFviikKjG9)sTU+0%!6<*?_^NF(6`&IetKr4x@ z8!@rhL0hq>qJ=iq`fK11UXMMM_V(9#>&G9<cS|c2pGVHfh0sB(&_0a_HYG6GE4vnu zA}eaa@`rt{@h4{p&z%#{?z(73a{syK_9u7FcWrmg>)ckw-;fu0t?C|fnLBUFRcqGo z^Xl$$n?F6fP#)rYLm%nD#x^Hg0Cv<-MBUK%iRBbDl$Z+1r$W<_yKwg#G0_SUrj;?) z#`uAn##_GhLSIuwL5VKI8k(4roL86L7Z+#F&8*4|T{<m3@S^_G{)h<P>9W1F(Kph? zo3FK|L>gNwjlO=-<+(a9eR;w3bVH&wFDq{5c{v&BGp@DeM<!>vgc5%tYW^$U0(%g) zB6&@CNWNT{FFh~T$dC`jqC78Fg7C4L96olSDnwGlzdmr_K<B}O2d{p;<I(#&Ugq;m zji!6<!3%8er+B%11JS^1Bo+Hn#|5Q>b*k`KF~C-spe8s!h$8OjurKHDY^o1Tx3(k% z`iF(rg*2_$7~+>^D%GV%#|Bkvz4;q9dF6tPJkNH&75oO<U`uLdU~|AGOh`2TDI+QH zNgu^-ei@q8sU|9js3=U~*8$CGKgy&oWU#_g+*+jIiKRKMCo^E9VAS7gH>Vm*VX;>h zWe!Da7q^u326%M at L|IeJ)&p57+1Av>*Uy!|`fY9P&BoYp$$N at _hBZ?k5+B at ath204 ziqDCynNyS#XSRf<W*V{$=f>A%WmiX;OjUE{H3!LmI?q^AWEu)jD}*ang6Hi5*iA-Q zj0V_tVvS4tAV7=uLWn at v8xafMQ6rb1$Tueznv+bqnX$2!Tx+U1Kd~Us8Xk2+YH>+w zN=flk7ISo%DK9IfATP~owuVQV^Q}oa*%>bvPcJG;PA$gRWa0$JWf)^uF_&X!F|#)! zQQ1xuG{vHTr-=0e&GwEJgVyoxQf2KhehD#XUrL~F^VlN(B4pi-R$ss)*j*c}-sbIF zw8)I>FJ0@~@V8F60k4^efcvMYW3MrDv>1Nx!0%rxzXt}po2^Fk*Vist!i!V at z+FT0 z0)If*vZ1t=qq%H?JsYQ*b*G)oTJ8%}#@n_Ii%<9)`Q0j9lIn?2_4tZ&XOJ!v0KRUw z*+zb5r=%?Vo)cD>S;McWn at +Ts)-3iFXs&X!ggq|)Oy|>2ck=IBhwqX)T4|SSv at Ax7 z$~xp#owZTt8LFk!lV5Lr>#bI9QtQs(>4-_Yi}V2KWPWD at ziZ=0;t#a%37LXrtG17> zkpyA7kv#KCTT2@=Tere{R(kp&P2K5-_yaD9m8<(NA$+pgF~k79M{`|(@FYR at a?Ts9 ztuy7+^7rb@*|oKp6=~gkK4Sj{X-!UhbM9t~QCHT}o1@7bqzy>19?cjzpQobC6tXCk zQnXV!`YuD+v%E>UiHg{{wBH3UXMvZ~9lR`B8Ve(93=HXcb^CJ^h{DUy+Id;!JXeJQ zPZI;qb>c!hHxn<e#LKy2@6es>4q8dXsn-XC{6YG!{sC2Z5T=ZLsreK-lWb(#H%Uj# zW(CH^K}U5XTR7w<?qdfjo;v-Q;_8?COJ;Ny6?b+P>CKipvzaG~t?jL&-<s>-<t!ij zSvt~LTGZLKp|jAeH<;<N4SVtw$Cs#r?+V9#roKpQ_J|QtlHQQPSPVDOoh)BrR$8Em zNhQ;y3#Wo)3g&^`kpTtu)#?t$JnUfPM0WEkY+-24<90Ao;>8XI at lXg{Lw}P#6a0&f z^__DH_eh`Z-i`J-&at%cH1#)=yY7h<dkXOz;Rd at -YKFt<1lf{-CRB9St(rZjWLk5n zbYkzEIZ7E<sh^ifuPe46uoaWGFjHtA=dSs_b-|@NAFXG;M_QaV!x$PSy*?N=Ri9-E zBy$nxTh@T@o+R8jIg!%klhTv44TRr(r4C*QT>@PdDE1+(Ceea#fk%5{rl!E_(&@=T z(y{XR*qwP4bG=gPV;>+sUX{}i2Bp_v&`5|Y at 8gNmtN32=J_+vHF~~g!rB at f=kH(={ zs$e71x5~Op;UqzMB_u*{(j9tJPEAfuLO^(ULU@?;tx2C?%1O}Wq=x(BEB`=pkuc*( zG9qso{H3gbJ8g1Wi5!?d$)vg6pq=V#NGi|EXbPT^AES*)_x1D6m2|Nw+3|@kQQ?7l z4<IUyd?+>XSz>GmZ;Xx}R}n^t`-oxMyLYdkQ{IRPQL8v$NNtjn3DyIxNrWc4)@C)< z=)*H3;}S|tF{v>Y&os}ep?L`+=#aBOjB&O)#&F}p)PGwY<7WF9kNlgR(tI(hgEe2A z_EB}PMDnV62s+T0bdT6yqU{m&Ul_c2{ILAbjs=(}m&%v&Ir2^X0y(<x;`iPY`yfBH z@<cvcz8T14bo3mj#-zXET>>Jr1wRPyn%%i3#DIf at FoZGd!?qsvtC2-qD*v6w`}t*f zWaVb&BGf%HxFSBvwLr6Jz1F4Or`Q;mr?<cgGtEwQOG(K|Ee@OND*5<NO%07*(tE$Q z+T`Zx8Wx%XlLO<3KST^4rCB);lQQ+lA5*En>GyZt#m|!yTUvVf-gOP@|J}+%^BQyc zxxhLa_Uz}<KS7BIM4aPjl$Z(Z)(H)WyOj)i$TyCY33iB{*6{2N7xm0Y>}r4ZhAZYa zF68Ie&Rcak|C7A2Zt<$i1G^SodElLCvlHen+JE4k?)Inot);hvbCcznb+_+02Yj1? zc%DCE{KCX4n9ww<BM+Sc%gC$@g{wc>b$9b4E|eDW4!Nn~+-)s07U&19>x%jB<$HOJ zJhNa!zr|#-^ljwT|7xx4oz?fq8~ysE&W6^Lt-s;@YnvM7Pa2xg&qRz<r5GpSY2;{g za*2V*|Ho~Wb at e;2d0|XTYf4Kc@?$~R-rU{YI;}gW)S6pbV$CZha%*TkuW7`3J`=I` z@Z;zeA0xc}s*e!XuyC%TCu*#QaNi(qMDh3Ooqep<IN@`VzJ`;pE5A3~#XGmZpkY=| z<{DGN>gAr~Y(4k<jEGs5+U4QFibJqpzCW(NGT)M3l%3VW4K1Z5tv7beO*Og&=jXIn z_Zx#fUBaV6ntI@XZ8fCAIk+6I!Ug?4zMiQ`#x#TB-fHp`a>V^Jb{}*)=^<DI_o+TH zd at J~h9R^=PZiuh&v*4;L%gftJO4|7Crc#r99nZ at y&E|86CIKVIC4bOF<ta{fuHYnL z|3V71R(FlUORj+mZ%O_osT-FqC at hLC&)l@Ezbt>&$&?aXi~PmcNmaHMK2lvfw0%!; zRb*ZD%I$l~%I=i^nK?AO{9!&dXJ|Iaj`77fY(tyTh{wvbkAo_N)Z$pGvr(t5R-w5j zfoQ(HxMbUk4I5VMEQ;wb5*}8?>neBe5^k$bmBQ2KELd>W&K*}S%Fb0u%J&WR&KX+L zJJ&=6<!lpT_AH=T5(r3ZZ4t0T?je7%B}|sTJ*385z{T%I15ck)*jS!pEpN*yo!vDw zR9mrbL9o^%vUFNzN(}F5EzZg;D#{uDs}$ayZXIYHObnZ0Te`)islCEf<%t;&Cr-rP zx#mjD8lLc>VY(XKKPNkoY?N{P=-o7_IB%v)3&jMZQDNPs!g7WLojwlAwb1TkfNZ4+ z`Aje{Igf+UiAjDO3;?miI4JNy|D1ZH%<$vD)F_j|7)aZciNOx*Rsl=v1WV&QS&su4 z<2-kd0lQwz<KYe*cMG_}n{SU=Ky=*%3u9~`!}}BMjWLZZ9<@E}<NK&+Cz$LnCu#`e zaJ&UDt}koG7zAV5xniPmfVI(xNqPW$ti=f>ds~<boM&t6*w~CY_L)1wlHdr_NH4Ie zb9jzc4cVZzNV15Zu+T0rgj7`%&SnQfnT)ic7cPZ~F&6y1sftyS*%&+}FIHG3UNO0a zag_}eU6Dd@7rl!|<1VyuV=a~{k6>-q!9|F<IH|;8+>(`+f;bGYF6Jo_-=VonwR0!7 zJ{sX+gg~4j%Hv(Ke<q9ccme!vZ|5M|!3=!HND`xB?wp~iKPt>WX=_<R2sBD;iH79K z592y9y2PrqGB(KMHPyr#jWxO4E>qp;L_c(nmts*l$DCk|DRhog?1}tuQaFwX6v?KT zv!H_l=Pm&Ut5z~!6iYw@@;X6ykH~3{V3B4E8vbHkxc!Vyb>-7gGR~DrO*z^=h_<0K zo!jP$j&TNYLZz_-g*ikCJN}cv&?p3Tg7L&S5d0NEOgoILCZPc+=>*_mCkj at R3lwus zH)M4eWEH!V)PwP2YNsZkkc_5B#pz2j&2rvoNKjgl-O at IPIAVSzCFVzSE21gL!u|EA zpE0`hOd&wmJ1f>-jt7$BRW%Dei!}oe)LephV%=@u#iM!c>7J=#eIz!|?h{-k;q)Wp zp^)LxJwNc$(>)cm7!d{ECI*m%mGkzPa^Rb%dwgi!sg@%?qtmNA8nUCI9Sv$7>knFG zKHcML7ZCCTy`#v6gfo?lJ*T<KseNGjZ13zr1?ppB=+Hh^k5Vw&SI-X+?_>8+CnAQX zE67sR5U`b;;)>wKQ6h`5c at X3VH-5HJsq%DRPNGM?hT at i~+5&T>VY=vN1KQbvcBarO z0b_~(6hR~um_3T<cXr}9B8iAd5>x6PL|yV8jn-9_lD+yKCqg(N6si2Jz}O8K6h1Qw zW8y672x^)+aefrUbHIMwF+sX0 at lMc16z>#HQ7gX?^Q2J~6Q2<KbfsXTNdurpR8@u1 z6meCcqtebVwt^v?*g_Z4cd`a}DCm6Fu)%i|Veck}{RP;&9k34yykV#O5_MF=Y65X? zQ^$7F7Q)0B+wtEm+93N^Ozad4H74pzGjYf#cE@*;v7fYUK7j}9V{--`!03>BVq&wG zV}ulk#iWgek{3gx#F>EyD9+5FXoFvgG$V(|8KTXer)JL-amEv940|AqcR&YGyPz*= zp<ij&s)*<rntorMkj~V)GT|%s$`JaUBIbktAnJ|g at w1A2`+_9o*Q4Kfk<d$IrwxG~ zDdHr~e}IqOoI0Ceyg#d)|La)Yspx*izNOf}sx#QPh(n}<Rg;7Wj)v1Z!U=h^uaA%2 z2`9{5AAhfyDN^dp1=9H7K|~We0t{=5azkUCEnj!HDvf*K1wG@U2^TxX8<s5z3xUoT zv4z4`L|i>h7G%$<bW?&1PpX7bV+{q9Xq0$E=-~jNhZ(2HA at U24#vIx;@R-=cBgzau zE&x$_)L|ovI7aXu!JQN;v6yo-<L$&s<0Bd+F^~Q#ENIv<u-+Hez<A`|7H(<KN`ZF} zxRG0Rk{&BsbqvTaRq9Q^P*@OlIz9 at RQR0lpBv2<^_Kj)e@iB|OiPZRH;>wTaA!qtv z^*3pc6b(A2wT~Ujb3&)yCdDc25Lh459*m<0W(mPpB63q`V|(<be1rfwg>VW}4RjW9 zoT#hVFpfx0s_Y!i$#r7r?IE3t4Wrbx+c2YbRU5`B7?fM=Lt}?vw_y|*S50UiCdHWc z3t;5g>netx-T$y%tluF~qpNrFNRC^pb0(i0vqGOqn+%Xi`7<@%mm*(OnWTLfHzOH< zidf3`_gT`(0#xO4xM)FcIfoUfQDSBxn?!rXl2hVl^>&-cF#@-WrJB<asDfx;r)-~W zof7A~PMIg499?bYD)<Kh>j2~-8nwyM at H4%4%Zg2Wm)J^yMP5FVgS;F^$x9z7;x<&T zgV<d{jDK#VHXtH4BC9YyC55V*a<x%Y(xzl4#^uDIn&7i$&`OEjSEHZo(ff2dQlZzF zIDQX>YU9HB<tCMQ0%i+fDslb3gc;fDqtJxK{s)!ziuHZ^@AlfTp;31SkQ4i!qxSFU z=sC<@`LqLwQF7S2qp<9D)^=fMk$g^WXIY7{CKz%-*t43<w)sX7A2tT?w8zk=QZIki z#uz2R8O)3els=u&$Z>Eg at p>xoP|lkG3((8%(*30qCv at yf8P+o`JaPV!WaF2YoN|SI z$Q*io`!CNqpzs(Cm<{4i(0#<#6o+pTarAnM<rbbn+A1A4HPR%h?5SesqdPMK3-x$o zUh1~JN`QV(wr^%xjeaj}isi;+gibwwxg%De-n3T)r5C5ds|J6t68HV8i2Jr8N6>#2 z`|X&j&o&19H>5YtHV|BD5VN~R#q~24*Z&Svngh-jOIlZ-HIn@>HKDN-dj8LFB(vgd zal8a3$A5sMSU)N_o)C1*|1U>eHC4_Q&6}0E@~kl(P;6v7J&y at oOaD7uxwGs3tO+as z;Ou-lYs7oho%>N<cwOL*iTS at dRWZpT_^f*{$=dj=yV2xWE<1F at x1t}#$QAbA^<;80 z^lTaEoG~WqS@&b*M9Y=-mrYVT_5!n!3yu0RZo6W_!53w7Ud$+yoO=mvs4;{mN5!O+ z#OU+frp;~c=?Tr7k)=O3zW~QyVsls1@t2F8j=w|~iM@`X!l=X`&kCG`={M3zn6QYD zrnJmz!(<0xB&M8M`3x%u^2#{Yu&{W>oLZq3^e>!Txr40b at uydKIjJqoVG4ZObB{d} zeEZ67^20Iv6e*w3%I`^<5etnt5;}KdI@5YrZ0FF9tkZc>rI*}es*jI8SqU+*#w#Hv zF?k7DIpSoFo6~t6*H!9HPF#{2ds6Kc9>1ZP4(!-)fM<TsqRqWSB%>6sdm-#CI=cfO z4J?RXJiQ|f7{!M=*+HHoqb}7+5A<+v#mnlVp6huPXI1EwH)i@XpY5^l%l^t4pYv!= zo$R0wZ&&9}<vh`6YP-MID$SH<K2>x#IBL=chm^VSKWmR>y)(DB8gtz*v?t~@rM(lP ze+j=zRdohCbCU}dj-IsDrS>`MKWbNXwf#z0U;5>Ck0{N8ca?TuJInE;)#sTz`|9NE z(_!C(NF2!#=h2Mh*K_df=ovt=SF!d&NDIADkEppTkqz!j+iBDK_3b_ti7|$FLwHhT zNkgP(bYkT4&EmXTNH?8TbNSMiT)D8ZBH0`j8sSOHHGPby*5fMfg0pLR$!<8hhAD_- zS2=rjOq@M~9V7e?N@|I-IDIx|6Y8vvqCH>W-Z2`Y_v`1<xQ~-9fVdex6}dtu*_Zl5 zo3bw@4w*e7WK|Lo?E#KBIjpW|x91OSVqZ!WJVG)iA(oi2*T$5C9Q~n9>`STTP*6f% zYwUBu*EoJ(>JM%9eJN4;sKQ(l&5Ya+K9tclINIOWK2x|%Xa`F;TO^8ULar)0Ge>zf zfTz<=)4ukLoOGgnmr3kp;XK)AShr($8Cq4u-Z-BqBR9Cs`Bd2{YIgj|GBu{t4(|lu zVU;@>Jg#_e#(?BZ5wVG2)gVDT+}8n at y!Vsf$tP}6jBO)llI at 8J-EfK>bz#a`xmVRV zV8;%OeQ!iLPlY2uc2~^!b8~&dK|0~^+^}FPjFJNXGZnt-6BrL&SP|nPr_&fr0L()a zU4M3P!J89K`pqY&>11$)PFLWb5ctxLps*q*;tRkqe8!`^S{>qv(6x5mt-ya>z(<ti z<nSkp(HY-X%p^lSp<OoOY*(XgWR<m##V?LEjfQZ1j&E`yMnu_#n$Qpr5uQ{@oI|Wf z8zR<A<zf3tL=rQn(};V7kU5=5R0K>?&m?{ZONDmp>?f7Zcq&nF(3wvr-a^=)@pK}O z5;{(8{{-46AJ18}Z?{v=jKbv#f1DYM3)T4+bdWz&&t_7b!&%`m>Tdd#bDF1AkJA|m zGCZt$ohawu7raapi~W~a=q8;0n0RUvqix^wA9H}yY*)(T4|4J#dmMm<Dd#y~6*?O6 zB9oozbm*WlXFLBv3TWH`&p#^Kht?SiTvhi^3KdK__O$3@V3ctHY4ST8Sfp>pVD~kY z%>-B!Goj$6&LNZIrRbP3;6 at Y%8V6CCMC~;E7Br-&)5&Q#>8f}v&VL%S^c~mMUneeb zg>8iXAVal6=U*B>yIvX7rLXNuWn4o?NTEpTx!BL_zRvL{W0B1R!#DACY^2aF<4(v* z%N6;Kfi%4gT*vL#kj3MCQuerjVLvUa*gxv|+7qZtI~9)o8mltnJ6HeSKHu9RRM>4D zrTy0?w2wK~8Nknjqi1ywDK at c2rN^s+9^_+3HZ6)|Q1StYv%+ at g=p<AL7nAk)gTvAc z)duvzla7>g*}!me86VXzV?;bp-5MA#;lqzO+}5xK6hGjT!mnc$o)Ao|sskNS=}KU_ z;f&r7#F3fL+94GppFC^FO|iPqpsaD8Ji9dx?UDV;8i&WqCnsIa*{DHj<P@amZ{q9@ zolhaV8(grb?e>2wIUxQZ(1IM~V=nj}cCQ!hfIDY$5L#F4vpI4)+^CMmnA8rAjT+e6 z80TAs4NWIA(B|l<?rV3oS0<y|Q3+VWJ_Qa+UIy3>li8<Q$7aVEv(%2ycuTa<!5nrw z@yRpWiDK($yj4<;&5a56$k%qdl>uiZF9J>zjk8CPwN~Amak5HW?5yl$m^cu%>+L7S zJT<w7rR}XT8m?MCIZn5UQRSEea_}qwZ*l6x-=5fpvNk|0u&lLvhDOsG@Z-AVgcxnO zmlWz-TsJ3VG{pgNOHe^nXryPF#NDz}inGQwN#mCZebJ0HnUCNhHG7AeB?5U0=-`qQ zAr9E}mlGi*b*(~qBa($}f_0nrmtv*uNm<6^k8qqkcSMnV=QBwV$(@?<i6utUPUNH; z#)pqo``<J!)kNcH9*T^nAZ$#l>vv72Sik3z?)*=($6z>++&9=A>5R^5^{xRQNx9hX z{`H0XRv`ymqP$39*kgVQvKd6D-vXVuLhyveswRoiMn{JDMZ`p86vigUDhXqP3PQtT zu-%+xjLnW$v&Ue}v0tg=12dz?4nMAg_;eOv(u`nEJHTfXjwHXO%FVCGBMnb?$UWwF zFec>{^b%`x9M at M<3&sT^DWb&>+0c*|psT8ihhMyZ|HblOdw#!o`SQiTzl*n3 at Cr&Z z$lK(RD|pD|-&6=4^}h{oCh$Yp1opH68M+y#o=wV{a5TYO&s&3Q&*?hxOj&Jh*)vZ* zDIHzAV*bo?mF#jPcjWU;1B?)nWy3_rtm%N6da})oP9xue)3N)LB_(yXc>^0Loy^|7 zZ6}bqsj|GKS-R9bd}l%H4O=%{-E at h$K|ZzgzNJgLX2GqDHk5Vc-xRNMAf4<tT0_K{ zevuMb@sq*JZ9sw<`=b7Xxd|Q7IjQ=(xryC#Of?&hZy3xe4mBEG?KyG!(~ODQs8qj< zG<{NS<I?kYUUk)u`^t4w69*>b$nlh3fnGtJ(HU~&wB;@wSU)C5j^?(Jmg~1p%#nli z&Y)Qg&QjA}Kj}iNNloLBE;8T7xyNA}MGK3K&TWa4nv`i*XKj|6LrQ#2w)5xp##(9< zOQ!|%NUi*>#J>-H&#*0_s;IH6WKmRVKwAjU3kwM>m|fpr(UKT5yRX16$k(^v#qfk4 zWLc;gD8T+f5szdaiSyi)nSd-5lfUk!`M#EfsVT)$Wjc3_%9Lk-{3siUtj<WROtJ2; z3Py7xQ^FDDD2b8~xk7%+#Z;4Dkd<K4dTNSYt}1PvQxX?5g-_3~Z7{^dX2hl#{ZsbY za$7TE{dMp_0Ja|vq?Q9Vd4<gC(Jgl*XI0>;r`eEmA{vJkN!J<;J4@4(kd}5ziRRMM z`n>86TTOGZl$w^D85{1)kD0=Cu?aeV{bi-O1>vC$MYVO6=_x6O2&0Q6c<E|90p$hh zZZQ{+dMI)%A)}|%ct&woS7}jK*FaOQ)sUDg-Q87O+S$FKD=*hz%(W&O3GYPqycEm5 zpn1hPR4G>WK+<fOJ@{=lo?iDk@&VusA9qU1%^%9T@~Z5suEhVWEBRg7;zJfb{0306 z!RR6U2;PZUd$PV<VJ*>qg~)(wp!~bOHrpMzEf)qeY#pAUp<J8pB)`~g5%GdJQj2$# z^anQ{Re3EQ0b{YPtLWXnZDxgZi@cR@vRd)lA>YLxl=t#Qay{<=+K{vCk$>R{Jj;|g zBb+8d+d_VWg=MN_27eQocyCgYgUi>6{%Tke&X_g`UYaUmWF7oOSy9Gh(H_S!c7Efp zip(ju`~v?t*pdHk*x2+-yyPPAn5N<pDo$pM#RTM3GhP>QcSqZQ5pDZF at mhLCMe&v~ zZLKZfp7JSk-!%sgP<)3U`bqE&ctVsJPl7sIj-?a>NI&e<_!)Wl9f6R$iqIKZ`io)X z7#ulU1^f5N$a7eKu~Cb4l`?-hOg*L<`RB-&($_enOZgFm2S(_gASCUi4MMw}1}RL4 zRgQ{5qbI~dFOg0%=SZ>DQ_~XjDii#&LZ=5+`-NrAn7(AFbDR7gFZ|@wTdw^Wu`#C~ zd3f^$kMakzQ+oEN1&3{D*4wrWEZn;H<NY^%eEj<Tw>+_Z+mny)*hakP;=o6XRVYyx zQUxx~h7mh4l_=Ce3O7uO7x%Nhaj7;Y(vNcwpJrbk9lv8sy)JP}(FC-r9_3GNici)C zB$-p9J;QX#i3zbK6}s4C<I$7EYWNN5bLG4UPQ>Um5fYMH`5Zb_eCSZ at O*h>veJ<~k zckxwx)o?q{<+<`pLY{DHMZ$~;nRdg;FG^&qiSy)!KAt51_Ap1p7nUJ|!(XB-M^5;^ zNiO`au)Udh!~{Zrk+cT$DXuto;|&{c%lnYKG-Mat@Lb-oe5}E=%r;=VAaWsIFN{Kc z_mN`~)BFay06NEuW0FKN_Jn=k^akvHTJsy%(=Gg)^tf7i70G#;kn_#J3m8H^&HhS8 zZYUb9l9W5QqJo>$RN9l2w`bi}%8Sg~RAa3p`8Mgt>ClgBL~fioQcQ{b2b<i)*T^4# zK>hU}xlwvZ`a$f#i|FgPDLf9d9p%6#{xj&};@#;Kyfo+ at 5}02t|IDY#AMxqwDZajb ze%v)Q(l;hcj!TN?UGg1yxzaZ`R;9}O<$I@0^^P!VJQuE?;(}*fJqvP3EM(*x{)%)G zo}(1VtWlX->@2PXw}GW8GliSGdPbVK+-ey}EVixOV~8w2r)6nfc1>b|$;)b}iHJ at 6 z*-%k5JtH}(A~0CmpA-4)-p0F4tCzPfXvhvX)+DWI$e9u(UskelMp<oXexcxn(OhA) z7KUyP#)XVwlrv0Tc8R<UG$|WNkYcfBg73qT*8q;e(KQl`dIYJ(#)Y<N3I;6+ZEg8k z$N-n-0Z%|%TlmLdLp2MW+>nHRi}T51exPwa+uYvBbpfJLZf`%*p?XSJt{i7GWk+jV zp&kUWQn<7XdAyO`5UH!binbP{M9M{fQImPUKd+z&huRK%MrSlXu1hE_Lk{o%_j86* zKXTCzS4ba1Dud$=iagsu3_}9?#E-1gP<|d)k~%&0G05E##*zFQGasFD<8HiO{^|8) z{F#|8!;iEeS?xdHTV>)}UNyYpPdj)131dcWbOQ#R*LONad2sUl-wF-7&wPH<sjmDz zd9_A^dhO>oIqfNG=&Uft7<qiBa1+)Ph>WFN**NI+s(gGYlH#3Mb+YH=D)|JjS}lLs z!z<+{U<}NYU*kFA+Am*3-wFLA&UIk@OuGW3XXurLa&&;6CIR7$8?&M^=s52z>*=bS z(Tg(>yEd)8WZ~8c8J1_(@UqtFCTnG5_2M-v=Pyai+Bi1BazQnXheAurO^mq<5~3I| zy5>&4vA<K^iA31Lw@WjjGN%t8pz;czUZlBR#N?uekXdCH(cMw~UFXQNopxB7>y_=~ z-#CF(+8;vukQVTZJ$RWyma~@0ckuNa<i{bbZ}4pS4rGIE6gbD>IVzN4+!q}H|M=iR z0+=6>KwZbblYa_IYs<^CaAZi|`Onr}>utRc>;msl4t3(2Vbt>$IU1cTwj;lskDTh9 zk2+*mq5)v2<Y|wWFF~I6)xEuA^0fbN=Ob5eruJ!$q4se})V7p`{vrD{<}=5*^awmf zG`Skh4DPWnD<Owx&Rb6BdH%2R{d47<joJ@%gJgX|4sYmSX|Ynqq_c{QeWzD!`g+Cv zPc8d5KQ3Q}$BpAp!%Fc2RR=ptk0*sP9_t_E<4True7dXIFAi(|>|#w$I&232cpptr zv<A<`YmP9MSnAVbX2+!0TS|-(ym_`Zv&L*Rnrkw(qi>}R@;8<lxrssEQZF)h1|{as zuyEg=;Xk`-^>ux9dabLxgRggdAbgbk-dhCi$L9Cm!r9_ipA-4LA9{LF<o8a7u}1m5 zzu*WRpt{W?>!fFux_U5|(1N&2 at x2u-!^e7CG00GR80K|qdC({i<%#!c$&a#>S`#-N zKX!OlPmej}EeMaxKsOJTkEUhtZNwl7A0cx$@v$Dw2rkB*zy3_`@Vwq<L_=`UQa*J% zUK%`5iP}jb=#GEEI_Z<)WvKBCFjOi~C18nq5}v%uG<ducWDI^~Fsm8jGV0$DRwU2; z8?XFu{<f`sA094komO@jDct2Rx6GNpxwW;Zw1xU9bhai8a6A>ys2-=QNbmp)vmq@? zQ#c17Nz1nO$uIN(np~s;8|pnie=~H(ms?H|I6g>@XMr2ZGR?6B6LaiDh!dfW0Y!AQ z%Lwhc#F^vBf>#$<<QIXw#Ue0ASo9XDM*a<7;=Q(ac9y|+cFM^;tpkf%FO~llH^(a^ zq^n at TX5P@ca%;M-{^ZPM4?jB8!Q-lv)3UCj{!6S4`CgvF+#vQxIRu6}uLPQ4QV_{^ zxbhSMA_h0E?(JFUch{x+@2M!r=qcIr2VTBn<HmDVY}#PgPgmdjr~C29gGBw){EJ)U zuWq?{=cTuzomiD7!q-AGnd;fW&>>S4`UbBE_>12v|9vU<AJ_o%gV*-hZ4Nr;!1LF4 z;PD}NR7pN>)FoIgusi<0n~z=iwKVR^X|q7pEET@9VyT4V83rcIC*)c|k}<nq>)h03 zN_J=81Y7n6+PFHGpp3+wiTPKlel7m4O(c8X-mv}r4zF<6_@Joh#E2<To=rtgel3m2 zLwy*wCOsDd`>Djuf at zdQIrR9JVhMxk^aK}V2LYqoKBzS&R%NHR*4{YJ6y`m}W2#?h z&b;mE+7RyP6=T>gc_-)#ro{&pW)ydFQ@|9 at z^OsOQv>Bo!b4*Grv|zQfp6g@;TzC4 z?6N?ZS;(c$Eys}rsX;Ck{1!cOA)co|xRGCyQaVB0RFjO{RKY<rbQ!#@)Fv;r{r<y) zr7kzjc~#DNWbN8Vy!ool*9jQPUf2I+zIS`<Ddm%M2Ne-}iZ!dS8w1anU!LQUy`F<F zoH5C~^cwA3%F(}=YaVu*CKxn={l|l`yNj?aG>%PPY;~CVD~A?;%L5jD+V<%p9`Nnr zLz4H)FaIQ;JdQse@YBmLgG3kydwy*A-jKCdXfj#?iR^rKn-4y8=nyikzzw=X#hF=! zri?rodjm6OE-jnx$dYX-O-uf;!5o*6l9JTWF{8F#Z<ub+ksXWWJheyT{ta+e>^oWn z`D=8x_P;M_`}+{K66W(8hQH%C$n#0(C}kf<S)4sZQML3KD^IOj;%wqM!;v1h2MsE1 ze}=YU+XXu4<$ulLIX??JN#2JK3j(2tf-9aB|2KTVoD&HBLrkaIcDs37`$1n_IsC@7 zGsO>WE*a|2Z`rlX-bXxnTIAIZRGn}%_v>tfJ9iGYZ_VBcK9?WkHdI_JKglcPJ!*TP z&F82`>j=2vVK>0}77XhA)Q<m>_n-ef{M{E{P+7Y_{C^_{yh0c1Cu};UpCpYpxBrMH zsc(?T|BF2F6u<tz`P>MO=dm+>ue~3nQ~Y&>eAN~0R|vJj4-x;_^N^FwIL8(^(vSxy zC~3$u1IK0|H`$YrldsdDX_aol%Ge-Iz>jKeBia;mA*FZCM$&I5tsOV&T}FERw7!8Q zLw#Ary0(}SbN_<H{k>Uj+e#}cN-JzOxv!$U9Pi5clfC<PY~G_wk-z34X@)Dd?YO4* z#JfwLTC!x;oTWp(vzILHnM>Ov=oj_pKh4Q*gN`LW1;06&Q)U}RzQ_g^EydVFr3&~Y zi_f4eMWtd}S+VfUG*Q{wE3as8zqG7d at _yXL`S5ptY at n?wMb at RI3j8rP-uAui!$;cp zwm0xYlogoTQOX0K11LY4jn_AP95wc~7upe1kV8EQnO*7mWh8H~@knUmocFMVZFcHm z?sZFDPjBrl8_F|t^EdEA4G;IW)b=)HB$=^oEapOK31F!9U7!j9&n^pew(qXJrG8d# z?f2~}l(&(USiqrP5Gbyp9;v$PFDg7}8^z{yhBg+`I56lI=x3LYNTHjBbJp>^7;(BE zY8l8`Z~erpHb1A-qunhkZ{?!R8yEL3_h=7ZT-NCNd1Gx$fz_7Mbp6)Ndlt{x)Z0$< zvSD5IC!8AzrTkZcImMXiX=#rq)M-fHV4l^F8XG@(#%9g1lD%P_ksasN+uqLW>~@AZ zAgeOhNVbL{x28Dq3#h1T92N#*>QtWeAs>=>NTSsO#(`499HrQD$Py20?t_Zfq{O-( z+N)k&z}H-4w<9k4(5<1SI5lPXyZ7JcPdjV}`&sBc7&UMT<(twtd0qHygrr$VT`!tF zyLS&iJo at -G&^36ZRrq9qm8+QZ at aPQ;4b%nA++wUKdjfweGsSF9d7<Ye{?&`GU at 0@} z?$(SnZcNLNH_}s{?i=Wqhr0)8tQ4FoG<#srU}jT1O%x9Lu<$lfdJ}k@xX*4eu1+?4 zpxDzQcc(O(-3%cFiX$>E_%gT63&uagPGnh|-MiMCFX0WFu5x~o9c5M9x{{thW%uQz zfI|PluJ~DH3ptBSA)=psc%A$iaT at Xbr~u6_VK>oyr&>)~jKXp<jY4}hyU6^xU#yn? zr_Yqb`UOIc^X`hBjmbPHIK;;dej2MqlOxuIT3Rf@IpOeZ@=qm=<n14uRBUJsp2GPJ zS9~9a)y7MIRQ#KNly>?XQs?%~>uSi2?R|YVH--erFT9DBN2~h4Lctf*E<JdL@=0L* zNoIt)*v%f_BL7r=3qG8zp3S16hwoMzy4${D;sq1nD}Ia at 0Us+!?AoZ$kr`!Xo^M}+ z6BF}y!LolJ<zVn_Gnn^E at GWJpboO`=^Qs5+`}mDZ<qIil6suS~{|_#9_zb0KU}k%c zsy*O(inVgM+(;@L?4CAmppbO&h&+rYj at FY*qOS0-XfA}^0UO`$VbMGL@J_GT^6iR8 zA6X`sIiD5zj-QY(;OE_Ax9re$tWVd8bKc-3^4i*|%y+kZ(+juQPT{hETfhj8Q`QBP z>pGMo76O4QKclt&Gw{c*#hIQ6h1l-hvfE^)I&xHxtdQt=&NxHmN*rfaAEOjS>uB-F zWvp!Mr#49OlqJ*iFzLZc)26jO^^|zhQdh&}wV|O_t`pB!`blcARae{Y5f5FuuAKak zioHA}y`<<MWmP3kesJSE?U#sPPpqz_moRQ)<`%t9+3<5RT<m8B@p$vj<F1WmV;<Qh z4hH7edVTzPd#~fMT{s2UQVY2D^#rYi&MKn&SK_$O97~RFqu=Ft-3$7To~;1~OnA<e z2EUIzGvuUm7L(4I+AcjuI_D8(4*wrKmr9I<CIj<-6h$>a>J at EM>|%zt!3;ywEqOwP zal)@U$R$2yYFJ!WbYP^Prwiw;D~>qXDp9t&`l_PT at RrD`qIGq3UTzQH?J!-Gaffe= z)`5_yICToACwY%=XE!Y*+MGatK~wghN1=gNiJ+h*Y>q&JFv^(rhkq$tJIBtK>Jz}G zNmq{YBd>HYDS^ue-M<H43LQf$FZgwk`$QbULQtoCif1m6<u=O3C3%1Jk$hVI;m8rm z`w7w!WOx1zb?y4az6ONMV(B-p?eFuCl5%lcIfB7ynnhj3)_)v!A;tST>lIgL)88U& z|9Q?DhR02O9+A3`+&C`i8axj>+P-eH>mIVFdA{v#tlQSERZQIB?|{?D_jpl!ewYue zqtgvJH!xmeP5`@*Ryb()YR#JUfS3{Fhr7CZ)Y*jx**4H*BSqL?pi5#trM08zs|r`~ z))>`cRE<0We(gTHjkT}6y{2aM^1;?x`N!gh@DOc)pVmF-!<rgfeZw?(Z}4cC(Pxl) zlN~3Xg^mdZsN=yZZepqt+0gLhdHzKVo<RCcsMeC*kdS#oc=!wRv-!Pomcq&r=4#2l z$4_}W>FnHmM7sbsJ%7v_wx|zjV!d!MD7Gh{$6yN^Rw*s2?9V0Rml at jVDSzd0k)>JN zO0p_4iszQLi-(h~w086^$8*ShO0&0>WLHGdbIHUb$~FUOk?XWVlZUxRSZAO~9Bi{) zJ at P8)=$!kAUzBz5kaRAU2_*4>cDB9K{tlMG3m?QHSd?)A(zKr_W;-e&#)|e87cJ_k zuNTko{fABN;&<4-B0J0jfXUBA<rV8vtwjE=%F6a)Uh`bTTAM6$iC7&uMO!6$HZUU1 z6f4qhj^dy+=?h`1Dput|GD&lUjd~}nOVu1zO;pjJkrn(dp@U#w312NeYKl%=(_;kT z%Av<$(WdCNer0*J#a#{In0FGAi%jj@$PYc5X{@!qS>8yq&ZT+jGpF}q87*i~f<05( zcf at K-4=R~OQcI@|NUm8`+JYoTJ<1N`r)#kTxmNzP?oT_#_Tz*tNW#@;<U)-JxH at 1` zawJ#U)Wj+pDuYX3)Y;;oC1iO4o=SRZ@kec>IZ7W{djx;ngS&i$KlwDl%0xZ_iU}>m ze5*LYmGc9_mpL{Z6n4KLt02Z$ua~<UUt=jrh>Oe&*Vlx_?W%BkVC<ZVC7mU+$8C8Q zg~VI)LZ>3{vL##}>^;-*@YwYB)|u(!wm#E=XFQ(CK+mSjnH0}amzxuggd#20#9k~9 zbKn;qcdPQGI!kFmN}ce~4QM*~yocbQn;on-+wn34``#h~iDN9nQP<wsuK4LHI~D$> z{U6%@gf%!iwq(|EDWSF{ReoIE38leol<&9ig$lkPJxKmFM_m!PAUtiimD&<MZEJ(8 z4I_5^xNx<JI%I#+vlwB&1`0v<hZ~Z{J6OyS$1;0*GHw4_D__M|uaadOk4TcAATu=i zqQ-aS7h2`}Kf*3JXsOIG;(2*iwOIkjJs2}Zu<og>wze$Wg2iutyWkSrJxQ2dKFV6G zU-0sZw^t=o{gI!52j#5_AlZj1RvtO6inr<DoH_L9X~h!m>FD@>y1VlDsETY|=XN%d zkPrzVL7>ADmPp9H04mv<Ed)$LSWM6WAqix`kbr<VjG~C5s83u5M55xjpyQyT$jB%z zgQ$!P0!mO+lvNM}!eYALcdF{%?ku3bnZI88SLxf`Rp&dWmQz)yPE{2eRgSyOkuVtQ z=a>N+P7pc+`I!d5nT*xf^-LI)J8YP{vl~KRM at 2bi=+2HF;j at Z(t!e>yfccSlp~~bz zE>L;k^ZJxE4%ch<Yy)`#*hmXs&TtF(#OoB`MupkK>|cS$V`iQ^pwCxj?Vg?(i_^zm ztCy6ysO6>P49a<J+KsGq3Cd~!ok<6WI7UiyCCXWiE|9&91=+R&3wkhIt{Tf}5cH<8 zc(THwC*Yc3;jr$6LNqo(CYi><Ko?5~rAZXyS_Q#sNy?LBI|K~wrQ|9fX7QeC6Q#-X zFJrN%=45nKs9)7&-eZE#i`j~<1LOCz;DguL)K#dh>QU|t=s<H-%2rh8g%-M$&8i@Y zKXtz67U(N=4nQnlTBb~jz8!I`o9qI4zXV$p-G`!cU)i%qhftb7I&QclJ|QEjcSPdg z(9v`CP4~)5Fk`_Y>fIr%>)^<oxhVzNj_NU2bk6GE?izLRy*`A!Zec=tQZja)gBP|r z{Q#X5fg6)VZxdg;a+}ivb@z~)O_P<E+js92)gv}j-|cIiAkw8%<kh_+uPML0Q)r5> z at 6dquc6HY;A02|bf7>Jtk4<w#jj{EJ9&$CTaa at +H!`CWb6mM8VT8PhfFSeSnBE-bq zQt&jo!ZE`-i~f4ZU!m+L_CpD#G7szEKcZrz`w;p%X+k-9SueMmlst5Ye&+L4`oWJ< zUoF`36qV)-?42;Z#qlY=1=&d!cxdqbH=5_B@WsdZr2)MXwSMk`$pYJ_=E?I0F_+ct zQW~acO8VHbxMln}8l=2soZ}9G&Sjf-^SE}j!~0<TMl(dC=LYh}?(R--C8Hk*<naq% z#-?r%y{+)X>=&P@%9po%Z*V?Ae2a;jZac)~k*m5CT<X7`6j5KVE(doWtFJHI><Gga zKwJ;*%nWmEMp_d`_Zd8z(3d09ZHQlAuMi!(cRO%nI3vQp0r|r|LYDpsz&f$IP|?Vb z8{5-;hxr-ST^@IstNRo~kY{Wwx6)&;ySl>EmtKr?bg?cmeF?c|USiq^QGt!Jv8nTI zpbMl209fWRHT*{=Qtjk|v5oKiX!L=l;X~~zq3(l6IpJOhwU#7Hlh at DHisqI(=!QGa z<z4G==5bNMiPSW^1CRUTn;|RuN3$+?dRZ7Ls1v4%hlTZUw2=IApX>rI`Xubag}weN zGR^nspJ?MK81C7Cf0YAf`^{{?hsmFDp$yos18yN0A;D3RSL)cO#d7k9qdtzR!on&? zcj at Ip`Ae6wa|d}XTxdFbfDR98dEEdjcu8(_lOikiAHA6aKSx?#UYcg2q(rVG6ngN1 zvVtMr)4mnA&Xl{biCv48wi_4q3UbHGX$nt>J9eNg=7Pm0hZ9-D%N2$g2>*s^<233! zc5YGxPEEq-I9Oe7$;;x<y!Zl&Z!qyj4SV}ek;GdxV1Sp-s=UuA!R8a_4^N8x+Ky$@ zOGaPg2*1o0+a|SN)WCitJB`S>hu|ZE>#au6f*EcX4Ek(L=zy5%*X0<sx!HwLUHWCD z4Ne|fGjg2saWB8%Wlv~-4A#xOzqAhe$WN|1nOBwjdZ;FgM>*QJE<Ms`T2Z*ZzI^hS zF>(E)ItAEz_(WdOt$UaBz&}m(xP3?7c|PM7+_}d0n%_ec2gP6AJ-A!cfIht=D;DVs zc*^o`SSs@^PUqV&cfP at cwht;=YmBv;dChgGJ0CaB&7A&wva;{TZtZU7sIzn6=&IK` zUOZp-w)52ZoWF58e_>G~Gk at FbD^a-n^{fUhv$(k!R({_wZkqRRWqD9>mNlC1qO;yP zCwTFUi)8=k>-~CK(~Ul|&R^W#j(s8+f4u@*YY3mw-_l#O>FGV%b<~`=QD!A3bETe3 z>=*MRwcTkMW##%0=5(7BW^a$loU;Ek#hlx8Vy8?(6`n>J&uN~t5Lrw6tS2yUUJiW) z3m7IydVGdK0?%_*<L5aHJ?EY?pT|r_j_K!4pR{(;bd3Byz{<`Z)5Z;&gD`*I3EhS9 zWRe=;VqTJDQCG9*J`v~2bLCugbk;;Qh3;X03*Hty(5BI8^VJMG85S|HhB0 at Ju~_MI z&(#2PplU?uLHA at ivv-e&8&jA?6f^5K at Wr+s^a<ha`Mk#%T(S`^hvAIgDS4~3^Kn}4 zPgt*WT4?h)w?3Ckx^!r4x~W!|eENo|$d%Wg!oE}LbM8;f^zJT&<~pv@9m-9w|4DZ! zFnpQkFyD#!NvJxd2#S-P{8?|3<(9s&NgcC04~(8lUN?~_#l=Qu)oQ#QWrTjFA7>8o z!`yezcD^|F#cc0s>7x#S_=B79=XlmSTGO**-+tbClgAVmbWR>06LD4YpdsFTHQZc2 zh;V6mar)rInBno^rl(2cGuO1~-519YPLJSY2*biUM)VyJjr-t6IGi8#n=?5$$bUe; zzE=)JJGAZ(E0wnEVD6=f#}f$ctxTa<HcnSI&5TkuR5#9UnibaBk}D;>C_aC-y-Dx? zkP$yK at K3*2{*{uYhkmd0|J{2b|9{*IXs&H at JAt}rQj<Lwx!>}vq5d9=-CF%No^daD zo9kWNKEkoDswve=FMK`ci9`99UIEkU*jd!tDj1JnFvgN|IzQZ_A+URwmz@%mdiNP@ z_O;DCS67;&DR?2#4fI-*aTB|)e(|dsJ7s&0l;F7vtR}vH?Zf4Qy0-7NlNuqSw;sMF z_N7~?OJweJIqa!iB#+MPK#QDT5g(sNVOkug6X)e6T`9*zXu4=?QXcfFyzbPFbJlFT zw%?YG26Nl`>Nh2{-6>mD%|!&2d?;BrOFpCzqS at x@l5)#^CC;;#l-}6P at XmsZ<bh3( zD@xJF^F7P0j4Ui(S{+ne#<_6O!Gu(DoY(R+Jd7n)nm8>-6BbUZ1BpP!X)%D18cWfa z{t~uQ?0t!5d+a5w!rWtdo+uE})23A{_QA9;3XfR)Y*rJ)@lYvoYD<f7a;b;G*yzLg z4!PBO>PoB~_vL*#ZXeAimSFJYHWyp`C{Inx4(#ElIXE(&*-*}!mJQhS)$E=~ve>!5 z=X{xk at d6trU@3<~Fucq|gVY_P((uwOGl)hdTpQWJIi1dSye7KkPM{mou at fjRdrYsK z)Vr31G~Wgkl?O~EGq0U$c^T4&N7p=D@DP**#C->4;dW0a)WMN^v!=&n<v)6v+x at hA zB6I?*Ah?Tbn6;LucNg(KYqzh$(|neE<2oZxyDALh4bFyMhn#Pv)$5R)R<A?SB&76Q zhn%m@Xt8NrzI(kb>M!)Kxv$C87jAUH#nJ at dqYdy}`T*L1n~u2}sw<%8<P}N8lNGa~ zoA-68@kS`m1xy}%d09bih-}AqqqkExpi>zW>$c|Y^ugCHS-oY;V>S0KKe+r}n`G;@ z9WTL!fAr^+|9Q?@=gGBCExzk%=tXr_gBpWKzZbJ at Dh!OrB!cPAeo7)JkM>)t%(EFJ z%ZsERJNc~`ZMl`9O~|{zU>oESFm#SOW0312=wi}|dAhH6csE?~O73eIXpP_9vnuZK zHEfN}z`J)W^Sq3~xoY8xhi-P%-n~re0>wZ3*0y82g`zL1dq-iDgGt;xsE54#MlZ_t z#g5+He%Uu?yASI*mow?QYaQ7WCcq;@-CtI!dFFG9FU7a1P%h5DQ=SXgx)es8R2~q< z+bRnsi^mOZE#$GZ*hS=eR@N%(BqB>Vqpd=-NnNoPI<!0d3Na)7t^Ft_;BW3rd6SV_ z-<)FnMe9lBE4dPW3wHLifu6j$zoZF#algrbGh$2UNqSjltm(Tn3_U+Aucq%(A7}&9 zW$AI}G3}>Rsq}#9k1!uKm}?c{w&I3Bw?d(Xw)8%LGO29tdBFW5=DWsB<T>*J8=1+8 zd9{qmjdK%Aq#0VS^Gx|isCEiEW-IN~(mA4 at Q&+a+N;cBv7w7BY7JR|2jL+V*___&B zAM(si&D-XO_{Jjh9Jp<G@?{y>kBQT@!W-X&EGevN$=^0m>)Vn&P?h#gJ$7Ror#VYP zvuLu1tq)7Sz?>2n>fzYU%_hcbFU=N1jlod5W?u3UTpqsC2bkI()BdOiSL;2zroEXj zTQ8I+k9hcOde#$cf9FLGX>N|?!x^yP!W(f+6kD&!<K;f=!dK2X^S{>{u;uKHr^*No z>?-o5^L^*b$~n>KBI|&+Tw_~-K85kR$d)VdEDvhFYDeNJXA%?9Sdr_TFsG`F{YLWK z-Xt4XK6SF&m!rpCJ%K%6Os5ihMKvB}o4wTDA9GJsxZH^>`z0<G+}jdG&38q0L|0{Y zB&0Q0bV7JoP|t)e*4C)daPq#v>{yfrTRdS_pP;Ci0X at 9k`=h+Ow2er!IvPX#=X;Qp ztOsDLVV at FSe8j8S-iqI|Z$YgQuC>+S6bblj>pODcU?=^A)qxz%oQP?pK8$r8U&`#U z5Ekw{t(ORGbvUunc~srRtQNt(Q453I51MxvYy+m9TObzFeS3Oa&cvt|O|DA#QsYhb zxHrIW)?~dFXz+!*yVZK1<unKk=?iUJi24T%ZiSZ?>cvtTdvSRdmX<o_w<eA$%PQbt zRH<-e2cazZO$4E<O37$lh*bc1tGN&|uO=bGilI;bKhE2)#2nx~oL%n2E1_oH`|Yct zXp*iG9}ezRdjErOWL`7w_JwuruMfU4w`N(tUKush{`|G=d9H%x&ILs^JD!>HWP)RU zmE+nc at 5q?nXXGtYLUWL{Tw<;WI%Iqe{JuOlGi%}>I^OPE%}bZjox63uesDph?>L{Z zUW2dg*{$cOm<d;>Etodt>W9;_voq4NMj2m&sUgmhh2_1v_sSdDH}r~_e$%_eUg;S9 z=S8cp3#y!aeZ|blQz~S+tGwa*%QGadzrQ16-n^0KT_R7o8hc$dOU(Ru1DI;RIZ!+O zq0FE$rLX>;e844oFaDlvKs$zihq2i9Fv^H+m`Ya=D?EKb0S(awFv at usoZROY7TO-> zq95giCmB1IsB;5c`;huaNe5PUUD6OvF1E-c?Ulw0={Z!<o1QDZqqP^Rq}kVbM7l-S z&`jxuitFWZ4g&c5S=zn1w~v!fcv2rr|LgFCIA8Wfc_KTz5d4iAB{_tf`&?t9qwyzc zhHvj!v}n=$wV0ps=<P?)y10)lbJ{b(k-L1kCwI}t<AVk{Ry1P{2+A*%qHSf!Jsv%@ zJO|$w6wkCg620$aTbX$T`b@c10<?~Rzuv~GOVT>hlGcDcW0jj0Z({;2cBtzo^%_?9 zo2=OIRt+yt``~+%O%we>#R;cBiS*sE1|)AjY>__5GyOmFOcVM->3}WU*EHy!YUH&X zGq(q1IM;Lx2jrrITz$n=hV=Kss_Q3LT%VjUq-!TV<k@?DW$DDB1BYe|=pprp7w$KV z(042Fjsv!@f*zko%dH2Ng#=!ckjJgD^Z2UV<!ysQY!TgZvB`@W`nas@<HjWwwqew* zkF9rEndC17`~}GR$}>?_?`30b$|7Z{>RRN%r>d-t&G|H3`&<#iWebsWMbrfk-fRj0 z6-%+^qo{&EldnQFDllUC!|A=Q+T+^8+uGC98tr2&3XAVpR5!jOPi*p at MVfQ-XexCM zrWY12q$}m!<`5h4;VnePGCq%NkdLhU9?7F{I4f}JJ9jKx$nl0E-T_+&*euZR^3Flu zgsZ6!#_{HU2e_3-@X9(`bYWpwCyMs*yr{y~p?HB`gjfG7>n0>cXQw*RrUxb$jYX4& zj9FMq<GUSsEemRw?^Wl@VY*8iU7brN6w|^9>?PYf*?p&@TubZK1+D>VEsf9og|@NQ z(%8vXu>=PVrFrOpTG|^FJ2X2_F{sw{Xr$stUweVPG4F}wu|Q|J!_IR(s#EJ`mY+Qv z!h<cWE!wPqV{7&n^K4;mW1ZNt-elE4;h{1%F}ck5gj><v^r8*<l`V}gYP?n<>w7G+ zulbnpcO1KF*ZshQ9`_};I4<apSC`*?(=E$X7vXV9a?>+|a(hPizJ2QKxm?eb&5*~c zu#%c11k-2Zl2<Ei(G2Ce58$;drAg@#k=cbKDmP4#<JULbXYQJ|(yl%H+jmUAdc^oV zZWn4l*0rdUn2S?_T*+fUC{#|247}NStDvB8BGvtvlk=m)xt6YDzt6U=L!uG-8rC at Y zZc5*O={$vmYIrJ_?yiDQ=r|fSEH@v2?Yndi?$b3Uei)_Qk#XOB8Fx5VUeVsm;Tzfa zEm#SWAL=e)?oW6pPRstp)KMN6MAIiN`-EwQk1o1(g}MgQqo-+hB{T5UdH>w!@HnU? z9s~=5Wlm47S<j-m*0uzkH_CnJbFRBTYFygNdCy8lZfEe!Y*OpJbtmB=-L57}%VRy| z(yeP@bE!BVm0aAm?9DJx94#bT&;(?96IpbtYMk3b62?KVZ>y8-3fjyZKlOxToAhhL zNuIP$*V9$gpLufnrqZQPOndr{QrSX&bs6IupM~Ci$r#Il>G|N_yEwDfhqYs?^z?Ph zlz07%;>nK`%$zp8q;#^Z=8YfGN at p?V?xpky2c`!fT-+bc<2$a|s@+Sq&j%b(xZ-U~ zp>p+8?Y66Q6O`V5+%`=67xRR^(rlwGjZNFMpkb}qb8fhyHG2-B-U%{{dRT0E-vqZI zxdXog6nTf|a~+P<L7gsd?>~sPW=u at J#ygV|V5i1<fbBEchq0}N=hf)L+MD)<Cg;Jz z2U7PiwpWhTHa#2G`BRzdXsGM(;WVMyL9si~8(Oysb3X7)q1t$#PQkuxm!}biDpNbU zoZhmxk>i^_vT{jBU~knOakSdZ{EUqL1Jj7YhsNfYl#YuT+M~zNm~o{g`LRQ(XWGF2 z85#MR6gMz=(16Gxi35}R7r$QIKQU3hJsgubByzx at Wac7Bya>&s{^p9G^)qx4E!NM5 z at TDj8vrWVpLHgNCbTG>Gv$tq#tk%z{83vz&LSEPbEsi0mXA)t=_ALG$K0)+=ezu7z z^qhXiy}8Cv{p>Bmj9c}ykLYHs6X~K#%o6iNwI~xMqEyr%-};JiB1eqJj5HFjD_e~d zgOdUHWfxPBx_KHq#xTWVwxn5!Un3Csn>h&O60rhLl|UPeU(z*xIese>Q}Im+p5}qC zQqZuAA~6lGVgwT{AK#VZo0<5=fi#?M7G70AU|!O}yB(f<(Rjs(0s1LHi1eyi^Qy~A zN^9(W$K{Og7a4h_`<s}Qlob1vdFC(njN;j4C6)FFJj^LBubNd+Tv;>No{se8<z-Xt zCFS#GmCm*oO`BFcjg#dUl^4&nJIcz-X3eUaWAB^Z&mJ2c9Wwy`0GtgDE09;UQleb& zUn!r^PO{4?rqs^1=N47Yw&P<C<SIeDa>S{|$KsOO@*+GqARXrzzgHthv6L_h6525$ zwO<0k-i)S-pNxEoL28vdF+BTsR8`hEs;Wzh?XgkO_CfY0oY^%e_&#|+Of=+*L6VEh z)f}~{oF+9P@*zpJtSc;^9Xzp2iFgwE#nrRRsw(X<QPGL`=!vCrFbmw9`K9S28?y#5 zt7s%>6Y|eBmc2S#{uwA3v at jg(3XSCr-RUd*Fq(c1_I}<O5d?kQ4y%fnLC=R`4Zb7B zfR{s?g<(W{1;*Un&^Ov)MeYIp)(c~S2&~NXMa}CkTg?E-F;LdOSoluG<ILSe_`fEj z%?v`VA0n<2S7U{LC~nV76C2PvGEuU_#BkhMJ5r2-{u~W?$6)SoEhNkpd1#aQXp<Af zMC`Y_P85hjF&QUe-+)^3q*yF&7yl3siKoRKg73k2MLa7ui+_r@#M@%MxK(Ts?}$6a z67jBhPrNFg6VHqH#S>^H_lVu%OYxQ1BkIMyVz2mGd?WUWZ^g^vKJl3NPTVhE605}; zu}gd}>crp0Qq<rN#0%nIWI)GFB^%mgqi~Uz*oIR4P;4b{oayN+&Wi2gBk^zXu{e+I zV{OQv0>pIG*-ymh;#2WA3Z%9;b}Lxy7Z<1<TKNvKQ+z=obeT9p?ZuB2iY-bV#U`8t zdO3BbFzSL6(z{YO>P~hFm#uw<m`Od*qAIDUaFP&J)C+ZJmbg(=Q*Xi{Y2uuii`G&r z=1^bwv-PJ)ag(?iT4laiATZye0Td;Epn()E{w!`0e-eu*hGNB0iW3JYUc5mGlt@W9 zZ8!yIoeicTI3<fuW*;hkrBq6zba99>#7{)xusBAU;)FOUei65c<Iwl7i)G?}#3^xF zoFRv}Tihi!if6=fu~s}NR*0v>qv8>9P&`b-XgH0aku-|3Xf$Qh7#d60QV!)(9*v`X z8joY3CekFjjtZ!dCe!tB)G4AVG?k`NF- at lu9CTGiGiW9btEiw#s-jtRBURIEs-apO zJvf(cqIoo*7SPSKkZ!@r?2G8na7nq17Sru?2Q8sHX(`=BchfSuhwi22bRW+2eSrRl z9;6lY5Iszf(4+JhT1l(uF<MQJ(;BLyC+JCfiq_K8^jCU@o~7sLd0Iy=(2MjpdWrr{ z>*-~Bg<ho%a4LG8HqslkiT*)v(q{T6y+v=+7J7%?rT6H4`WJ1b4`>^GNFUM1w4MG< zpU|iD8GTMW=nL9OyJ$CkNng<(s;9m5HGM<-=v(@ZzNh{41N}$`a0>h(I!s6CDE)+U zACJ?|bb?OOFLa7d(-~@@vviKm(*^pK8p#P?ePLiAgQF+B3~$57 at HPB!hl;-ufL*d} zal2ix(as3LO;qiTP|Vjm8l8;Gjm}1x(Z#sJ=xTJsodR|v+~{HS#K}IrjR@>S=xg*d z`WunPmAIoJ${1)w8!<+#5og332}Yukgx#zu#vo&`F+ at DzlRBju{fKXBRY_H4@l2nL zsYTUQl|GsB%|CNmRZY><spw&RGj)WlqN%kt#XeaoUbgy_EkCs#Q(9YDQdC`AQC?JA z<CCL)%TY0N)u%l9$$MONS!GGvaZTb+k}>=zxiM2Kil%~_z`Utd6%|D$BkD`v5$3yM zEO3-Zq`)lC57{0ce8*^-IUZjI4)gfnJ5CcGrzI{@IaH)SPE+y}%QydGH at C$)Lb;N( zT*a$YpDN|2wpC3eu2#RzmT!TxJ(%#Rk at 3B2Wj@t5Ni|<a_n&Ve2iAC`^exit7wh*j zE$~c_NP*>^A1XaQ_*UuY)t+D0YFf2g#A?Z1V2Nj(GWA<vuE&?CPSsUKHC~yu)l~r= z-?u56URD-^VMvnolx#hvm`}0M)>Dl26l*=jSx at oSQ-bxBXgygNu@*+Gg%M|A#90_| z7Dk+f5ock<Sr~B^Mx2EaXJN!y81WWHyoC{OVZ>V)@fJqBg%NLI#9J8g7Dl{<k!Ynz zv{EEmDH5#|iB^gvi>oAyt0apn>|)hrlw@(0WML#(7)cgJl7*3EVI*4^$reVkg^_Gw zBwHBC7Dlp#k!)ckTNud}Mv8 at zVqv6M7%3J;iiMG4VWe0XDM`Mym1WWK(H6A?i&}z3 zEy1FeU{Oo3s3lm`5-g4rERGW_juS16#2Ag5Vdi$Ci4_x{pnglRVyBoC&~kMAm}ryB zm}o0yw8>>mw8>>mw8>>mw8>>mw8>>mw8>>mbc#kbIf{v~uu|gGZ^`NUJwv}|>UW1J zg$K{c>bI0w{jO=G#Ou#m%9KR?S(ihKnNIVQlB$1C)9;#x6wO15<{`zQeou|o?>dK5 zHGZnbPu2LT8b4Lzr)vCEji0LVQ#F37#!uDwsTx03<ELu;G>xC8 at zXSZn#NDl_-PtH zP2;C&{4|Z9rt#A>ewxNl)A(r`KTYGOY5a7JpRV!KHGaCrPuKYA8ZTYrrEB`>ntq0+ zlcDKkXgV31PKKtFq2<WXa%5<_8D`B&jyHeNl4NM43=>I9lA$HZ(2`_o4l*?dnVN%4 z%|WK-AX9UYsc|wj2bo&3Or4XN8b4FxXKMURji0IUGc~?L<2y9IL*qL%zC+_XG`>UQ zIV7GVQROt=<?ncx{NY{lhxg=w^r>alQ)?@xmlxj at FxCC+pKf*W{!^_-pG-L(@hO&X z0mIz!14`V_{==*U{w3C9z;JhrfKvCf*YGJt)n29e=QE<FtbAIrPnmifp|Hy2n=xXf zQ8puBq&r=}4EJ;3%#!Nj;>z-(%4ua&y|RGmRgQlFl|{3vX4h0#%_=RnWmcBhiYrTe z#wfB?N`zcDk$~CmXRkbFs)m^wr?{zAZ{t+zT9tadJ4L`;kLQUV&-2{R-sM%5C9|cX zN{oq1&=YWSxiH_xgblN6fmv+<{_}1X8|MBbEklO5k@^e5hR#R~*RM1>!}y3XxG*pZ z?}lfL!toq##Ns*5h{JQd5s$Q9k`mA7-N7Go at FBv;aAf6)pz at -cO3dFmo&XoT%RoKJ z6y;?lMVS4YX}#pHe8vo6&dY0>-7vS01+{F<!GUIkf$stGsXLx(<P*5TY#qNc@0k0u ztvC>~Qa%f=3jecYEXab{J=1_BGNmu9Yy)BWNW$zt3vrnvf21GTg!ix}9J{%P1!HD$ zb)^WB|3kG5ED75-e2~*@2f<xn>Yb{QyV;UfTi7k8fWKQXW8I7y`&XEko*^%a#XL6{ zXRE!7dt}~)pX=*lnCqy>as7z3k7FVqVZ7^{n21w3CV^%FPWV{ndR{z&c#k5iM!?-S zuI)G_YL%Gh+9f}Ij87}|r%=~tq7zbwx%fnmVAlrVY!K~TTM*|i>;vC~eduo@#^1!{ zc<+qw!(2~`F0OxyD-hU&u^zwf!M6wGr+;f|-^fqvV5fnl)3sH8dIg_gCk5}E?hF0t zJJh*g*HYjv#kU^<b3ZT-fzFe_{0^AAk>*!m?uU=e%QEdJI;}zhC08Tj>_HBAp|r3X z%y|Z^5WEc7jIKZ`3OX!vdMvCi2VwJA1Mh}*hQs)WaSVs1<=Ecx3i3+!y4TywJIZ^% zPngd-->ZE~eb4)O`9=5*@f+!v=U3_Xir;p>-G1M-vA2n6liVh!&8#-_+bs5v@=x}k z<A1aNcLBo#rUZNxa5C`nz}UbEfwu%M4t$`kSKB%A^;A$;kUeNn&{+AJ9JDBCHC|8Y z*ZQFKL2m`^#B2Y5Sl(?=OU{WT*GnP=Wjo0A6HZTf3bo=iYQ;I!igTzH=TO`HQGUTF zop#8(5WGjAB>TJe;%>BWL_9oHl92z&$kP<ptDy2Rs5~YzP!5^MMF*%2bDb5#ar5m+ zoc5LD`bFg7^LTuo;5vo+_$|(OI)eK6wJ3M(5EUrHd8h?zfUyDew}9?D2=5}ihp-<s zLcsSCpq~TcE+8HN;tueAL@bAnxX*O~{2u}TN5o2m#}HN{Y(V_i5MD>vh~N(@`$Qyo zhyu-6 at U#qgkKnsU5mqB?z;~}9pvAd%;)bXTko0q4ehkd-#bDIHA+EQPVkc7U6n{Zj ziSQW0YJ{$+s}Ca2PNKd(2nw4(;bY(&M&5jb{5Xj9`ZHoQ at N>Z3IOOL9eE$F_ufXSr zP_G|Bnnw}-g0K?dF@)6!8$jbL#NC6i58(i4H6YDdgg_vj2cJ8@=XUV9A3Sb>WR0M9 z9 at Nf*+Idhr4{GN@?L5ke^I;jvfn{g8L&5c at DCLh)%FOX6IF;-x=($jEej1$b1IOQk z+n-QsTae$MgX?qP>J#MFC*bTfxH=7vz6VF&lYw~HJK&lLs<)vwg@E!dP~Hj3yFmFT zaQiUo&t7o+FgX4PI9>wk=Rkcc>cVX3xlmZ&LSTanfn_rUbz=|Eb^&b{N^=)VbC-An zVT)@o(9WS12jhPTLN8YX&>Df}1n(E1=M6}D9{f6C)$@YH4ak!QNZkOcjo|$Pa`giE zzaSbA_bdX}lfB?^C;0mu{2fBce+C}k1EnuPVJ|4`1%<t^HV3=rLCW30o`+QHQR3@? zyB~S>E%NLKq<9Y42T{U5poI4$^?IaUkJRgdy&u^7fxRCk`YrPB2jt%ms7LL<Z3Nm& zf7c1**)QPb5c2Cd`1un2;9M_g$PD~8T-K!q<Xr>u?kq~?G|)}~=`^JK8F}^#^6VGn z({Z4k0NM$lodDV?pfv!k0r}TJHaxS`hk>*oC_8mM`UI`E5i%SG--p5XVeowz{b?|I zjCRnsA$afVS^z)cKOuD;c-{e?KSwUC0^d7P)(2rp4}>kf9r|RhO{<_Eo(A7_&<%e@ zduT*^Kq)~BjYG&snBZCq9Z?4z at g!tg1s$;jI^u3{@i<!hF37kSGCqzReHL8RfvY;y z at H*7+I@Itwa99VqcS7!+s4F{BS9YSV>_nRmM$Uymcl3g$84GQ7EkZ7||2Vv>lC1}| zdQhqdrFu}RN9_#;pY6~ZLh#<zwHdN*28E4~^$T66cSBCp7ii23)REz?e?hkGkd5Wq z2I at N@(=Nzz6tcVkSzZF#ddR|U{{zUe9dc|0@<zz95$GF%z7gmfA;)gWu^V#ih8(*g z$8O2dR&ewSINA!1J_1J{f}`!==nyzMjGR9zb3O^}A_JUr`yC0nv!E-v9gjm9Pe9-i zLAmRFaI_5^Z3RcafTOR$(E)I@9UN^3N87>Cc5t*E9Bl_jN5RoiaC8(L9R){6QFGcs zYeYa3#G>ruQ3q7rtOwdRKspGdcaZXP$@~3?y$-zJ58j^z at 6UkuHAwwFcz+)hwu1Lv z$jwfm@(g(YD|mka<1HJgJ%`f+wt?c?s3G;>{dVxa6udtQ-nW7Gb>RI)@V*ATuL19C z!225Tz6QLn0S8;b`&RJ26})c+?_1H at CxXWU<dPrwcoX&i0%~g@T37?<{vEX51g$rb z{!R3Q{>b6cs4WwrcPAmY3gmcYB3j!d)Z_xRECZ`TgwdxjMs)si1a=0oPvG|kaUJSk z0kmHTBx`_VCoqoeh*EJ%nqxpA2mQl%jD8;g7b_6wA+*!4 at V*COAHo5oX+WH_2tJ^4 z0Wn=thbjGKgZ{Fy21C38pc|0q9H=*<Zni_NW&mqAcpnMfkONNg at yi6(8Ho86e%phv z58(jfG~oBMuAiVE&O at RCV0odncte_BAPwv4V^YSGz*y7b7hmvj1iw564Z=Ba3~A2j z95 at WL@yNvjaA9CQ%@1RBtbd~(@kzm(!0D%`ADd7=4x@f>?|A`Q<2*(cE^vPuxfTWq zewE|&Z1BJ%@f`e~3rcyAC?C|uV_Y`@)F(olPQnV{b<jQq$k{@y9CDtmK>UZGF-`qn z4~)GCoR9luPM;PBfprL>0ddbFoI^N|!1;SvYQ|tZxxnXs at VOuL_AGd19#4u<vNTWW zoOK~*U7+nk&YlFlQ=oSWT>B$MXD#z7E%PDJIs`h$AoI_<v`#|4<0!2L at O~1!vo=2l zn#V*v!d`@L5cVN($sI&EBunr(aE^oT;}T^%a@GZu?cmr%IRTV&KskjRJr9%ypm41^ zEpy21GwM;F_9A?PVD=vek>(JB?ni*qAnW9DP&@>R`$6ph%Ht?_+K&<ocD(_79vi&@ z{2w6Y50LUOV*iBL2Z4VaXLJ0DR7b%Bat_k0z;9n6&K`t%guMvgAnZfnG0;JTLkJB> za~9zo0*`S&g8pcOo+lV>xeIz8Z?xsN(3U?!TmA at b`6C%?2kPe%^mfmnw|f^Yei34Q zf>=8cYX at TOfIPi0qfEjmHwE=^5Om`Z^iIq09iw~*lmLwq1{7bU-;DGhBK?O*|DlYv z9qDgH`X`ZoGtw_WtZj&;^egLB0xh1cOhkEeE8(8v3e=7Nd<_0a#^3)P#6L3X=ly!# z(1yQne$n;6k|TeFKc1Cv*?i0OfO~lK<jqHfyuY;RFFa1PS#kN3AL_n)<dKPAn~y$x zo5X1TEAvn<>qpaGNA)V)qj<NfWw}Nvy<33xLNbm4O`vAM?Jy<-Wq3mv(i_4D<GO+1 zH3ohV0q}#k3KofUXy;DwcF002%7*^5!<T0!VpZZ5jg|WOh`k)Ic<8wYkmeDw68;m9 zVaz at V{s?Q(CN_vSKxqp+8q#35s0Y1$c#RMT at ER!&;WY~5?(;z4Kenid50+NN at cWis NJMQK8PbU3X@xSEV^uGWA literal 0 HcmV?d00001 -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 11/19] video: Add the AnkaCoder mono-spaced font 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (9 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 10/19] video: Add the Nimbus sans font Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 12/19] video: Add the Rufscript handwriting font Simon Glass ` (8 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This can be used when a mono-space font is needed, but the console font is too small (such as with high-DPI displays). Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/console_truetype.c | 4 ++++ drivers/video/fonts/Kconfig | 12 ++++++++++++ drivers/video/fonts/Makefile | 1 + drivers/video/fonts/ankacoder_c75_r.ttf | Bin 0 -> 65596 bytes 4 files changed, 17 insertions(+) create mode 100644 drivers/video/fonts/ankacoder_c75_r.ttf diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 46c5205..a18611b 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -445,11 +445,15 @@ struct font_info { } FONT_DECL(nimbus_sans_l_regular); +FONT_DECL(ankacoder_c75_r); static struct font_info font_table[] = { #ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS FONT_ENTRY(nimbus_sans_l_regular), #endif +#ifdef CONFIG_CONSOLE_TRUETYPE_ANKACODER + FONT_ENTRY(ankacoder_c75_r), +#endif {} /* sentinel */ }; diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig index ded3e5e..ba1ccca 100644 --- a/drivers/video/fonts/Kconfig +++ b/drivers/video/fonts/Kconfig @@ -17,4 +17,16 @@ config CONSOLE_TRUETYPE_NIMBUS License: GNU GPL v3 http://www.gnu.org/copyleft/gpl.html +config CONSOLE_TRUETYPE_ANKACODER + bool "Anka Coder Narrow" + depends on CONSOLE_TRUETYPE + help + The Anka/Coder family is a monospaced, courier-width font for source + code and terminals, in two styles and weights. Anka/Coder Narrow was + developed for printing source code. + https://code.google.com/p/anka-coder-fonts/ + From: https://fontlibrary.org/en/font/anka-coder-narrow + License: SIL Open Font Licence + http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL + endmenu diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile index 68f4c3b..58b1813 100644 --- a/drivers/video/fonts/Makefile +++ b/drivers/video/fonts/Makefile @@ -6,3 +6,4 @@ # obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o +obj-$(CONFIG_CONSOLE_TRUETYPE_ANKACODER) += ankacoder_c75_r.o diff --git a/drivers/video/fonts/ankacoder_c75_r.ttf b/drivers/video/fonts/ankacoder_c75_r.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3b73dcf00e79b8b5b40f65b003680ded3cdfb592 GIT binary patch literal 65596 zcmd442Y6If`UiZ^y;IW5q)h5$lF7`Zx5><;PI?a{gpfuk2@ui<RS3O^G!c<<DPjX` zfQYErv4C||6w9KEie+_o-F5k^>nboe-|w6|Dd7Ig^L)?uJYR+>_r2$y^Pcy-=RI#b z2aa$Y=Z_Bs=T}@bv5a%aFT(i&>Qi3cIIiu at zfQ%q9 at h~SrDf&(K2F7PLzi)_ubY^f z@%<nDALKZ)7uQWQ=5)+ESp8BJ$En^!g{NjLUZnSq4ocxTuR+`o>6$ltPDXVX%6e|X z_3N`c7S7{B&>}Z<3D<tJ`<He-6<WUo2=#N^ZF{?Cb<Dih<9izQN8rA#8vu`=xleK3 zi0jzyIg19jw5&#Z1o$W}_0OHrq1F9_@?P6<{pp;J!Fj?L{6yTR?du0R=FIvf;>8y@ z?j8ll)qXo~?!rYm^Fj}D-1mRyxPYJLEtoZLzwJykp07vwIP at EjhVEPQ;>8`)eDnUr zdAb3>ac>|0xNP`)=x66X;a9a1*KQn7q4>jdYNc}@#|is~ejYj~{K{&Q at 5!H(#vI>I zVz^39;&gbDwgpPyD+N~qM}+-kJHFlcc*Q$_hs)o>`&<{VQ7U;)r8}?Wd0w%G<NwKV zOTQnkzNl$jI#<Z)O?|2^sf%L5&o1P+J?B2AC4~K4sq35m=7^mO<bqJ%pDQFuB!Jt< znYj#N!aIkX$9Zy>xfNU=*Tyxn&pxh?<lt@_mw|U5XXIM&*?~8G(eE~{hU>?7nCrTO zYentlxM~7yD(<(Tm3iDLZV<mO0zUkr_4NV%3HvO;=WxB_SX(o>2tX!s&8T4=ASDd@ zK46l|Abng9=L2{LAfMycx7>Z`g**2a=S2JC(4VU$8~2+)RRJ{U1MZ_Qzme8 at P)2?w zFdRB?hrSkp8Zxf5#4y&w-?YEOuT$v7DQ*F~9)6l)Rp%<vgI at IEO=&m$rhOgz?n4h~ z`~T@Rw%jm{^0+);wHWVHct7F#J_Rn$!iapr#)Q^&%JrUy?|Eor!`K#ver8w0I45zT z7_%^pU>_Sr`d!G4yY5|%mWJOq;+g|qqp&cHTpRjJsfy8ay~m;UPcWN5<^D!uxuclV zSBa5ZHYDNdFWfSW<`1}cm9HSJ#0m(d6vcz`7^PJk`qYTJDO9F{jEN0gMq8mT8GZ_N z(O=!HtX!Vbp&OXbWKYZEN$c$6ve8mDxU1Lo6y?M|#+5yck2}zRT6YbX$>jh-xxF24 z8NWU*6~B$ZfIiiadMJi69vvv%&Qbj6y==VlK+QgGJ$@B1SRbzG^FgkwKCXfb$8Y)h zK0H4W&&yXOs5=i&_HhI37p+r==a=Js3f%e$N|r#DP&oLF*hn=okwR`Qde%UDKzuPP zQgKClGY`CT59;&fV!^3N?9JgDzeo<3#xN=Yl$PxyE4ePz%8`}$g-`iS<qG at z43N+8 zVt at bLZ~5+j`XrEdBY&5;OYC<(J{=hSZoCTc`&$O(xJNNdrr~!T@P7)pL&Uk>5lp6X z{4qeNbdBH?Jk|y7UTzK6Fg>@5tR_2nJO4WWCjSTiGl2+#;4XLx8X-t92qs~@aG&t5 za8dY4F-@=2$LLLZi#|tRs$ZmEtv?nW932zAJNls*O-xvfE+#&vGNvPDmch>u9cwUh zM&9UU^fLw;LyQr|SYwhg)0k)MH?A?RH*PX+HSRRtXFO&+X?)iByz#8 at 55{xGPmJH1 z@=S%MV$*cf3{#h>Z)gaZ>bZLvhOaRUFF<Swg4=Z%-XR<UhCeDeeTZHU3^VlE*I~Gw zVHi4s;Y?tt|8Fs@AHi_D@m}MR5e(k~h8KWg&IpFHOua)xLthWQGIV0dOIjtZlvYUd zr18=?sY<GlilsuyBBeV&biUzy-TAEZe&@a~zx?vjm!Exk{>yj1Jp1MDFK_*F+NEDE zeSc}yrT$C87tSxPd~x}U^Itss#nWH-d_MG<^jXYj(VvBU7WA3sGvCj=K2u(lF8=%C z<%^$PeCy)t7tdaN`Qqt|Codkkc>l$_FWz-gym-gOZ5KCOTz1iXG4`V2qUxgJqHqx` z2 at 3xI`%lGtPy~VKjkprbTlsJJ+jaT7FZoFUTALDFt%erj&Us*^^5VQ9-F>0S`EwdB zfUWYuoEG{PmFeM_4Uv!*QJkKO=3+PlW{#0FVeOCO;xVHVG4GS1iKJrYr(@PzAnUA< zCw8nR*^qj<kQDitj|Gs0MO-oDOet3e=~MxIri!bEY^;S88jo37&rN`|nFvWe2`g_i zq+}~3$YjXDDUgHHxap8)99E%?+-B}pZYQ^wyPG@29pnyk4{-N$4{{H4N4X>1G42t} z^yAzK?lJCh?n$g~r?{sv-=F86<6huSgGdV?$!BprJP*C%K1j1Z-Ww937j<vq_VXd! zLOz0z<YseAWD1gv`0nRcka^rg+_R90Gq?d>ON#k0z~^u)d4cQX)^WFRdq^M&qR$W~ zk;rf43ik{@nR}V!@NT>(uj1A89#6QX<SL<ac0IS1yP3O<+rr(>ZRhUbwsE^T5%68y z0q!pDPVOgCN@}_Jq>@yTYHk at BM`{SP$rD`C2`- at KL_&SbiR#6zCpbev$O%<qOMWYZ zt!&kQc!C6^ge0FJNqYUKsVCHl?JatJg`vEoSKp$a*_jm{9o-rp-I{!YPpWBXDKoT2 zC!Y|KdP4Lk3hP^<PZZ*gB8gTRZHQjh@>%#ht>Gxw;tc;sYq%l$gfg+^MET-YcB8cw z?I at GH+uM>)sFEIwA)A1eep7pU_z4cxsFNOxWuU?lkXw?*U!R?t%sowpZsBT9xYf5j zM##3-$I5AgC;ZThzy|zS(;5jB+FM$sQ$v!lS at EILNT8d8c?XpraHUFrP>QZO^i>b8 zI88L9i at ZcW=Q&=-_wwubd->x+qcBrAE}RvvC`uKbio=Rulp5tc<!R;H$_vULRJ<xg z)ucMEdQ0u59<N@fKA=9S{>9D5&E!_^*6+5$ZLiyLx4*jwySKS-b^p-)FCK0l5gu6{ zO&$Xt8$9-V9Pv2q at uA08o}6c;=X%elJ%9E}@oM*a*Xw6*&fCj7&O6V0k at tS@&wWCC z8hsY|yySD)H`q79x7>H8?>65Dd at uU`%}?i7jaQ at JF27&>3;lcjSNVUVanrPD4r*T0 ze60B<z!6XxFfU+xz$<}VpkH8RU{~O>z<UFq2>e at +Pmm*MYtZGOE5WK at O|ULFA=nmN z96S)bEO<lkj^Mk4j|86zekJ(b;7_$0txlVuwP}mB<F#$t#oATcEm~1~P<veatoHK| zQ%FXLBcwW{DWo%GAmnVwdm$G?{u1(YC>QD#8WI{CnijeybZh9|(8Hl8LZ1(PBlO3x z;;`{yZDCzu3&K`~oeVo2_Ey-(VPA#)5OyV86|M=_g(rmD!i&Slhqr}yg)azS6TT_@ z?eGiX--KTdzZT&Z5g4J5NQ%gcD2=F(XpiWQSQN1)Vr#_Sh{F*lBA$=<D$*}9A~G&A zGqNzUHnJshX5_rc6_MK`pN>2m`CjD3$iGDX9C=OWrVG}^=u&h!x^i8EZmMpD?ttzg z-4nW(bZ_e}=)Tcij?zapMoo|Ej~a|xAGJN|K-2|&tUgVjr?1pE>Zj}b^@IBL`tAAy z`iJyS=wH&mt-qlEMt at m<Ejlc^KDs at +J9<&{n&_?3d!rwSJ{f&F`mN}XqrZy&A^J*; z3TwD7CLzWaQyeosrY)u`W+3LFnAc)Hjrn`b6@!N%)?hO<8U_tV3{M;0FkFcBh)s!Y zh at Ba`C3aWr1F<J!pN at Sg_Kn#0V!w_3A@-M8$yjWxHZ~Y%V!dBv+;2R{*3oy39~(b6 zerx=}6k%$^`o6%l%CyBKnhu(do4z$?n5)fO%<sho$K}PXi`x>nBkrNN6LC++`^AUE zm&UJ(e?CExkdV-luq)wg!sW#B#D>JFiM@%76W1kfN!*dRKk<RYlZk&x{5i>yRGu_G zsV%89Nlbbz>6c_a*(W(HIU_kId1i8d@`~gKl8+~Um=czfmXeoJn$nrlo3bEfS<2Rw zT`31r4yU}E8l0+2%}Q-Z?M>a3`b6rbG`F<zX<O5tPJ1ccBfT(vTl%T=)9K%4WMouk zG-k}p*qZTB#!DG*WW1MgA>&fU&l%S&d6rVkGRwV|Pc!wIwVBH at 4`*JonygLMPV0d6 znDuGvW$Ujt&gNwcw2ikdvu&{Lu-$DtVtd8*w(VovC7WdTvIpB^?Roa;_Qm!i_LKH! z?XTG1vY)sAkQI_upS3OPWY$|*Uu9j%)@0kV+p^bXZ_nPFeLVZeoWPuUIa_m%<h+*i zan6sqKDqkbq}-g`+T8Zs{@hi$J8}=_p2~eA_fqc9d2V^Syv)4uc{B5t<?YCODDQOM zg}lG#^Z60^nfbN(1Nj^B@6JD+e<A+|hmRx9;c!fKtZ?jgoOPUcTyp&2xK`j%@NLnW z;wvR?CAyNNlERYlCG91>C4(g!N)DI2Rq|DdRGL)UP<o{Fa+#xSUfJ=ougcTP8_NgE z#qzi5GC^%`ex`B~G*UH}@)+S#^G>Q29{<RAOr^Y-cT(W-lY2~{U?l~gRI5C%<((uH zZt;)yH%9wMm-2ckmh6?fm6NU<EmcrmnJ4z4%Y3JD3s(OCtW+7nRD;d5X#!P-7?U;A zX2~FLCFX?e2(i7`nxEU&?8sMc$uGIKSh%$~|7zN@`i9(W>QUjjK;ao-gYp?@4bkw3 z$WPk?9K?bu1J&e4z<rUa;U?91_nO2EhacG-nG_MH9Pd$^fGgpzX>l>0fuz8cQIi%E zDt(JfRj7Qa52tNmRTZ`<%R!4M;A{yn2!;TI9p89c1ol=N1Pk7PlZrptE-v1ayfJX2 zZByW;I5F-b-uPw3J9$s?pUC%Q&$U%#l1TQ5Bt<$;-=fqbN=L~gbPcT(4jmFolplje zzR+Vh;_pKuNno%xz~4dgNv2@1rVy(I0Dp4|1=#FXOOV3fkeMQw{k1w;SfwWGatqTE zPZdtuEpC2B?5LSJZu;J(A~B1+Z)8q<!$9BhmBM_oI$(C4E_QsdbXD>So>!uK#krs= z;N-4`>ArlaC2scw-L7YYYpRUWi#kJ|DON3|CS-lMTDR^GAsn<gtbxKd<r;j4LPtww zxVi??Ay_hWf-2J#qY4bp2+$HUaAHGz{T*T3hFJqEqb)_b;W@48I(IQ;d`ilA+Zt!s z8uHG6x<%BzcWqdkH!a)fW3tye#<i7}wv{SgsJ$9htN2g1WO{xlY*gL>T{9WlAZHZR zGC^}mhE|nGVoac60imBZyA8l#N<d!{VAl#-HHlH-Qfp_VHyQZ;Zz}UrR!r6`+Aw2} zX5WX~2UgfEg)IR~cNb;d^jyWZ#k1B+=d&^!TS7B(^Z!g9t;p at l*Ug@j(EsZFx>H;G z-`yM>ce^P;Tx_;4w=b0Lemz5a>cw(pV0mKj#68U+ll;uqBJcnqNy0Py0ON1&cg#$l zj!20xDeBy76D;|@jDI!W!~Bb91BPxA0#s^n4 at UwuT6GG~Tm3Y4t%@fPzqfyC<Bla| zzdlyK<9+`8SJF+(7MDEsDN%1C0(oWLs&;8L<w3%29J-0$h&sG+;m51ZHjOotH`{|X z{Km^Cr|y66{T=m>{mP%0ewDIrTfB^H{qj??wtdw+$$@9h+_%C)g&(wWj?meNflnp$ z(~K!zK)|%tOwiMk5k>qnZJ4nBc1-+WEy{xI{3Rb<pw_J2zaJlgI-mW&X(JWtc&(S` zZ&){e^Yqfjs}+&j-^dhASX_`#)CV6#`2@v<X+Er1$A@@%qVNQwib_g~lCDfZIZv;U zcs2Es65w-}&@KD~KHx}Dw7<bW+8XU|@fW(K6QXp2)QkKfQQA)CiKJdS0XmwbSNUn& zc}hp4pVI6A+6;G~%uka(_D!D_lbk$pRb!F#K#X0}kd)=&U0}{_f`CF>ytmNJAHr;= zZCQoxYj2@;s$ct(oS`GA_ifA|cUU}HP7g~a8TLvEtdDls8GYPBZWSh&!D<Mym<?8o z*@6jfu^QBNOOU~;#ZNQtFc|xx1z0Tu@gqnsKp(ayLQvqjAX*~GVz+8(omzmjc-lM) zx6q$<tJyXxh~cv36>dE5;1#eQ1VQ1&6D99KKL904qErY3FoEZlO2`{RKYZvNTAUC; zp#)D61tBvh7CIUm3Jc=Ae0Y_YO68+KmAso9 at 8QNi=ml+!_rayY#|_u4ghIh?t5j~N z5r1CvOU3UQoHQvfw at K#Hc|>2}<fY;RvJ*fA?1B`K`)hnO7P!Q9$54hrScGWA3YA zHNwA-+3X*vC8mHtA0_xvZMK6Y9ZE}v)~;0tt4XjWgK|1~XI_^ZS(ElRRX|i$&79IF z+UJ%miB@TFF;M#4<hhRdVf&?<Gndp%PE%Z!q%Y>o;7gA>zihtk;EE;VQcpej(8;@h zs9v^f;ryDEqYplMbe+Tb%F}a+_t$NV4=-a;S<Y~RY&ZC;&BV?x7hklI7+!hDmAQ)C zn{vqQ%$z3hX>&p$%Ibl;MemOpg2hc84VH2cJCTJFM3_nq_9SODq_rSHdb&u*M>}gH zOa00C#F)+w&7|7MElE3-cSzsGCBBp^rA(eJK3~5+gnXK}Kp$6Yx0mMh41j0*K&w== zWu<M&Qc%rS6CVLhur)`=Lq7Uj{U~2$ICzN6F^z9In4MHxu1Tw1Uax7rbCwuan6J~E zIrGrNQMtxoQByD@aI5JF<sApbc>8_vg@={~R<2&%mQ}rPwx!1CPl^MHr<hul=NEr4 zH#2h<0xNXw1;qphaN?;XawpMBV(|wr)5*(ElFpHINy&#Soj6gHlnR6M1G{kk+ALAJ zmrO^08d-k^VWDxvKuHt;x(QUm>v+g?)+>|Qt|Nf3zNGNv<KXDN$Q_k@wlvOISD|U% zI3XdmtaD;o>C{sRYyB77N|Oq+b(;8kA-1J;0l!m}4rkWheE+n(`A4Sa_%EB)Qq>aT zSka!BJ-aiP;Xh2%AowC62?7}bSz{(p&W8`cI;o)R4X#X9Ebw&wfygH8ZX|r61g&Lh zvg+eCv!=4IGa6GxLrGTDlZQ3?ULvRWYnI$&+TvH=ps`Pg3#cdG<z;nFv0Eq4%*i{t zBe!!l+3#y8o3U?t-noDM<Bz=IHD^t0jwvR8#?;iNVoGDeXk4fqq;k;`MgkxegVY{G zIGy#;VdwY!w;@k{nM)G+aimwYWlDq22c-|_bHWI}_;cxyU6f~RU<leTx at 78jBjwg$ z@T?C{dzVlA?6SVXMgT+NLx&+4SL``*Ca`#7eYqxgvR{;g%uY|-oYS$qM6>g?9cIS^ z8#IO8lTxLR0_`9~Z>wfX*Gyqidv;}0<CNN3&qS;Fv&y_BaSNn>W=^}aGxPlyGl|=Z z*p)ezyN*Uzu9mD>Q)gvmE?>QFF!SLpz#YD37q4rW)?#H?15?-V2r(%FRR`!`1o>aj zDZo`_;P=g&s5ujxUuyKE3~@2OacX7iQwKHoJ)Kf9ttDBrm=4K=dQEeG-6G*&%`Kay zA8Q(x73)I^R!Doag`J1GYd$*n#jDQaGw+(}(6SNPzHJ$lJ#J_SUThmbO=0pgGYzZF zPffq!t)chWnk4+)bx-f7qN@`f!=d+uLd6&GtZ at M($ejd{HvU`Zr at YA-#;@RKoRRXR zyfgd^*7pojhIMc^Mi4$Bx;hQ7P=f8+Tf%Npl*+ME-B>5Fy0bdNQ0J$LFPwK(I`29| z&XTjN&c2~rne2$dTE<aziVkTC9d{<0C?PS{z66|M2nbZgn5eEFOxCsBoX7aPw}pu0 z*}FCOJ~MS~miT66e`R@=y@Fg?H*m13tiq6Ii|QW7?|$N`ofV4)50=-?ly0-sRCm`@ z)3G#SESn)CeBkMWO<}Y9W42=~73NF_<W-7-a$1m|1`><dwLV}x-anJ-q0pcWR_G=C zjN~Nc)MP7Bd1>{RLTxk4rt(o~g at uOk^RvVDE=wtx9M+qAc+M-|2IaQzoAKC33Nb0; zS0WV_k*KaVi#0ziOns?%VOMTSMN))E at 3BYQq<=}r6cJ&)c?slJ(%b#?^S3m<c(+rZ z5X@-Rg2_|FWE#AxB-)c0h=mx;(Qvf6t-rXB*jEtSKAUt3i@=m?fpkgu4bNe|;Ug9E zGyrx8rE-Q&37V>zx at w5_hk8N@?vH6GP=2OAtmJTPO1-tn6kfIbkz>+sJ|)CjV)^K! zp0<qIC5H~(G+sH#dj%&K%y3kQwNuXTI{1uo@aosQiknh3&UdE?4KwZ1in1x-g$~d- z4LGM$y(9)2C1!IpWS0`MimnbSI(Y;PvqJ!h73909^p>v5G}5KFO)0QOhN_i2yyGnC zI(^S<xA4Te%qYL`B!kI&M_@^DgFQ52${ynLcnL4+lge$ix)5DqbE4lB=}Jk}rbA2W zmD>1*te8owicE+*)42;eWP=VGYWqQ^Vkw8Y2{}p(=(9sXIgbe0p$Yi~H3bIgZqiw^ zc)TsbQ`+_R+ezigdOvk~yP)n~`o^a2Jd>AlFm3YuUAK{qSD#3p(79r+qx-QBn$Zr{ zL$u?Pi^LyGhwDuUU*JsSKXSfIzUJR>p4+1w6r~kZwPVlJvS(<7oGw!ADMG}N;_H=3 za_Lc;rb55;p#uMZAS-2RYqXFW|3u&aM%jXCf%g&nzc+9EpQ$>*K{OO;xu2A;ApfI5 zL749riaM|s$zDi<Zsnl!QGUXeP5d3KJYAEA=^|6b3ffqOLg@mDbB2*P=>kY{^$Af_ z)KWQ81Kw#w{V=8pmTtDzr6@)gUMqSgG#-P9x3}dcgn24<coh|D5*<-KAEKITbMJpO zBr!2NN)uyEH5Zkcg*#}SP!EK98D}U2{>0*rS)}&w7s+iWq#b1SgVI~`f%YK3$$9kJ zXZ)6 at C1`)HzEa$g2VtaxW06Nw+=Ef1_rp-{8vOLaS{Psnr`A5^rzZ)UweQI%_s!Z< ze4_D0F)EJczqqnVxYL=%zj1Art6oY&j60<vCZE57fPx#zgW at KsO*weYBz%e|J^}yI zxs;8$6u@S2P$19N$Kk=(V}6a9O_*mUAv>*dR!h34Yf5>ME@{7)Ya)4O)+L6yb at 9%! z$EBb4Oo<#Bba7<<OjvwqQ&kyJ{^puH6i;8+>@tVClp)?=9bUfufj8+Jzqr$)uexo< zz@!NaKH}FoS1AW|Rhv2|m(1<3iw&KV2gXS@kd*ajJnK&|qA?uRw1#DY-E7y|pe>pL z)Ml#SjC9dI$WKi^80kW2(2@<UIbDBSKP4-7Oea?<u8#D<{kzgfc9Lk?iz|CdPmW3q zXMdSA{c(mzE5>dz8-qyTG141>$_4?F(uY at rNeuqv<H7dHOTQDzO2@2eMdr|~-fE&h zEPZ`c2x)I^>uHc|*Z!g$)TGobscW-$v=@6j7t{VT{wc)Rm0;{F;2)R1V>XUxHPnVR zWSWOnpkS?1_Que at GnWli^v0X33Z*NJtuO2dU-49oujn3oU-*RXI16!`z2M><n(0r> z6ZfuMJxNz{Kz~c(jtRQmA1#=d-kcvbxV%*tQyaF?`sew&T_5+I>}c8nzAHzc>D-c6 zayo4>m!z|zbe2v*o#LxuxdgoBwF5o at rgB-qqQhz@W&xDTsk20=FbAI!$%KU+9Sara ztDhV@{lX&(Gummw^W|s<`UO;Q%nL#`O6T@{erccdPN?#Zt3N7yuFSn*{s^=j{t1{r zt6fuP51mJt4PB6&S;BtEluJ%Mo|}q(<e`4ZAWUWY6ehGMnM$IhFYkNz-TS03Nff^M z=lSO)ck*v%sk4;)TcYa$)l<G>@(4*!N=O2VoIut|7w-DwM|Vk|kWk)7xS2|VYb%{U zvhrw)$^bka09}_r4p^CL^CoYHx?~5 at ZoPVB&sIefs)rvSU}zTqjVgfAcvzx8Omd}E z^1gEAl~tnZxGYD<*rEY;qMo;vPuZ#*beW>S^AzgYDAyA}1t#97$or@!S9(<y@LPkZ zT*vw|C>$1Iz+<4bJ5hVWcgA~@pcS8dvVsJ`h@0I=3Z$1C3DUM`-&=*kp?^X<;FQ$Q zQ0NTvXKn`EyC|IJPb;6GIdaqwLme<in7h>3qU9g)Pm9n7OJ|eyk at mt^MNMExWSalK zjSg#!p5}Ei{a6tSoeG>z4V6(%ow at dcsOF@mngCW?s%6P4w95|7s80- at F<n!=^dn9E z^4c_w*xaufe@jzZ+N2HRKWdtzX{httV!VfZ8<UZ$8J{@$mZYb7iz&aQ&FV}~FLujl zn3*}=($iv}*x6t(=d{%h<AX5^LfqHI!7h^xa|_xXwWFBh6PIQzG8TVWP75Y5gK5No zI-{9?PZi9jfk~n`fgIiVAhnW?<jiQdTH9yjkh9C)Sa;;yzpB!dgVGEK8+%5FNJR+K zG{zb%@TNt>pGi9plTA;ZY~*9s8XBG0=(nvvx}w?jYSlgSHN-1-ySsk8u+BPpM$VCq z51P}`UXWfpvhIzgXOkk0 at T4-G#x+0X{splqI<|x8pO&sERw_ps&9S=tzJCiQpl1tM z!~7()HMWCfi-X*CWs}jt4ZVuimyNcIan>=q%I(@*owHIc87dtWo at UqhMxt4a?6mRT z+q0w}psvg2zC6kp**Q(*+ud!;($nJ3eJGMu*0z>>6C2sTiWtp?&WV&~G5 at HaL5GnX zHW%qy467dAD!EN6N%?(_L``}YNtNCgUwzdh#yZ`hsazKothg`MmJ{t1ul*!=i%zYz z!fviEOP2jTebQ>!L7}jiq7lRAlr9@&n0u`PmZ#wh44#R(eeKNmi=Jw2mY^6}p{9Jq zJipWkWc~tq1;a!9zL3QFG;lZU(BidcH03K-RA?$ztdzDF)zzKfsF~AI-JlDP`)Ivp zTx~*XRgGrCrisb#ZPqm3S{K*s5n~RoG6p6$l;sFN%q`2RTCt)kt8DJI=L%bkJI8l* zXRUp+GFuF&tj^l<exkutVbq6b)U_rA&cEN at G$kpp8uX)jDV6159j31_3<b<ce at 0Co z7=KXRT6<k{52KPc#Ri@(Yj at 2t{#<86-&Q;9Nn|H*{5Q%Cer8g|c at 3p#RNPP0&g4Fk zZs&(w@Ruo^z`}R6osY4javJ<dw6fmSCz6L3Nzah3N2RalDet)U60e#r&3Eo4f8Qh> zpwG$e$TAmN7ep19`4s7V+j;)&w_$VWsIrK1Xhmqo_?hq!j5zekLc6DBeN>F}k=@BO z8Ax7yjwq&Pdh}V#>paqG5+gkoVuao!F$<r<&GSl~=?X5is3;=Ck{{!D?T<pd#XL~% zOsC%?{Ygc8E?NftpJ=JzN^T@2ox@D8gRBTfE()_ts9`iLAHhRLmjIES4AjApA_&>3 z71LW&3=f?!*Kceww{DscFWqmp*Trcdwd at 91l;NHp5sBq|M*E^CdrLz|;KjC?N4C_B z-*oKn`P~DKwu1S61 at R^AQ!?Ar+uDlbnJmE^pndx9WF;l4+|dz<`FmNy<jAnN{GF`4 zmY`f~bhzZ@@7w&(@{10+|NkrbWxTSQJTOLn$#WqEebvZwfpu%xeJ0O?B|9{)*KE)@ z@-+D?>lEBdQ7kn?-|`j|4$7C~sNBW#)aN-Yi*)37dK*}f`KX}l(Q%^-9 at ZO3sM+nB z=`(~#AF|Y5|Ju&9?t^t=UrXBfoAmzDW at A=_cUk`en`ZvKmHfH7Sw+cShD7C~Ve{%! zRvhR*G`W6#sUsyU)%_7oL5VHEtGGK)H)Y3U8852Sgv&D02#<!vM*&`fm<oW1M9>l9 zQUH-He1x$`5S2;UmDUhV$K6ry{V_-kBoWeQ`rMS2DM`7L)AjDRcKGp2L~@@rO?0kx zi_Dh!p7onU^Z|Z5*RqnrGo3IPeM78+-zSPQ7ix;<PPK{6Et=HsLIrn?YndNgw__In zsB^+#`=Y%zR@Y3_Wmh1XepENyEuA~?&O3KW?}jQuodx{MSHA<TBQUpDp&YfX#_EO} zdiUlZn|>^RZ||~q-cfMQ<GkAW8{cq^<9nQY at LUI;%f@pz=!P96So+)1mtQ_A{gr6( zjm#TPx{J(^kf=^NrMnq!6Q$dQDm)uZbtxZsf|$b>ntK6(niwEdNmqSs_Okfkx?3W* zc<1S2ioKGgt%r!`ZUuLBRnfH6aOJG>tf-V4+Zkbxv!CAue6X$vWvCPSqn%K1Db*mG zd4r_kpK<<~4|i(j^S_BJoXosN`8JpSXaudPOoLYxyyH*T_#he+^@qEHIn-_V2>}$u z_czVSu~~Ebn+FHQGtpJ)I$c%68F5g#vbk2eT(UmddZ1alT-z)jzm at n|XH|(^uo7>D z7D~vvp~v|U<*QU5q~l7~iK6n=-+o}U7{((OdN1>9$_OxwM%#yv7HF6NI4_=QsM6^w zC!*=W=Kfr(EoV;Cpeo?_t<vTEE^*r==`tE^t|flW2dv5KONbv(LVrv6Um(LHs4Wej za1@~o1)YMqI8q5}j$zS?eBb(tnlL}#2)pk#-G(OLywaMmNZ<Of?GYOl5xE7yX-O%Q zd$J3)lM^~;G2f?s=fxY^H_QwaN%3twV#XXq9oQgIaVXUKDc7>-tLG7Hqt2mK7; z9cG#_ueui6DpO;%e88^oe&cqJ!1a~YOi@#5b_GmdKy@{(XLu-ewYMschV4mRk`x^P zOFu3-?S;;;p7bwvQl*XTW_sKsG!FrBKJflr%+m^7(4IlJh@$L3opS0iou^{Ygw8|# z at FiwBn#@9bOo7IrL(8Iai(hIT(3~+kNWb)fW|Db|q0BQZG^8SM!KCe>k)@gnkNAu= zDKYi>@{J)cFNxG>{9oL!F;AFQWmSkoqj+fIOXm3M<zi)N%DUUM$tq3Y^mae5RCRPj z(5j&1bJJ6*O2q8g#Ik7fp~wBwYj2sP(lwwPm^is)g)mKVFFcFzM!~11*2*S33z0$M zF;gB`vN#zQvg?#6As}r<?~3tFe$C at n^sY?D#q#<|eob}Ddsn1MulVKn_2=QEPlyPa zgaRuvGEk~^Qpf~cEVo)wYFzU`PDo*QcVS4*0L6#Snd!_%!@5j@Ku^5j|8l)#8!reB zr%#Aky0BaNBZ-&aE|c#~agO-jd-zBiw)~QidonsVsI^QNBi9TOMZU#3S*39<XSC=W zY8Fyek?@h!qTCIAHOrAPIWWdX)#b=y>w_$$kw-AbB_IcL7{Op7R4-j#si|DPbh7`Z z_qRA|&o1+ue#o#TuszAo(R)h)fYmv9m9Gu at H62RW6x@~wx@{1a&nrr;?CGvbEt)63 zwl;NFZ_4UY?dlgdHsz#E=qb=<&pop~wRV>|eO;+``I%+yj?}t=9Iaz19oHV^S|Lp} z3z at b-=-X&?S26mNR1_tr6qh8X@(IO>$;HLV396X(vXZp)!otqt($usfh&$MUYlN)| zJ at dIj>QiS5HU6Nj^VIS(Lat#sLKqEBXd=k&eN06VkHIk=g{`^$$9uaU8OY0 at cXamV zbF-zQJx3niTeqq<u4MO|Ih!^P%rBi0Y0a|Pvg{Ev%J~T`_ph%XzwUv_?GLQ2UHje# zXK&m3dQQiZqPE?g&ZoD2xV5r9vLG$BFsdCf00B0|&)}EG at JquiRw$Tk$sV@ZpsdOk z3{n}u#)#^SwO<8lH<RVot|KWWk=_$4kJQxr&Wqeq`hoA(`^B;*Ut9O&tbpXwKC%Ta zQB8m9!pQh2kHolIa=36(d0EbV`}R8bn$z9%`Mo9P=(5Tz1LLC(u1P2r8sRsf83ckg zKq~|VC|%$D*%x12^YFvYi|nh3IM$MaTIuU#>6_Xyzu8>K;64^okxKxdCHw|(@nrB$ z(YcR5Bw4U4ig2Iv-tdcz<Ql+3664A>P>hV5JEU;}@}Q|ic5$jQP=^aPVi-%69pgx? zd^+<DK?dXXBg~USIZ@$J;W^S@%XfFtK{JnAS-<O%+s<ay$NG+&IA>+xMt)(=oY4{P zJvNZbPcrz2dxYK8ws%Hu=U`#+l4<rAn<7#ZwNb66#p55{^i|9K8|v$Bez2|mfz`FO zYwt&QVS9W_-s6w*U2qYW4_&(!%4m6?VCH{Qt^proB}ceaFK3GdgYl4HWTT-E{<p)@ z<-_-H+;UL*u%@O)tnrC*C>mEidVKYoW5<G<)-<;igr}uK38C{MgNqir6~BRB=-5zh zVFoC5%Fv7u#KCOCSs_nBJp4E&F$B*O;?5WFuR-p!jIiO1AvrJ))q=(4Aq!-8$Y&ls zi=$&sc?S|+>Wzvy<?mPJ5u@|i5;a44$I6MprupXg{p{kCQ{&zbaQFu21O(@m(?AyY z7N5+&r@RxnAskyWsV&ad6&CTKqK0OH0SNgluiy9qo9OlOh|TU%xy|WbGghRH-?GrF z{XVfg+H3agx%0H?##p5?)8T8h8rDwLR+i;%^IR+L>5eN}>p4@*iP7p|LD06Hiu?Jy zRqH?>4*n-Cx$|x!+^x8O0qI-7?w3oq3DwYnjQB^Tpqa*cX^ITU$8d7})bTVx#P4hC zf!D<^a$AVE2v1}B6sxFIdMmaoywz@blZuNM-8wm?`ldNh7H^w>&y-bGe&Mwl0XqL! zpA>I%upan~!z52to?*T$IW}uy4g;t-=}Fb)amVJ9SH<%p|Cl<y{`QXYdtSJbuS~x3 z0UN6vzLlRUtfKnbhy={wr#2ufW#THqJlNEfm)n9iu1aD2703^&6H^6<rq!w8IHRHt zsZW?V{LF^<dLL2oa#qvC*sTMdrHzJK=|^+&3%)^lhGRKxDZ$gab3s8uviY at qEBJ%1 zG?=n#a{=L7#kREL3RuUN`Ei!)=lD^%^#te~AV;&ni86OP1gHeD)VJkga6*L?|HM)0 z!=s5slgGD=pInqE{n%Au@M(Ui*~dU-6F=_H-+lMz#gEN$1Ozx{J@$Uy?C!inJ9i$+ z>z)msZ-P9^9+pSMD%b;p1oQAWH}voth$4O*`y!{4No1|`@>=PYS`sn(8x&M<!ceNP zSe1+#Rq(T5ej3#l0m(PU0G6A9h1wtt_xZ)@>S&shL+1Q+OLVmO)wAN{)ED`GtyN}G zzvw>lfMgXPeN<I<?H4}PVfizmG!dk)g<Da-5%Cek8!?+)j1SdGrH1doK0L)RNHIQ2 zHO(&!aJ5L@)lX^mn6n@{<|z-~t5SAIRNj;Reo+Y?o*Hj=cfp-6r|3M+&>%a?#x3ol z6YvAiFmdW&#W^|w6Lq{=>7xn|6ipOIRq`lqV0hN|64;Ab$R&i<7vNBgotAP^Nj|aA zdC44jz!5sbuy&v(Y70eo46XcmcVlvWibwOZ^jdvtagN@z=jMI<1kcb|f3-cKj5LJA z#A-e4VSH?LwUN=%EYni?geoB26t%k6vof!2zGunooLJ8p)A*FocvFz4#lAZ(z|+T^ zes$?uWwvz;v6+nR8~P0M>1*iHF~WZ3z)Mu_wt)CWDyN@`R2(jVCkS=|D=Wi?0I%yQ z`W^fT;H9MWckqt at ul?EaJNPj_6W2>Beh0r3<*P~IFdW<kzqMe6pG(1Jq`yin5M=n; zp$W1SATWZzK_I86dZ$W%f7EBoY_jCb<Hx_0Hb2%SRFFH}CP}Ycdq#2Rv}FoWnk72_ z4B_Ccqy6lV0%<H8cr5d{g7)BRl4*kW3RSe96^Cd);c31BUf2D5_`|gST*;X7J**zU zv8#vDWw;*d_jJ{NnAVfzs(%zN)AxEftDn&Ne+LI0Y5fcidL^KK8W#`7E-9*^(Xcq& z1vLDnMA(a_O<6KD{xqJb)I`~9=9WpX_4JUO(m6GDeSk{Zb@<LZ56|hD$zQBkdgqFj z6RrHiBA=a5)-$ke1$kDQB0B#8hfzGy{biIcsj7=?oXda*<|?BX^MlEBe3<q#<2ty6 z@^u4roUeyJMCqt1{vG@Rt^dmJ>v at pUceFg@_aY=6nuK%IPXJ30Y78>Ikd44U+yCQ_ z{r~s}`xMS~4-Iu+xzf!IarkEVdcua=4t`KU22=iEu at 4o+v*HlNGs}g)3}^Fz at xk?Q z-~&FOcn-sRP;Z(F{Q at 7*@f(J-conO^u7}p2N9)J<F~6=#fxK_%>TFeq><DEeC#U`; z67unxNdq5=;zy+Qse>Q!ZeM at VHPLyC^H-JPd+D(+j=h6=;1iQpF#kGyS^+L^I`lfE zV$n~!R at +B}6HORFCLfhfxIw+DeX>J;>zxV4wfApyTTsy*VRfUycp611)y!|W13rFH zQJG0S`%fG+Pq-y^)_c3|f3>tfj7H~orPP#~#O7({ml`^*P?!7&J=5W1t{8fIXd!xB zItCsGcpVoy)O$T#xf<|NE^sLH2Ke28*K%${>g(X8%4Xz5^>TsIkJrIdRR)Ypb>JH^ z6bK1i7{+A{e14wTYghn+xhy=8!5ToV1ZuQEiJ{jP7BxfU%oQPU-8IncB?Jqo9T82| z%}OcAO)-^-=W|oc#l_~Nd~tVgPi^h&zFp#+S;gi=2Q;kSnwsvuUD96ifg|<Sqzt>6 zY<oUA-4^FOm5`Z{)VH~+YIEPM{d=CLSN)qRD>wB^$0_Y3Z}>MtsI2n+34JI>4 at PN< z95K-J+)y{}RE~isFq$R~^<NKHu4OdU4uxL_uTZu!+G(YqN8qQxkl(26tp2k@O97YT z0u=r?m6_GQXsG8p_&2UGb7x~Fmxs0pX(Nw~I~yCoDX(J;QkCby>EKfM8?NI>C_OaZ zWn#_;a4U5>XpmGunOF9w|K4AHMpI(XZ2Bqd^_<G-)0+O7^~RqN=-c&bLc>P#rfwq( zgz0EjCd-J_&R;ZhOG82aqWYPgTN<Qyq^mEV`xnAux at iyGTqDyFc&94wML%nCGKM?) zIf21t*=A#Od0}V}@P;vPO0PO@8l%@3xQi}cj4os0jF*Qb#>=DdJQuyZ7`*@o&t`Dz zVXvg%mp6 at 7z*@1&x-8{HB-BwqBF&{|iRS)=$!QKpT5 at 4QN^((QdjYiuNqIwA#rTSA zuO^n3$D2z_QB&Q}QK4Q at 1AlA?9R+vhsB=*NHlk#*tBjavUNqze(`$yELCpQF<wfbW z_)IxO at hS^h_!CFM$#pG7a!6xZb-}upau(L;uH{cVKaY~*6xBq<A{Wiod|d3CHG5V) zBZoH9=Tz at o_d1Jiz{4;iHxe1GpBtJ3TDOdW$1qw~56!$D?xHovXf2m#a at a*vj?q+x zGvBZ(nAKA_)Pj22W!^;mgs!6=Oe3d)z~7I`1f`rb4n-5uWmwC|ZXcNX at yAmK_$-!d zRv2?rvnVz#&S;%ol};8f*htoswX^0On|Aj=V|tWlq12-KP3pA;m(PEl^@FwV!b_H0 zXdnFscF@BHA-Bp&BCs0P!{j-m1is~vb5G-rEJx+?(5tXlm##C{mtTqUm5`%>qvfRz zp%d$!nbcr+gA(f=o?x=3PUuvG%J89|0GI2hJfEiK0I$PdVY)&?V{(m8q$-)g%ZKIy z4(S9qD^J%wQfhXU9xd-0pA0rWW6R%#@>OQ4N1*mG<>lC928&IOE&n3Qm;HzOMc`8g zzbsuh<nnY~%~qYJ<=OgdcGd5Le!Z at G4&^6t6-4icUEz;#IqrqD4=!B_b?lIN0%fOy zCxTqDz5gVyn0FEzHK1qx?{RwSx)f7W|EFmEr($xlc(rCUU{7g|z7(o%LSM?cIAAxd zTgtqlMqkP~t@JGJxYh$!p6Vu*9M#3IFHhIlN=`czd_#Gvi&tHz)5`U4Lis9`Pa7>S zby7bq%Gdm?0>_NhFHN9vH41<4Uvg=AOc_2e0bceW>fej{%TWL5`Xuub(}6Q6Z)05K zdT5+qxdr7*hb|7q;tt$^BfR$j>aWbm|6O^Ce<jLC-BA7j%2#DjUHba^=^Uv-`Rvj1 z&Qr4OK$>$sZ!E8R0Ofy!JpFj2JRPr8)u at e-IE+8wSt_P84pN=5hQhHfLKkFwCNP@^ z8A6aYG%JEd%w^W1nVEE3CFLIEXW~=ph8?fJ`i!DNyc2)o)n|~YNmyMpPe9MnJONiN zaHQK{n(A)M7oe4yrB80i8c;k*dGOa8qTQggY_p8&{=>G(V&Jb+%In%Swyw!~fLxyO zEunlnT%PF}N?E44GnqEFJmXtJ`F6Pc6Cl#Rm9k88XEM!IUg{a<!{@xmls_f)u>1z_ zA%oXaGLPWT_<Yp10?imc3KrXgul0sJ1q47vI9WbKIq<o#0JMeogJf9gl7JN%;RGwS zPT6W{<%PUIUvU0%;DAtlS42XdC9;Jac!s~tl@}{I7fxJNp6D&g_+&}<3BM}A|L}DE z#%VB<YS$6qSp&J#DD%%U%Kz{MGW?OBA#8HV(l at BAqxl&q-|u{x=EZ`an+cuOH0zbZ z`v6DY28Ht!K7JUEeiSJ<Jik^hhwDPwHnCCL1koS{XLMmU9kqeWab5;j$T^OV)Gf<^ zW)E8T$TQBDL3 at S^gX5XeHk5zBT$wbqj7vjb@dR+3hqmXbsLYsjc@>~_&yFoW2-+;= zE)HFkTX2;hK>dId_zGN45cs^&2aJt3JW~i9!TDy#ifz;?<y_zR%qb$7ohi-QvxmIl zyDdiWb(+_0+f9)pILUz%0`aUrF0^XbsL~2Bvi|&~%xg4Z<qdq1P9b{RWN9iitG>!@ zllel?*+J=y=Tk at MG-?yea2@(w$7K#pM;~N;l8y`JC><9@qhTAG!I}J&;V3;qyHu=M zRDRNVF;bqkOZf+Ij>%6}KhDYGgw-mv%Xvs|aI-+83H7v}ec&IOgB8tWgPaaRx4Mkl zhcp-s`!i-Vo#?)&53aVXsadSETO8xy#Z8 at xtfGqPFCDr1!uD6EPBjfoP?Rj_T)30Q zBv}2hv7lMd9HkMN^FKDgvdG4?A!ja)X4?e0n&H16$zPK7aVA$(RIX&W=)~YIx#Gp- z%BX&w&E`|3it_jM<z>0z#pH at yJ_+-0y_yHT$EDEqVYqxK;PX{}p!YZ~e5e6;P#*F- z4D_Zxt5BBP4I_pL1m=2yfS}Z+ at qU|Hc9IX%SVbbUu<Ov}zp~h}diU)U9I08Rl45gK zTE22HI9Qa*u*Xf*YHyz<Y@dDG&)4R~=Ge``_JquAG&y3^x<Pw_*-fzblNqw3b}Z7_ z<YWV=t|O+a>o!Ij3N9aVJ};RzNex(pP++f$J=AXP-zn{t_EHm<-V=<^!*aDdZ<h_% z%F4}se0ASu*<>XR&QsL7<*RWQ^Pv^UO_t}we!9-V&KTk26jcbD4~C&O+?mASt(XtU z;imIJpt5ARd?Db-^FjIMAy1Cpag}HFGhOfls%s9Hr}HgEo)1G#HXr2jEPq?Z$3XG1 zx$t3mKq_B`k9lYU?lduc<nm0H4W#v7SKeh$N3i-`<t1O5A5QJ{&#B%sQXX)o4{^)> zM&T$urv4Y`d=8}iI8q+`H9Vik)?Y4}X}&kD|4($Cv%BhPgHO{Wx0^(Ln#fUM^+N;3 z`oZYKH20D6sDF~`bM$x8&_|>Ma)CZEnosUac?jwU^|8vS$}F#(cuPNt(ti<OmS67N zCraT2j@8*BeU|#QjF2htJ{oa40sJSfqz^bx>`*`GTh3jg8(7ukzU(xO-sZU}YF#CA zm%Q&k<~fqS<VogAo4r4Xv?`)P9i_Fmc7*?1{6fxQ`a;BkUM?TqsE;|z)n8dpmGzIG znf@W$dJH~BZ@#{~tbcG!PK+%-syAO>UX~LalM`dh-;HsshF)6^U!PRS<-EbhKyI7) zp}03NHh6-;b3m^hY9*B|dDI_1+(s$j2UOHwHjXQ2{xVk^Qk`q9sCkz9f`-fY0p5vY zQJFo*>^8YPl|^e*_oMz&LjCzUu682;U**yr2(y#g7(TLJ@*Y;6YP`@z8C<qg$NJ;i z$CPJ&Zsvy`E|1!Vb%<G%r|F4!bbSYh31u#OJAv8TQ{;MBd8UIqsf at j@yqtpy9|_G( z9b2Bw8RzfnS2Ft#<*EHAmv`m(;hN4m!iB<iSPeeK+Qbra)B@zQK=%a>Cx_WtnjJl& z0P#zKL=>mw^WJ~@GG4K(uW(uH8Xs$Td@&z);R|o&&c5V^tO$3-1%owN`iFS!wyF1) z<kSbpRK^R)i4E`G6doVui8g6I?HAB1{gERFzapDbveyP7TyilZS9*nfxN~0LUa{y; zSvKL1;?<ou-F|0xLJ6Mz6r1#3QgzY%I_M<S3B!jy&~(>bO%JHLZd%&kzw{<bU|Mb) z%B^JO;I=WC?c^O4+W$b4%5ToHn{HZ$GJjJ(#XqVVhZTeEefS%BRf+gEd!EE9&HPE# zX?l;Su_yiHR!Q2bJid|mZDjQyRj%ZJQ1vkXIwchxBXok3w;Ffb7M6GQR4uUYRj#Zq znm(ziqX_00sv(Wa0p6i%p>a at -`lqn<lQfoemX&oXPj;4;c9vqzysCJezg;z1&Xdr% zN||WX6X%CqUG&i4;6o}awyD=D^B-Eg_ at Tv@Yi+h#CMXFpO6 at o+v66YrWhYnx9NIx@ zIqjr?X!)CRs~eYO_lP@v^+5rC;hoZL>4S|8c|Gy7clrkf>%Fz}Deaai9~5F#duUF2 z0JV=;)|!K$0ySH-<-9eTyLH>-F2Cl&fZ#|RwG!mF!moa at e{^$HkS5B{KPog}P!+q- z?yunk^dXHQOCrNHe%h!oObGD*kfe+jn&5|k4mcD%bOrehb=)P~qICM-X#W?C&0^Qs z(0Kf!t%TpIG>?qU|L6E)oO%>r!S;O(ar8i-JhF*b$@o~_&s$N{R)S2TBJx*PDp85D zIi;d(dO>k=MhU*CyPf+%@g4tX)g5fiU at EB*Ft%hcbp^>e`e6RgG06^7fLOD<IC*ke zLoe}Lsr+Mnkul6o$a~Hj5s}<mhSmN5$nXD`nEr$W7SqpbtJAZXe)M?6^pj4rn7(6j zQoyvSx#<``zCzfBy@7XPKejK4R=ml^tyS8_mkhD}S0nu0%J^G4&-fdAwyn;i{J_xD z^f}H~`j*K&*LeD=zynk^LSoZB!H`jqN_2h0HMI*=G{eGm4Vwmb+A*}tGrdaQLky{4 z^u&C%5Psvc&pu0R*gd>Qm~`!kNOulj_bjzqQ5TcXv at R-XN87@6BI*{d+n)$~6W)3L zf7_uLfHC|(>{4|9@&B|>QSubA$dADj)<3TMfgEgG;7P4oobP~Zx)%==u&^!E!aw$r z+~p*D-)FSz!eu-YhB9l|Gif*Um0C`td#crtY5~+<r14Vh--NP`eS^%Nr`{IPnJ1FX z(uVtaY_01SpOYRX6UZr=3`V9&_q_>TKwGlSLB|kQ0<K5KkUTy18uu*>f41JjR`7(( zfkK4SEzqjG$^I<SNuTg#NJ`U7m#~x1IpjPb-osDg1=L#(#GwPC9%ys8cGZJxtiZUo zL4TM9T~I|{VAn`>r+e#gKbN)JJoFbvH!ACB;04Apjdjt?9mqQBaKiBixco<c8jh|? z%NKXd$9Hynw#irIFT~Fj%C9}!kjoD^cPu%E6ts7eiW|x!`2o34Xsb}UgZk0zRulGq zM$^!xpBk>KXn#5FmF*Yz!JZ;Noc|MT{qYXcg&nPT?3K=kXF0;+axHo<y(QNizD$dQ zv2MSg%(n9h&I{Sn<~_HGq)VIf^`Y$9dmGbzd@>sMcH5=Xi-V=RL`DbNHnr{KxH#zm zMkDm0zBa(2!xQikgLe#F5L#53kjE{M&Wmwpkin_Grc^~!I2SoI1Mn3xobIbo(Rf4) z7f<66WEF#-!i4<BRnO0?o;3_k;||%()@aFttwDCsb}@HZyVzA3jPp!6g1xa+^+*w1 zM-tezh+ at -l0}VXb`G557FotcOfuq?{G@2BAb!-kd?2v|vf;+3*+NwK)c!lJpKi=M3 z(!WzYxO-<`@$BXY^->^7>bUsCsn0slUZ3=$Pz+l<gldeV2X2fcoy(zLgKM*w0?EIl zr@ol!Hs;2;rh&%125XCb+RSHs5dLK`VxN*Z6_zm1DfvZyDOIZ*OpEG7A{10q6clHb zYsKk>3mZDw_OFmcOMyLdRa at N3@x`W!x*U^HdK`2hTsP>M0iDPV7p{Gq{@Azq?SVJn zJRp55ttJl%pE!&77q6Kmh{xfWA@#w7yIiMBurQVEjJMMHGrHlH1`2I3ME8ws|8s4Z zbAG}1OB;x}u8u_rH{R^zCuPrQx6wU-t}S!IJK|UQUv0%^K)P4%(aVvaQG6L+fR32m zEZ3V35li78L1K)-!*YNTl4HJBEq|+Vg=t{Y%o%(4S{o9B+ImdYCBchQ*L0iieJ0mt z&w2a-Q*LKwrn|S$l2}=gKTXu9CHec0>kJ5=nw|W_9sb#i5|Wk|ZP*hK80P_3*RX%3 zo`V!qGB*tkP&<&6=sM6Qh~`QmZbwOjBjJ$5CaA4QNaFW3&cznrgqm#AyOx7fy3+4k zotd}!?fr1)tg0`n_Dii@H>s?}=+T{EeBqHmdv{Bw?qF<4(?Ckh+Wg*om$e2i?9hiT zwi=78tOg<mnDh)6dB0J}4ZfcLaX;FKSLf;pMZN6K!St17DE3f!!HR@ekVNZfdX<B> z(;ke3e<@Mr?NsV$WEUQknt1QX_~HbEb$n9LzW=nNDzi2rkS9m^z=S&TWNKq$p3yp` z`?=rmsj^R<otrm%s*TDGHm_k<4adHzJcq9oa`gk5e{r3GJ-g6tvEvv=czBX-q8&Uy zZU+Yo?~#*NR`D-7i<m5M<x8TjCG6U@X9m~skPMHf(vL%F<;bZ{Ea`ym$H;Uj>{Q{7 zB0*{&Owb$Hj&9IIeg}E0Vdc$9E{+OHN~yLbG>z|Hb7-6)ZB=JW+RPa*y=0x35Hz{l z<ef9Sspy@(e^OM0glj2(Wi3ifT2`Fdn41`_@k*J{y~$1J2{)z$=?d+m%%~1eoM_cg z+o=X^*jk6Y1ljK;@98X5WYaSq0&JG(4116YJ}zyV0q!n1#MNmAEHEl{Tf-n}*kw!0 zH6INBMt?A=C_(r<Ci{4Rn=`AWzM+YK#pxI~xv7DF)(dJ7oAXrWV$KDz{T)yesYjBg zDa)JactolpqS&?>mK*Cs_<jFzTP&OFRripiH|&a)7CWRi;p21v8r~E;Jh#|s2G~DJ z%yRYzj*p2VYBLqh^1PDIZNjk$NTKHUMNX?gy1{R+&mM@+jBl#haxbu`Us02$dGbVT zNs+nTBRqn~uHRR<p#<NpzWRr51|ED=QjRSmV9svQLz{$5VzxGXCoBN}>*OqUM4Zv# z6dIFfTXD6<AVctV`w0DM>a$=BF=yqL<>?K{jZ;dJlS<nglgv%!gkPH}J!;OZG5PzO zYpjMB{Nrk|k+AvpS>sY8J+&o0Pc3d+^muo1a6Azg-kCFNUU6H=!tOlM+Fmku7Tk*9 zOQtI-w$ZF>Hm)`dEOdlGwSgx1f)?tw_5spRFc3Mle`>?Aq`avmJv!m_>=xS>WzK6) zJzDbOOkPna>uXegW}|PBsF;P)`B3b_VDWYMAFVjR#dT;)Va$WiiQO|lZt5)Ut4t5~ z<cTYUj#KA?2>#!Nb5D}>7akVP)zen3l=&nZeA0osJ*d9SGMOBL)lW&S5`?+}c{P~C zB=2v7NzYG-F=SMm{H3Sn45ZbRB!!*aBknmV`kSgVgmts?ok#r}2l}(g+QWMfhF2~= zz2;Ntn at H&nGB=V$d`jMyGPC9kG{TYxeOl1&1gv3 at eF5}j!&G8{xYY(&2)?3ls*lN& zEaQf<!y02+o({>F8FJ(v>EBT?(-P*t5jwr;K4~@o<oCUQNLvw8?(8M!oXM-V{F=Hd zYz|45YFYhqzi=Qj8nfVdFs7y5&>L*>nmAp2TpaF#BJ|pV=r?Yoh`uj<9)%+fZ$un> zv at jOUz&juEhVJ9QR&N-)7MzTUAb^f0+{y|en`Zc#$vVd4CgVu>l8HXHvOG~48L50c zFW5IE%FiP%O;q?8BRzD<rralaolX%LnQTgr at bj3SpW7Q6JAT&;vdvM|7q6Zi-7;QU zP&P9mHIY|3fASBC^xZg)+_pWtRfu0unI|o*T2yTh^A$2=J-QciHr<scL1_L4Su?6% zjm_&ITNzxA34}8}&1Yx|%5PwB#(&KJ)WQW(|C6gcqlGGpl@A%}0DQY#o?~bEK*k}Z z8!}IhX~6ePHnZ#DJ<=+golfrq&q6tW_9OV)$Z^V=fcG#s(?6*XR)&Ljs2)EK`aeXS zk?y!;UU-`CN27?*{!DdX6vx76<>&iHgr=q)5<lVZmCjXEYszO?w2{dUn@%H!wN#ST zMaj}kX&TP1!0~qz(&}+o+S&;#Dw2K0F}ShZ%WnA0m=#I+m(nU2HWd8k-~+XM7$Vz> zhP{$1HO`cv$7azpomAwVtf^&L#&92Z-gOe9&OWKEENF*sf-x=BuL~z8+NIas-|-Di ziB0g`?w695oo0&-jm>MFvZQNSQ-<q6MEAJjwau}4%?Tlj_rE8*ogXTK;~KI|`7;XB zLVXpKUam7%V6WI1?xXf<(&+UVJ^vmToomn+*)F0{Ql7>^gquL$5KfQTN#&>ih+^w+ zKsvF3U`9c-nszE8lFQ;gK6rvDwznswc}&?${yBS6qar)AuhZeY%s=<g-S&=Yr3s3! z3Kn1efIsg%OS-!6o;sJdljCYf%_N(vN}9IGUSpc%4YMarluCAsFNniUy}+vKMpbj1 zIClaq`QiQSgJ^AfSpt8aj?plmV1;1&5 at LK<@LuK;mK at Kb`TSFcJU<V8qA^tFk(dcO zO?I;_##`ji<z$8ywC-$i at yA06QM&Z9ig+iqN6;JgkWeqje1D+4LACQ?eokfZiXaMq z%mr@;oy(EyfE<e;<XDjBDI9pxvlnQ-0qj|v|4i*@gbwHlOR%4%ztVrA;;Sp0pxCcf z4x(Ks_0;Eq`9&A|QT?9oRf0tTo0N_X76v&1jUMuaX+g+G{mo5t?tg-f6^=ApE`{ST znJ2q?9+^H@eE#7_*f=Tgz$sg3gU-njKMKVW*EBY)gwDpQ>!T-gDF^plmDk_Yp{YV2 z?rWID1>@5#4QdwFH(PTFy5s|2OQ<@zdSUUodc#8J<+<irdr50}-c4S@`J{!b=Z0R5 zdS%JMgKM5q)I^=G8KxuJL_gtw0S;K97+(gkUiu>>f+MA=7tD+Uq at hQy6JI#+hjRzk z6p2M^4!rx;0sgQ26z3uSc4v{Zf*k+yop-*HCh*VkFVMQ0F%~qAYr%!QGjTM}mmN7I z%W at EMOsX$cqU5bfCrr0IXi3`o<YCjq1HEE#bAoAdH?@}@dbp@@Vu@+^$j(=GnG4pN zSNs<E=6^LE=`Eg>e8<kftj;#8I6K?i)GHs?DbtpYzdNNZJ(COLPv8ZAaHBZ(1gvZ3 z;c;R4FO?d^u}64|@gk#@tTU9z<40rq1Xcj8l8ykTuAhIy=K8P2RLA1L*Y*h?PdvOe z>dGd?S5c2lzeat}XaihCeLtZ4b-o{5C$jr&onZWjb;iXT6o0DEyO_6+`FoUuwC1b2 zVZ2$Lpg68+9cio%6T7B!mB#1f$Q<F=TtS`C4~%RNe=4>nQ=`na0o}sKDLu^@ZCR0N z#^|i at AfIqcf=`CD>ptTC!2YvAhU$!XU+=A+YD?CK!t>4w{u$UvF8>bvUVgM^Ms_~9 zA7mL-6HWAZaC!tDco&Xvrli95BkS7QNv*BM<kvn~%xGyVjEgU9Yt9fenkN^=#}`g+ z&iG!qKlF{$S<-x=>)K?^^i3PbWxx3P)ww<HiBs0Ue{<G~vr8u$-Db~ri=DXi%*t$l zrX*sZDGglyCG=VLFZB&Y30>&3mrD*<u+7LW3xWo5<@TFvobOOca5ZYO^mXMv6dXf) zj5EzuqET|FhSWDDq>3NmOsX><iR8(5j`7vbQ(p^Iz7zcaE}YPT$Em!b42LG at iw}8R zymo==LsZ((vqcmha`~ZfVLi(Gaa=qq9(@I=&HOoMuecwtNjtzY^pUH-0L93g|6Pw~ z<-9D)8@}umJ9)z&-CiM35f?;{9i;vSoDZ&9efPoT(m!p@S!D8Zvpv$QI$yAzIoN;b z6~(&<oIc|`EjoWO6_lF#r_IAyLUUj`fHy20=y(C6tA!=hb-XWKNxmLlFGMU#JTHnk zTzYs(!m80Wyd2RrgyHLwdzg#hJaqQ=vJ?~a21zRN_d0*+>gi`<v|kpZgU-{8cI(0C z^vpWy`$&Pi%7 at 2pKLwO)B at A<V4itu1hijZ3%O7&<?g}zwXKHo?m6bJS=}HG`vXF7d zyZeM2v&yDsg<BfSDuZ|U#v7BveBW`GUKbqSpZ73!)WkNgD~WSYsadda=kltIre$4A zrnKh8s*Q!Kn at rh_@md9+Bw{SGflCK)38(UuI8?ww5rhm~6K@9VU|5y7a2@!`<9}sQ zcn6s>Ytw8Ec4L5An%I-4+yl?;wQgHRW at cn!u!o>-xap2rvkT10lNUblz<|?pDbfFz z^LEhn_C!8Pdi`7J@6wa=o`0S4K>@}~<^!sc<1{$}Cnwzm4PJoiCfkM<e&0Un>%&C< zv!!$Kc!<D{7EG8lK+lllZ%rYAZJnE{bT|tzguFFwpbbYNQMrOX(fk0Gb33vYo`B{e zOLoQ=u)J9M2;Fo2|Frkz@o^Pby0>n(B&#LwHWoJ6SCVC0wq(hYyl>iCvTSXZ+PjV1 zl3G$*t?qU&lH~;g1ha#IATWgB0YZT6Lr5keFoX;t8McrFl1xHihJ<92$z&!kWD+d3 z-}hDB+ly>tLf-qmKVB?Lw{G34Q>V^3b?VfqbL&c16ZtT61y2HjMv*H)xje=xb2z!> z at rS34d~>bmsI#uyRnd8PdqM5``K?=dZf)EnX1^FmxIF#R+rRy$o)`Rrcg4l?ZYsER z&YA=D>tpMlzUEFb`)TuVEXaWJl~-fUFa?yyjEw;&*H-Gil6><eH+mIUV)4M0`pkaO z;QB~I@#L+?pLp=!|9yWae2Bk#+}$~_{c*dNS6;U<FVepCQ<>L3F|w;ElTO1O>&wgE zlk#Sr%d!e%LOIcR)+wYeO`hFvu29Nn$U`NRp9F^{tVFX|uBXpzdj7g;ImL||oTc-! zX0KTkyJ7m=b*}Q#+ABsn9 at trmbGbH%!;|LN_a2<qazKCOn&<TF84H)UmuKfJuU<R- zvzg2CmM&g((TxqRNBnmjS-Sb?+|Q>K7G!TM)`Q%ylr{qBFjcPl<g9eIjqpZx^pU1- zCs_#j^`FSr541jUJZtnWWgzI^pZ3(26y9%=eV++pUt?dw{RpZlyf&U}AFPCZ at b(RF zJbtQo-N?G$Q-A-{8}w}Phgh~gZ_U_mV|ilct;dg_i2Yjpt3CyWDL|OAl_O6&mJSp- z_tS?)zme(eRp*bIFBDrZF7nl&<+`uF#k2(a7S!}tFPXZt(%Jez at 3eJ?dNz*WG=tLS z;+(GizJbbZwz=~wr8SUO;jEmO6~3(S%FQ#{PWCORJLs%zT(EZIr873{8tH)ImUwY2 zfsW(tg~vWR62sbbY=izR&VYGltdcZhSu^fa#$AY4<1pZp;~ao_!#t1*(;d$){pQiw z=S2J0|N9QW3h1|Oi_MHxiuJF{di7@86S*=FNp{7r<~@J;+aslox6Hij<GTOQuS-WY zn>YWnEn{Dn_2A#ayF=;>o8<FB=WM<ATi?F7^{!=)UG*q at +VZ$TO^=NINMAH|lQq93 z;mNrQQ-{P;p0R4zV!7hYQBB+!w=3W)fj(%H_p<*|KWtU^$}fiJ?dl%-gYOBcH-yYM z9<&ErMwl@?=DCn|?at9VZT)wmJD{tK?T|jsy~u!V0l3}pxAEO5Xjj-4l##jm^r<+= zlb)vhsp@D8X<S&J8mZX45FVz@|NLeAsTg_i(J$RSa`%@Wd*CYlF~^2od&{>j-?MEE zCYSB`BCWk_&#n!pFBARK-u>FvfBOBjZQEx2{u}LI`#rXliH{pd;H)3?V~?bFf9$8W zUFgSr`bBZL#Day(sRdJyDgrI(;!e-deYtaUrp!(~wrFJftbLUa;gb at x^NVIJPCdU! zuNw8u+EcdWP+<~}W`SmBHG)@qr{dUePiWeDU+0>UUE8Ov=vxgTdwno}>plGpoj%%o z^~cM;ItsJ_%kr-*1PztfM!gb%y&j^^?KK$!XnJ&TlGA1LH_DUD{3{A;3LGn1ie_hw z%+7NlckbL{Y1eMjKR>Fa&8;h%J-4`K-2z+T=#MHEs_Y+?6{<F4Z`iH|t!NV}xIFby zzLj60+C5L}s_~*7n_8DFosqvX*8v==7dTe7F1a{kWKJ$|SZAWpzj{m4iQ>!WwB%hn zW8O4iGjFMLbpf#{Uu0qf#+mk#?H$Y&T+5lR*$hn2mcgVJ-udG7TmQN1-aW6?ZC)<+ zY-}8gwCG3gI?}xTqgBGT?u;fLi@~4q1yR4Dd0i#t6RHD#K78{TE3``hS?&semV6Zt zub=1Ruq at hPa8c^ZCoZWSZvDzg?ujQ5wH+&HcJFP^6|?5ow5(Z((ROs?O8wqZZKQf* zb5$k2_kr~%V3)v$I7zPk02v0Jf}95Tj0x6OGP$JTiuTx at Bd?wKPV7 at lw{Hx%UK6wO zYn#_DwCBXSN3MKj&icaUjnx1NIq+j#8ur;{G^q6kCO2t#ngej<nKp85rc5d!o|yK? z7oYpsv}^C5cT4)zf`+nbTSKwOMU$Mf%Qr2Ul78Zn`|Y#GzWvy at V!?f-(=I8P@u_Pa zBaSs)`<BlrxMW6AM}+iO@^>jb5?JF4X{lk~KplXkj3gKgFq62OFgIOn(kB<J+P7}i zu2l=BWn@e*SiNKQN3NI<fP4>zLbDg;OwPcEKGL(M&dSxdI1hbf&zhy%yRHsaHxFIC zuX*8H{~6l?f^LbG;f1H;dDV4Yo0b)nH?OEFT7)Uglm+lCTX}xzQiO#MPvdmy*v57N zS9LI@#Kifo4X?dcx^2UbhOUxnFWwB(>Gs&KpU^)wJ#SHtSe3J8aqP?4s~R^gssG6B zP?aB#b;oTd`5sdJneXsDB=K at wCpMbzHeSMa8<`KD%$2dvC-_bQzWhPwCgg?q5DoRN zh2?&Oqs59?7C%AMb9D=&o=2{@Vni&Cy^fb9oPE`crkEkiKk;wXo!9O~0Ca4bef!8m z4~@kBWB<bHuKwz=CuQU^^qYz^odsWGi?-&>F5vW{#OVm+q!_YC?=`KuOSze;>^fL_ zXP-6tjy!8rG{qjrKEwN*HwwwP_N)^}Ej;Xuk=rh5h1reoN#MRw-b09NYfay3mV9UK zf608)=ZSJyxq!DTP;Q5PA}m5+u4Ynt+-a-9YiX_3<$xrqcV{2yeS5=3cSWuvJ8#8h z>wi$PtT<!-h<@Xy+>ILxEA{>-|8Y?)u(Y~;T~2eJ6GO^2bn#7rvHOH)Lrv at p>zuMp z`UL2^o=$lxxBP6T_k)eX6JlzCACHVYHnOqj<jJ1b9ut*lp|Wrz$G-Sz?C8h)o3D95 zyc7FEqqDil37YUg4%qhKP2HKC*xQ(cwkSD5epR|5d3NcUPVnse@%$3$H?BYPOX)X; zO#(P7UNY&uJMmM=W9c6Q-dWJk)GQQxToeAWyGCpWMm74!=m!-mY~R!aY2U>e4U=tm z+V6aZd7EuN)UQtaxr`;6pEix{i3Mj)=pEudnWt$luq{z(`=mac_V+y92HW6Jj+h9z zUp*^hzNmCv-d<ds*RgDJ?De$V^{(w}7jB&BSXG2Ch~yFfD{bGz`u)4;@1M2*fw`X{ z{(J_MkbOH~yAd%sCy{qX%fYvlSibGd-L_Q%?>=dh&lG7tka+x91C21&cEg^i#99q~ zw(JmgooB_5 at VNL9m?NiW*#F~Baa)Y%M_c8mtMYXCfk?~Y`opiP$MfZ7U90}s>XFz% zaqEcwyn=$NVzT(5euHg4ZA%%cCSQ)#ZwTFeFf<a@@7r?ZSVQ9wwqn6rNU~vT(!K#4 zXYzfB0_$yBNEBk*u;L;CRg@c&$h9>U8}o{bjNGEJyNfD|@LyCQr)UXs4jRi!bB)qv z^Tu7lrxNh454=kk at BM;wvuK$vVv>&LH#{)5;ETlb#67UWy?MSg<Pv;`v<~WqEeEuf z-^RfPwE1luI1+R9N#FWbtPg(INCfdp_LH$c<7}sx82RZ>LF4;%LqDB%LV7YxwO2k< z`H|D81L4LH&n<T5j*QH!TsCFh{`E^{W{Z(^F8g~O2N%Z-TX1!5&cy|*OR95o#=fYx zR5t+TI_R&>zy%R4)0dY|%jHLGxK%16y}?E7A~04o-++6neC6)$3bEAviNzl+JnHJ4 zE>6TeLd=f+SHZ(a3Xd<o-yM6k{HkFbcQYcMY;LV<zUk!1Gb6D(w$`?7)omlsNS;Bi zZ8yT#F+ph_O|Cfe((x(lNj92SQ>cZ+)bi;N8k|O>f3kA^{L1;lk&&}_bI<0*IT_zt z*06Z?gwc=Q^SP0GKBxO{yZ*XcZ;Acq^_2&XxK^!l9XU`bK3m=rJvds9!^(eRE5LHS zVDu+w8hxEFzGu4`ZBs@$WpKYnopZh%LANu-eLW+`#PHqw-FJzR!r)?*o+6Jbjx~xe zjqd*aQ_sAs;FM`!!FO2xEM;_NE<QE49MPt>zOk>o6XZFT!FTjWPQ4itJ8u^|LvNzp zBJEY1-#&xo3*={`%CR4<U&=C`L&6h!^Hsg!ouCNDP6Xc}o=Na=&N-riXX50+GFhf8 zb|1h`K-JhIx5pj<cnSaGHoxsniBI`d#3ALwS1FFM(7lIZ_lDlY2e647K9Pn71Um7g z?H=2^)G?WWZ1sRG+3tyb=g<qWr7s+ceMhW3^n&=_3x^O<oD+NSz}s&h5c at tN_AwEA zP_?@Q{n|x11!xiPYay61V<9nsXnZ{MCOX)CSadTH`}htK0P0iF(Ln6B;5%dg1&nT! zd?`BfYY7K at DL^D$?l@R{HC`0OUQUJg@(rg>iFIc|{3p`yeBW at xST@g;!dM-aZI?p+ z2h-h*WlDpH=kgGG at zr-fCMJCCHz#A?z1a4^$k>K7`=5_P-^>*2ZP(aFpl|Zz*<R|X z35>eDbR5lv$xFSSqt2a5hhJ- at Isu9W^qiF|OINQfE8Q?QwtA=~uw~1@!CU67^;CxH z>qEg?M=B1iURPGUPCu}!W8a2#+dC_oj_=yN`_osJ_v~5iay at Zq!>&E+R_xy=aqgG% z-6=-%;+zn9$dVfVu!iJOAbU<-HtocBMi|Ku9<eX%{Op0ZPM-d;ec_(#ZrpkL$AAGl zQ#ddePl4X7<owTIoq0leXE5Fwu~PznAubuVaSyq|vYyq<T)lk#!s(MYRqsomW1l={ z#nfA}=C9qed~)5!ed+VkCNE!dBDKWa@~O8pe<tzRR=9pw$tCI4d(*Qwmn}7Fo69!r zO`mckb|9X;sbcBRWggH556ZL`F!tU?ijkk3o8(uJcvYvr at dP;hzKxZ`Gx&X42Fde# zWG<X9J_g@Ae9HLJ5(5(W!>=v<LC?kkUGI#|w%sZE9zGNM&7<PF`(uY6<@Pgam0B0= z@smopu<mTs{e%9FeaFMGyTh?VkBXV2qp0{O$45WT|Kqm=({RJtkjH!LWBNo4t(SP_ z>#?zuwu<z<@|{KLE5kA)e>tQ2z)o>&(Bs$_Jyqsddn)btu49My9yqXK#h<1FZqL{| z`lac=ll7#P3qM-<OgP=~LTkBxX`){2>sGx9<-`Zy94Nt;>EK%^paDagl`uf|?xV3c zME=b;ql_k?8}vP(uQnCC*|vMclc!E8TxFZcr(YY|>(toEOxU1iKUhchlQX|KhDqz{ zv6J>^Po4f5X$ZJD_wZ7)3{3*`FLpJc0Q_ludr}- at yXX^P5CmEPecnE$?>UuLq41LJ zt2O{YzjW+vaV-6J at 4a}+{xR8xq$gp}cS7qDaEK>cBCe->r;>0{<<Lh?G{6|;o&=;n zK4xcdC-IeZ04&mh)(mvx)hvWQnPp6zRT<eJ;IJzIBwI7#pe@L%YD>YA<rFwsRDp!7 zX<`oAJuT&1wk#wUMtg|OsZ+6Mzzxde8=&V?fUCX*hTcl9Jf%vow|{&L+)*+{c(kXa z45Iy+pb8lTOy`VVp4NFv!W4ul`^>U(dISaYb-+BOKZR%ZJYe~(q at iqI;+ZB#p at eLc zf6+6P1{L%@r#Aq0?3vNa?RltW$$-Q~+0e@&3BZMzKp0NH_X*oo5*4%TQ;-)E7qAH! z0rnfjhqyp&$OY1Zvh@`DC~;AcCBuk2g#?uVTMDD?Dk*EAF=$8FWIsugYr6;1Mm$KB zv>%LS6BE$H<T1-K6k6^+ksLK73x)HvPstx>Lf!$cqKPUi;Y-P36%GOW$40YbM%oYF z!vYdC5p$$m1HLIAAPP_>-T-g=!+S+QgypbtY~l=?TB0#ke`>7ejZ>%0L0}>xWk{Bl zc+ijnMEUm(kQ($JtI<!}{r~}uJ(E@e_y8UsJJhM>*dbL=+9Zk^LnI5zoBWsZ#~S23 zP~rg^z+F_HdgBdXMG-_RPyse2Te5tnT-#7$C57Tr1L9ynpdxW$MMWr6deDgEGxeUr z5wgP{sf&~Zk!&X^LLggQE>K?16jLS5Fw)U7XeQoD8t|uohOq+879Udy93|bPOv#ZD zGo-Yh!f?pkKALS(@$}CiS5gk;m{2p5T>i|=6&SyuV(e-P!PwgnGl?Q-B;^Zj$}y+( z0|_X9Z1(`ixU!MjLE<Oj+aS5Hxu}=Y<hbA;>gv%RkbomZ$_^Ek?W#Eaq+Ce(H0dEl z6LT5nCGhN+sbXw at 0FPtO5Iae8Q~o7>O6tgHVuzt2^_;2eI5Q;VANVQB1BR%;Rw#XH zB*><e2m%6!1DZ4GC;6p%N7|!@axCY@afFaSia9i-FpgfHpuJTWE?>&x5}j@#DIgFl zAWWf at 0txjY2Q3!$q(AC$tW4$E9Vu8RQ8yX;1eKFJ at l1l;kX1cP!X;I(2qtg9yDa<9 zl^iooiAy|#P77JjfDfKsjE58#>T<=4v6JGZ#53_y;u+KN_8_j}rTEkN=wr;*EbEc; zld=AprOm-wH6O9LOSBT~RIWq>z(#z-qaL1<7VJ#yfET|*>(Y8*!vyeE<00*cc7=Ae zcCB_JP7=~c-2(GBEh&vNJ};QhFB)5Q|I+f%!121t>82)8-rOvfH#NnUAzjhZA}X4i zPU7~3=H}Q-O-(j|^ouPmu at _~wRhlKv&OHwd6t*Cb-JM8}o=LQx*4fQ}UGks0$P6<p zImaxc%FDkKmdJ}(+{u3$&jAA8D4WXG{p<O5hjy2CuXexop!O;4)7s~?$F;9$Pip_D zeN%fL-ltdK`T3#t6YWjyZS9xZZ?xax<hcLQPHSiIQLYS;1<%ZMyuUa{z{fEid{}vo zU&_M&?R?5kavQv7y9>|pOZiGJYo%wMqb%#6uRLYVs+)X0Uw%_#Bc$b?rlxzPxZTs( zIQsW0h1}-md*$P?#=oMJC{Kn2anzq^Y(#@-7Hu-o*w}PdlfdhOWhl=I`S-)rlyoKC zN|uwQK3Td|8FNs`2PtXuF)t}_lJk?&<|t7)=2(#f1|vq}ds3zzbZQOo4{pV26T9#> zxm)ufns-1us70~vKc-!&U87yE-K^cF-Km|_?$iEG`=s`$_F3(V+7sGWwWr`yd`|n0 z_LBCh_L}x%?G5c;wO?reuKiZ~Z|z;2 at -qg{Z<@#y6Y=)bOdRwHpFcD?zSBPS9KW;~ zm&4{b>z7}KKkJw0e~izZ1*rU7eDW{e%R8%F8p^C3m!D?dXW;Yk1!j6W()^r!^6$Ce zec*EAa3h|iMl!xk+b|9i|3-TtpVmLhYEyF)E{&s)CvG6g7$X=krbt_*#(p6y$_k at r z8h;6ahw_*88UY6Xr1UF at xH(5&GGKj9(!jxk^un&#Yxv(S|1#kbMcra?w^-68in{oB ziIO?_ckD9>!F at h1s$KC^_qUUtCz4%VvDXuKNy3lz;{TGMWN7iyVsShP!GqjKZ1>n* zv>7n<OIyS48a0p)n at T?0Cg%|ObUKRA8hKLBw{IJrXUiSCUw5Mz;JhVH=wG#Y35Rxx z{?*t*@ndm9 at Y|<}XISk@s|6CIN(}y%Wnw4aeV4e18)4go6fRs<;fs->Un=;T7P8%A zyPa6Ufg)ZMFNbW#=v%Vw^=H0e+h^O$XE?ZFqi!=c+U|`ZYEAYpUN2LJ1z8a*v)w-W zmd(Jk4XC%*wvY9uGm>(yxJA4&+KqaA*ZNvpby@~&JFKoU_54gR%Qi=dce(qvJNC4w zE_v>`=hHIAo*sKv-=J6jJoaGhK|vQA>h@`G*vf1V!(L)kf!v2-aAU=aS>_v_jFf^K z=apAKcGt@<p4>Tfw0*~+LEFRMyM5%9 at 7{UqYa at qUdm_Wz+l~Uq1gsQD@!E-6s_l*1 zKHDza5NL=fmzA{U_^|}+ja4kzXqz1i*X-}Cs@>nQ&o+DX9evqlH(j>>y8Rj{uK6Jc zGm(<fjySA>HL-cb9lVMK8A-hW70Yk?{>vk`fB%&`w;vkbwte`B-}b4|UA9%PjNJLk z3nxGJ>d5f+9YcdVcQ7mw^mqrZtH*6!COwjNS~8^nQ#GPLe#5@~$B*ytye3$;tfa24 zq|B)w>%RK9o0(-b)#Vk{PH;ohj at nKFPtule3o@)|ik6?JAs`Q4)agkO$L;AT7I1$@ z9by3|tL<d$-DkfMd-wA1A9?oXXK%jg8_!*L!*j81xfjpMHF7S_6-(yfuQ5AkUhLSy zWqbE6Te$vE)Rq~$Joc{0e&(vz24m$nKl{y_ZvDq+-pa|#pLJ1Q-bFcic{#IkbANf! z&Yc&b_gEvu@tvs|j0i}^7C{RpcM2rllG<q(87uLsP2R#}>|)=^E6dHxxj0YkH!hx? zn|E<eZmepvn6YK)YX89M6rApum7l+Ketr&6Db3IS>CHFK?(0Ldlyc31G4v2D_j1T( z`Aqd;pP4XF(&eXr7>;h9zl*hx`uxwz6q)@}S?kW5b8o17;DM|)*XQ5Zyt{1gmRpSL z*Et?|z<J%=TV1Z4{##ER=)d)L+d~i5UY~d4-mOKKZNAmGey!ud2PdvLo_k~CWlLS# zZY;QdO=ig*ecs#d==I*NaQnR0s{h7*C3fRrBf@(u_usgIN%Xj^#W6xs9x2J<G4Blz z|J%bid;avNhSR6>+auA31_$-3$*25RJoL%S!}m at 6q%-zsk>S*D8=@e7A*$L~ti5Lo zqK!GAfF`CRo(TIFQ#11Lx}QybD^-19BqK at i#lM`s=)l&QqJGLO-B|4S<JO0xLk~Q7 z$m!g1=!mQR$YI+gk(RZ1o}M!~_Nke>LNm{3TLYJW=8@>3$F>~Vwfo4>WqUaY1Xe_M z+t%1uLS{8F7vqWyMu=7UtlX8xRNSED4Qb+~Nvo?HYFFg$uZ?agb#<&x8~b#+FsiSr zb?2^hZmwQwU%9TY(Yawm<C2Ds6Z<~uy!4VwM9qo)&f@0v>uR?2qa97V3*)$iaeoE5 zE6Y}HNMA4bs`&zLV$PCu#Sp;j-W)Be(+d{>leAg3tg+*1B5mp=)#V$PT)OzG&8{P% zs*=We(<6I!4OOhkzjTX#*mXyjE@oU>QM{>i;awLCJ$=TU@{Jv<^SA8_fBoRXisi<( zXtceyeol5|=bquuq=|K%wJYY$&c+4_Y0#n_wr#fkitiN`EVavR)mbJQlWgLZi`;#k z8~d7WS%0yws=0TgyQkxD`=i%farvsPJ)hsRt*^f7_!azGjL-(Pt84}KB6{Po?Jz4{ z&p;IL3i0BKulKfpdqc&mTVJ#nU2%GMW$gFY4T`)CUu2!HXxHga*e^08qi5nvqj;T> znr$J1J at WG;GUCDY$96U?DBZgvylY=a?R?Rrf1&N3W7iz*@a}hiBs132d9-yy2m7KU zHm%-P4?V=SFn60R?J{d>ngpmMY1Y&NQLsGCR)46yqVO_j-ojY*Ay-9VhjZ~<ePt|N zJRPeR!QR1AW5d2l!Z#fI2Gi>=o22i*E%wClK5jGN-%O!P&NBT5-n=1~*{7-c(`H)N zGW561G~)Se at ZQQY_ztG+8)iC9n_?R?)9Kn|oGY!$Oq^Q3;AJyCNt?ZjU*zP!>9+4x zPQ-j5?3e+?*N~&qLd(<dG1Iy>N&l{ywrM{7S7zF-mDz4K(`ndg|B0DS*XG(Mndymn zx7atB=}B6}s*B9@^o)mA^~&_c+Vlx}&0Fv-1aMB|uoi+H-isMAq8W%&?1DBmu%}as zXs1%_lNxxB+OKt^lm}%ESnzImAl$eQKw?;9ASI_#>qJf%(4sh7B!s_&F(B&{qhtj8 zbgbdUyHb2w8NXAd3sK$y?}is8`%tqD?S}CiLTeu2Vc;FE05Br9&44q2T!kaCDv`bF zmgUv+ki>KdxH!=AU;E?0Nhy8fT3n3wNe|*dYz9C9;v2?&K<hEF^P`LbKTaR|+>3l4 z?wF_W?}_)RN9K__!|(tpdWXOj(!%13Ruc#ghrGRg5o1wTp;1=4thC6e@^^<k!$y<4 z-yI4J8jA*0qOx-!9EmOsMT?Aqes{4uQas@H`a;o4UXWc_<QVcs`iwSD*b^G`bQ^U6 zf5d2Z4|o8v-(6A@==Owg9|{GAmO6?r^v~hyGb=7O+#!!K;2G%jgu+Ik2jKi}qrETM z8}J!^!tY^AJ?;UoZ+I>64t4d(u4q!?-k+P=S-5FZQhGsMpX7$t=IM?4+#xM4FO-`Q zXiq+sK^{tRU4viB_xrb|q%Z+n)holBp2c#M6q}rZZA|Lb1wy?ZqikuZvF2<Vg3uB# zJS$sXysWes+(W&;9Er0@V1d>Uh^7S4E5*t{S2>)QN at Uq>Z99144fu`H(q&7Rm6jsM zLV#L9jh|wSrIoUeAR_54MWMK6<?>=={Qsvt)Y$jRah=dX?Vwi=w9$~1I?9$R;ls5H zZ179%MZFb8+R{waZ7kakpGYIFEzra)o2=a$Qy@@_j%TZ4DK#6l(z)8Tu;~JpL6lG$ zl={hsoFS>3-4ZXWq#IAdct)Kzh+k?VH#C}0;+xRR)Mj;%nys>A1S81-93uD$B3*)? zF#6BAC5XH*gfA>%Ed^|j!V>hk4t;mXpTAZwf4KUNB>J~%PRu9lQwy#xq#D7S8kyUU zzvrXA0UbbnvO?DNfKr^*I3IAfq{c4ADEdD!_k5T>lDe(Xj5w_yGj}!m<V21&hjAWK z{I+H)OD-=gkDU1x2Wc-@^It?t4rhP*_LMG;n36-j`!E71LmUSV`IoagWicpcLN%|e zS(7sB!w;p8b!j_zr4?Xc#91~#5KjX5T8#(NMmgg+v9O2&YEagtwP5uvQJ3=RG2s%P z19v?Vc1XgqsNx1}x5Rb;7<9@0ax5Fb&Wn1S894fgAD<HQ0oi{)a+pgShL~AnO<|~T zAH;LggHPEr;-v1_h6AuH-N0wcVi=OW>PLCDZ-Ay8|H?|R`bYbNmKNoZBi;ZGeYhul z;%?zWPFa|!CkkdzmW=?unoQV25cni?K)|FaZ5?W%gk&knU>oEhH4U+`IB>RZOvYK! zMqwO9J>o>#DVwqzSXem&fXw?IGtcUi!|bEt33X5>I7B&Qzby*W!eR|#MjXjU>t121 zo-51Fj}nw3>PAX2Y0BrSBrRUbF{PQbFw8OLkkUk{R{SSryHGAF at wQs@OGs*NpoAw& zy2WW#pK?fkE9$UUlo9G?MI+1l?8d#7_x{vSQc!|kg#b6vy9Ae6f<5K4)S1qb8YRD^ z38_Ks6(?AW)JwMC9e}1}m~^-FhDBYA-xhT*M3V%i6FNb`Rpawq^+`$2Rg_AP<XA|` zm!hqOiJ~yECv_<$N_vuKaf>pEaX<<T%kgUIj#ODs;H$X8-4Cy%5!V4q`*AFg7iW(N zVx!hhl!0VSNcVHk|J{H_4sf-hB-jo7g6P#SsG!E0)en~K0^9^H2@PrWp0l`;TP69a zcq+`*6ZXrHa>x;wEY+5_sKsnlhgr56zb^c8=4}U7dC0Z&dplCeQkMsqq(cvW)f!9n z!NP`oqWq~jgqnf;WB<?QW%39%&SB9yu;Z&nJKI_WD4gp)7~R#l;#{EQhjSmt1Nj%i zdPC7uX`e7)Qg>U at p*X2 at w?op~(iuvN2T-1CLf$2MNF7Oyz#elfrrKX>L^6LomzB05 zy_F8n`#@{%^YOuwrQ}f_mLsAIm?dj!^2F*nu_KpBXG^C#&_m9BsU8Z>0>qdjL5 z7Yp|mK%hT^Qp(w9yxn#_ye;{$tPW*QSPLj6P3M<NYtE;&!TC~YeD*2~dFC8NyvNT( z7m`2Y9P(%Nvk5Jw&V8w?gY)&%;?KD>qmoWOvGgyk9=2kQ_<+=uEJ3NJWy2 at br`G%t zg$yc7h#nxT1g&J+$t-VxryK)nl#<^nmo^MX8(UYUEnF4#lQ~3-C6&_2WgO5X?=J;v ztZ+PX+_4=cF|+`b{^jW7tW8Zq92KuDYcfF*wT^o~$*^=RXFd<+90Py-(1+BI?1Lr8 z7H6v@-AIcNG~v0mokbaPf*QG3!dCMbp{w4|j#PPSrtZZZ=U%p`@;Q!30otjY|9s%& z5T4O$Rbx^0K6!?tHA>A(+?D0Y)gs3YrGC7Or)uZ((@FK7tFSh#R2nc3aBa*P!N7Mo zsGXUv9XC_7N?d4_hb0{pPD+20Gt~KtbE at ye(6WmxJJ6adlq4t*5#__;o3egVdmR8& z>LJR3k_89u6cwnkESQ$ep1<FTmXz(od&|-tHtiCn1^GmAjy^2jC9FO5%+d&~cYdpn zWhp;uj^If4Nyt`h%S&LD;e2<trLA<cTG^bhw_IOyTqe9DY at 0g2GrnT8=xkAnqt9A1 zSffnI7WEMI33(ir{sspeTAY!aK?&tARx)eBIzQEwHN<gkjU=T%2!&&wEweqfHsgAp zG$J13H4iDx()8hs*EwqJTu4(Z%U;d))HBu`pS;RYc#&5<<}6(%d;a0uJRi^KlS<{; z_*LZiRmb at yjS{A$=)<fpC at b%`!k|^oF`F25%3|OcS0j_0v}8ef5x71}lvn&xJ0HqA zrWRxgOZHh?X_NEW at w7Jyku?<e##?NpLaKaPRv+=CoE77$R-lAM4Rr{A$J=(?IiQsI zQJ#8 at b92HoM{TEk6xPl|C at z@^8qo6Rti!)vlxJItzoaDP*mF+k)H}qME%K^xqda!Y z@nWqO)ksM6fFr;GnPxucd}_vo#Y#GpN9S67Gscy;DT~o+Cz;NqD(kDg5<`wv&acX{ zPPMI+ZVj1Zr`*&UoVPACIzP-fQLQCpuMIe{H)ro*IiS1LYB+gyp;j!U3?+eUHOeJ* z0OwnBj(%3o^Bf_Bd+r%9b<VSF7)u(c&A2;8+>|wCS$d9htc6l-(WPkF6(1W4hMN27 zg-ck?e&9}?TWd~hzDTtSlr&PdEeTP!MDh-qvZPs3(bAg9$Il-Yr>!++h2%AHr8HTq z5{uf&R^z!=fT_Js)Jm*UxPL at DSJ`Wc65RK*C~eI{XU~V64QYR>IoMjEr~1CE9;%Vc znV7t%cBBMwHOCRp-Z4d5cVjG2D#!`-gj$2^{R>IEf_g}5LHg~jwyA0R0fRmhN at q7L zOtxtCP|eWE)}NArbJ63%(q+|B+A-0}xhc&)Tl&W;t9%a^8a<A4jvi%+E>Dpgi-Tv+ zj-(iQ!m*QB-6~F;%W`6Qr6n)43Zi8}A3Ci;%PwFm=UPcp63A_i4mILFh?j!6STs(w zlq`kkU%Mv5SJFlKQ}RHLoNvBV>zTjA at 1^VuY6nHP<d)gqx#nl(<)EfGm)|QP)t2Y= z{rz4J$pyB~^`Ry6=a&g3L9X~(rWrcI@^8K0ULD81vN0{pxr5?M;?f{!Om8b?h#DqA z`DA@F-qumJtfiMp6KY!Sz*xLi^Si>EYpiz60FA(!5!+mm5$jDzCGU^5!{V(&{tlGm z4wE748FIg=1$&hZSgkqnYhV>y1&Hc-p441B at RSf+5Do6a69uCU<+%5@3ps>M-P$bg zwxB#$e;gA|ZKt`X#Wf;Jw&5=^ZIyd=Y?G4B8tlb3+%sK|ylS((X4GQ4+~ebFle{9P zF61X#O~tAKWmJ20z(Ucb27ORCR{=KnatUjfY at eP@7usbl*5l41F|ERHEB?0ODR&sR zp%(9$yA9V?lv6v7giNf6Eq$cTfYBze^?*tYNG*ChNGq<8ThTU4)B`V<#DXoccPvS& zxbVCRP+6a?ZjrePGtz`yCN2pmoa@=P!o$Gx?It{GL-xNBKZMM>)b8W);4IpLGHjKw zt8rDdC)KzonM at tU@r3*l>squzeA({=&y(>b<;U~DA at LqB1C~q?7P&)uG)gM8gMRpy zwuGc|*l$V?<-5kDAoEy;y&+Yt+KPtCPeK0iO$Swim^Fejsl8yoc1W5fsIKTt3@ImU z(ei;Lc+575vxN_3ma=4xgalPM%7_VDBCmPJ7V3cuC8-58<uhv(sx}lh2C!FBsQSfv zsr0o*i&f65m)Jo+TSF*VR@;=^MBhnI(n(=RE-PwW817Q-|6N2B0-n5K6*Scz=!pz5 z=m{A<Z<og(MmSZ}&-kebqN3Ux8jY5q$FB-EsscrZ6^esEoTUnwS(jkC0zof;bb5S& zp(4ZW?`Edk7Y-QiLBwylJAEEQhHtrzx~i>)JF>>n7l{Pdl$3<KLf&8`yflm`FT^92 zwA3{^92bt?I)8kZL*n1+Y&Pmznq5X?Lyfb!-I<KOQEZg0FzP&=p{P4Fj99MHm48)( zj at CA3Ra14NQwEjw2GDQDk&%{X(a~6h4i?Igvq-=QN4x_F{)&J&fsn6z$lL94bbAIt z#o&MkL1%!{74U(4fsi}m9W+DHf}y|xPgf*dWDrC&7z~6WvNai~))n%gY{2jE^z`6? zM8)0Z?)D6LyCi3PUVm@Yi>A8(d|)8z_eQ)P03+7`5YPvKhv7DQLLOZF-HrgIp(lh` zIH1{Yc>TsupBGVZY%gpKxQD at bBisj(>Q=lTAQ0T3WY8Uo_z}g`=MAa=z5wVF3NLj? zVu2VC69`RMb|5Z|3No+(;3&ki2&9U3L&Ojr80hx)P}qnWT0~Dm-p*(QIYihuY`7uT z0e>&AXbblcb$=jYgabZ^=rA(}!XDqCC%n{f^?4k!u_7Sb<wF~+=pQyP8oYyw=<GD; z)8+OPolcAt9}!{1V0SlD<In(NC4rD?P6<8Wg{?tn(igPub4O%TAv2`W!DvGa8xvjj zo9K?mL?Ug{o;*uD4;MN50z;6Wki?dK2Erka&*M%EaduAfz=#Y7JrpsM>WUa4&%vlS z<dNbB`7zr8Cy?g`KP^3x&>cp109dmzcQEK1cA%^zV4y21p-6#5SE6Ck7Q{DU0yu8G zq2vxgENY!~4b2U%hL+}bM_wI6FD-qQ2ON4Bq=^;?ChU<T1jkpS{dg}GgN=CTsbdq^ z?g=dl7oH1u60i&O2|;cK+@XHTRTzV+tB>SBCn4PqB^hWi5Dj$!eKuGGLGVJJO#MY^ z1R+$H<@iHa^3D!cP3{A!q*xvfdb&)ZR*f6(o`_P%4yaZ$hBh35rXWet7ea4AXsy@p z_F1|u88+!d)d4-h_(^a%g_Iom!GIsIAa7wu@*o;dKO;^*w%KHjK65%qZgbqQ8Gz2A z{JOj4po#>Fz-gZ+0x2kRaC}5Np at bq)W*EiAma;JLNNSh><_u&@c~#mp32CKY9pgy= zt_&MpeQrMp(+fEZ4Y;WRL(Ha{v?MT<7?fCt-!o)*{Da<5z)u>pvsKYZUm$dL7sK9O zs5EE*HsfImgSr>`ae(QFr>oEJ?Q;7ZLm at A@i00LR3qp9ojG?{&=<koKEmNFRa6<E~ z&bFq8_I3;eV}ViA(p+l}idIi(z#Eofv5-$FQx7_a*8Na{MM`ON-hevp^~foL6f~O- zbVe|UK?R2!C<LJi6lGJ|mX)A=l+qoBE<s5sUXKJQ)t;$_hl^5FpPECUT74;68sjdm zv;hi}O>(miYj~r%XeTE&Ykc=Wegi{f8K~8bSt{TUuW>k*6&lSRgo2yqN($;wLm^9I zyx_bS^)Q=7F-Q`^Q-lF+#3ct6#tStDw>7DmQwCspd_7=FS>gNFOzv4+aaqu;sUwAG zaw0qbd?96v6S6sBc-)wH$q*<8Kbfg!PM8QV516mw?V*lgxJj)Pp&Cop@nIB{bD5>s zx($<_RDBU>vSK$TP!A;e5CY$ow(E-yxc$YjExM&K*M|gEaUeuuQrkfJhXTQnm)6Vx zki^JH6d&+JP%#3V!t3#MhovpZdTan7I-$m3i78E<GJOYttXapLe=(OK5Z@T|dWO{e z$i^Y9A#kT0q7gWYo#*EXs!7~a^t6L76{zU~dOmD;4h2CyZ^Ynmj=&PaSV|gz=E$Rl zQA#(2`5d~HO3zUfXNc0G=A?oW82zSd_S0m+%*k<KssfM^lMx8Q=}*3c03{tnEhr`m zf|~!>X&O=$g*0PiIihV2>G4g=N6NxidM&9e$54D6(vrgnHmwIVVwr^lfe3Vh$KM?Y zfo>dV-7qyGUfe*%IL4166g|}C3CiK@?&|jkhF~_~g#$$;Q~hB~C-69nTztfdl~t0E z4ym;Sa$!A;XOAPn9?)jFqD_jDFqIc=NOU5hoM-|{dzINpLQnfVMUH`JSlUs^lO*^6 zBMFQ<+Z<9#P*zeTOqpF(c{!&6{ZPudgbj0U+Y9>QgV|n`48pby5yP~-90}Vig%Ti; zi?d8~745CmRb8hC8k2 at y6v99z3<kWg=zC-Xri@uy$I{NUT^;N-#yeowHni6?Ry8y^ z+Z?V<PPHCuZ>e+bsA_W>4eds2Tg&!_T4$}1SJjUDydq;qgKJaEHkW}CZB@;#T}Dft zQPsT5*wWBkTjX%!gOSemcB7 at uXlQC}Y;e{V84b-fjoWG)n(K{f)N5|xim$1`1)#1L zS=9v9;6%MT2XLv`1jtp at 4UG-1T}4J+gR7Y^>j1RMXsv2<HPmcttZFk_x3#slVBuKR zTnn(x4b63JXvNv&Y<4+-RZUCluC|8yO|BwTav`(GaJ5y{I-9E6wiFS=7IeAIkVTdP zOTaLk+fmMF-&EDuXyB<M4#U{g(pZbq)lOhvg_Wd22OXF6D>7=UnyTvA0}C#TsUA2I z6d<MQoz2d+s>ULtz13OMzywIw(B`ZGf+z`cqw~lGB-j+~&aK;!fpQMB3GiT(Q=$%> ztMI>uqCq~OIC at Apu9h}e9F-jn?M}Sa)z;7s@;K_+TABbHtUwJieH%!Kwjg2(U$TXH zd?pv at ae+W7)^;eu)jF#h0T6Q0e0EtlQa;Q(9NPJP4jNoKe*AFD_=COt#trzja8P}i z*Bz`ni9mF<(ZJ0sZlQ$WJ|6-$+*S@?Yt=v)3OCh*a-)DRG^PfS@a2QUas!jwY`t>( z*lOd$Tpf5(Svl?(!PQ8&ugi1`<KSc&=5%&`2b^?t#~(ebMhEJ*<7tO<l+nrCh)t_P ze28*Af>p=E|4ZRGa7051J`b(JB}gL`N*h8>r`$l`Mzo at r+Lk5txD6FX35FPO!+{&q zhs{z9xMBc}+Aw2ijoMJ*mWK*D<fair5EO)DZm3P3@$D`KhQu>p*5~#cYtt284GI+E ziTddPjktl#wvXVjnMB_PP at da&YU6o4eD;TuqJnLSm9<r?He)HP3_MfYeGFsZ=Bw&G zDZ^k1D`3gmbW)qUYU|btRbV(XVG#G!EiKib^YxRj^qjAs#74<IaUxa+A^U8FX>y00 z+=IW|{@*7Z!KHHKkvgPUqUWTm#T&{4x2#z^IllwgbK|%V<D>)a5c5SCQ4Hr&bWow4 zR&7casW=EevBe|uuWC8LD?Z^5c{C_f#H<x}Y?~Y5A&t)YdWkWe*$U>_^~gCsUuL$G ziF=De7T%fikhSx}S5zV-1z$-L|K<Y|Qi$7dXOF$}kL`NM>idsv>Im2Sj_VH&gIHf^ ztXQ-k&$&g|#>+$49e?t`kHomiGdVaO at _6ZdKgbDQC}};Y?FDz|<K;Q>Y!N<*pyagh zFT`O0ve$*W0mui(e-PJXIb8<WN(L!qiy(#83mgm$v7T{MvMyi9-fQyy{d%LMRrO$* z>_Gu~vKJD|yhL9uxiiqeL{AyoM at r{Gk23^#k=c5o%qvaFDZ-s9vmlObqFiAL##SGW zNq$jc)y8lSCh7CMo89GEdjS`|bT`Ai?&>!>@rpS7*?~jGf#^UGYgnve<PyW at K1|!W z_d@TFJMv)$FKTV9a=~hBf_EmK?r3VSIch!VsA_NOXlQmh8w(59ufJd!M`p=l>7~Ge z8xAYH$?nA!tG+?$rV9kIS>Qd4te#L{01lQx<r#sK4IX{^R8%*x*thB~E^%b$cYAv9 z*r=&qRG5i>9UboQfN|uQYz=R{2e8bv%6t6qm~{aZ*MC^eBN at h0$QN*9rG#ab^lQpR zD%{Fg2Xg)GK^3m)q>lmZB)8d=(xxu}+QS)W9E3x?+XzNN#-30(xUa}4UF&cFG1<7+ z&)!J)71m%(cVHRW*V_Sy#G=7~4>-UtNblBSpg36Q$UNeR_X?>@5Qg`y0Wa3dp*ClI zM at JD$#-F);MaDATNrVVHb5Ck{pakz-W+sSq%#rW$otNA7HRHHl<#MI+`kW=s<_dXY zb#75!-kLRQ@)U3K&_RoZ7CBQ3TVy@BH!>f`D=aDiO$*Nh0e+cKpd&mGG-<kQ5h$3M zx#$SD0s=j}Ga>`QwZ>lbKD8N;6%8yaG73uf`u9fSw8K3cEkl!e>qB{qP`z+XUa3kH z<soMQ!OFZs1(+=yD+FP&Z0ynO)9sb^8v924;FW*-Cfu#E*V?P?8|-DcUu9osUv6J- zUxoA(T-V#TAhifJ%28siz45O%!U1g+THRn@V=uKMuEO2~xXW=@ZQ`;@KB+?~K5e$I zAQpB+0|18_`!bZRB!>3Y$f*E~Dtk3j>j1GqmSjHrv({eER&ciw*9!Y)`&N{y!F?5A zpf9+t!4vchc}}EO+FR_a3C~^z>M-u*!<h3{oxhp&e>3a<X4b1k!Qae!F33N)Nq+w2 z`Ui*cH?#h4X8l^YHMo#YG3#-<7|$ofKYZ6`1Q#K1lK$vEnXBVuFgwnHOvlOm6R;3+ z;3UXwoOL+~=T=U^DeTj5Lir4wB6tzbhs?n@9cSZ&woCBMxVbo|&CqhO2+7A{tpF~p zg;<6Z!fmt!%bsG)tR-03EW<e{<yr;K5?X<?P*&k|$u+Pp)?s<G0cV_7!i`mpMKI&P z>M(LP!JV}kafyv^Z87q(6_IppaCEs4k+vNXb2|~^xEl-gy>MDwhOxFEOT5qE1nrOF z1mUO9%U@~7wOep1`h7S*_#vTdH)#KYbJFk7evi|$Zx%M~I-IWkGn^az3GFeQ9sCFF zkJ<y;XK}Xh^V;X&*y_?wz(Mi?7DwOK{#koPdl6?2zl@WHd$jLs-^J;}pND(nzi>+N z>)H=+5^*0+<^C<sEk1yye?OLpJ}jwDVX=8ox*_<cTLf;4A???2jnI)u=hIQ`AGE*2 z+3lBWAJMMV{zLmu?Q6o0(~Hw_ruPJqsf}qd;lSzM*<vD2`qpsH;AAlc=NnHI({SGR z44iL#5l;Qi!70bHarXBm+Ml#P<Gka!VxBNWuE-PlV!kL43&cXPNEC|2_+n6z_MY}b zQH=8+OGK$yCd%+dr3!p5XN6cPR*BVOjaaMwoA$O?hc9z(5F16Ms1ntpM$`(Ys1x;K zlV}i|wJ&I26k9~2XcEn$MYM{oqD{057tSLe)qbx1LTnS;#SXDk>=L`h9<f*K6PJk& zv0u1Fr|1&h!h`RJ_liE at 6$i8 at MZfTg0pZu)(S9ic+QTBK{Yd*)aZrSC-uX{)zVll+ z<@rq!77-B at gJMV=62s!KI3kYXV<4A{E5t{%N5qxlDseSFlzELfF0K{ViR;A;;zn_k zxEUwT-ii}&ZxgqRJGA@7$HbjtMBF9r7AM6$;$HD_ai92vxL=$Se<vOg4~mDxC$+o8 z!{Sro5$#^_sCZ0#T6{))R(wu;UVK4(QT)AlTzpA9A-*iWBK|>qReVi6DZVbA5>JbN z6wionh-bw&#XpJX#J9xr;@jdo;-AF}I7;^=@v?YDd{?|Gz9+sfejr{GuZtgwAL0Do ze-S?sKNW9?pNTicTjF2EzlpcS&&4mqJK~q(SK{Bruf=b~e~90T{}jIy|0Vug{9gPo z at dq4|`$zFV;!onw;yrO%jEXT46K8Zy7rL(7bi1CW<H$yRf}W{6^ejDFpQumLC+k!6 zsrod1x;{gnsb8ee(sT5SwJ+n`#(&ahYhS|I+RtfMY0u&-0-x6B=$B|u<Lvpb<C7?J z^?ACX-K6L0d3wG+UoX&c7Nqt&eGyLOzFjZW7wb#3k7;-6MS8KmR4>s>^<{dQUanW@ z%k>rdN_~~ST3 at 5D)z|6kagOgsy;85ztMwYaR(I-kdcD3$Z_qdETl7Y~NpIF$^j3YV z-ln(fE`6K6UEiVa)OYE-^*#DteV=}r-l6Z;-Fm0qrFZKdy+`lW`?Rm>Uj2aHulw`? z-LD7qpngyf>0v#hNA*E{NI&FYWLV#Du+QU9Lx^4|kWnR<Y5okix~5lk(g%?4mY0mG zKrfcU{TXg`&8q40h7fkt<MSNK>Pn;?HQj-Ty9?2s5l2@%nO at t4Q}_Jo-Re?{=G>7C zr`aHO8Ij>ncd7;vAc=Mz&UkHyC!Wl3nrL{`HQlKodgLXmK7mM9ZzAoej{|V@#*-83 zyAZ~QFvkhKW+H1-qE1#{B5mIUSB1R~f6^P^2k7>sBPs-!j0Up@*fy6vXiz=yDv~vr zxO&x9Z`iDR4`gjlz|A_4NKe>eVbgCWvio}xSm^g*6V%(4-stW^uwlASUM4hJg?(ls zy-^XuColF!^wRFbpY&!`&#&q=TlM at _y=GO<uL$UO2Lp%^2tX`sPJgcrp+*@kW+ww? zCtFk}1M)Jlr7!C5b%&w@K6f-SF_3(h-lp0PskYm!w&ClMbWd+nZHLsQU6l^YOICY= zG+E(9dV<S>60xATRP~6w*j;2*gsj at 8ScO0@vTB>z3&fL1R&7(gh{{Xawh+8pX;J>2 zxGlL46Qjv at 8QV;jM%6WIM*_2~p``S#r1WqioxWT3 at vyu&cE at GWei+HLFylujAjJTX z1Ctb{W(-qkA|gHp;W(3FsG&@GhpqC?ObJV-WKWsOLd+_{HoW1kfzFA2fk409F_&c| zm6)22KL}HchWsAH*1(I0veRUqyp!=f$jSyfGQOI1vgLEcOZfxgU|J-AOInpL*ypxa zBhn_V9-(ON3}+CLG6BDRH?nL_WV`y1vNu34q-{aOi5s#!(An+Qo1%JiRNv`^gi!(c z^;WN~tuK()?(H3L+g$Ew#x at 1Y*4pQ_)!?@^?9C<|t9z5p0@l65O=Kiq*&bC_-b?@j zGLh*;ZJC-8RnM)eGBZuqw|Am=S}!}3hS0%?JHv!!Ka2-9K12`&x<p`U{jy6w)g^ya zKjh5_sQ%bOeE|o8_6FQ=g}QxdGHHvTUuN63Ao_%mB^yvlL)1gBJDVUalqQcuG24VI zQO!0V${UBQERz|vUWs{@Y|R(-BQR>ha3Dfm(l29-nmkJ@%F7%Lfi47X`4D52Rg3U9 zD_x&RXFB7#wTI&+G8?R1$1ZO?xjFtkt1$r~t2vR*c7^*Ag|;Lf<TS*=bfn1mq-|lu zK}M`LvTF|~0AyBK*^cTsRLAy2vObZlO(YxQ$*kQ8V2<7KY)1l_$qn9=_8nW?@iv{l zL~<aJ^jpcy7AxgwO~7`B;<?UnJn4!T$lMmsboN-utSx=<cC%dJc;T#?L<h3k6J;j0 z_rXnV)t<7+9qNWC#4*d>g$VQbQwBjMKb`92+>1D?ZmWo+A%Rpwoaj(GrocE5uwx~m zL#$+PBH5itdgIBgBwjia!sYPBahvQNNB;gqo1O&uJqhxA;+ at Ql%QnKY;y8H{<o6`V zkGNSA>CC8=!b=QRGOIs9%q)as#`C+9$dAx9D}Q1*h5XnsNG1PpGWltPrH~(5dmQ<_ z38cL7MyzqXd|8>p8}DmYcVbXw^(NAp38cFdq|S^}+A*9+CTeB*lG6S}Iy;g)j{6f2 zauU27H;$tz<0!j3iT6_z28Y at 8^Z~D*mJUdbXvPe@CsJ0rvLe%uH%>shK*;Wih63q+ z1o?*oneyCapQk4Rl`7K?`BzR}ncdiLleel^C!(R%W2OctMT7AIFc&6uBH+vuQTt7S zP`2F00S*lR&2sw&U=|@v&h5)$u-u^A2VwI}1j at cZFN3ZTeTInChzAPH7YW9bossNt z(B0+Xt1O*?Ls>pXc6$#qLfqktfYlKuvn9TW9tYH+<Qq@YO*r{U*fUvGmo*7vlDa1? zcstP->GsFl!d)~NzcZ`G at 3Um8yVD0UIEa%<W_9~zlHia=rouK0DL#_Tbo<#Na?CrV z;wZ>+)?HRKn1BY-Cqv^SSw9*~h9*l{^(Uf+dMry4%{aV?Cejv8op{NHk6CgulSxqJ zeG-<Fk+zDk{3MfX$Tn}0ij!&*Zj$H<8M0_63rmbs3MY|$3Y$qTE;Ev$Pr*&R5CKA} zJGteR(e0PlG<E0eQP)sJUL8<Buv2*8d6rBLz`l+SWXg0m-Y&`N#Im734Dk(lvhWU7 z#G}$vRQ>?=UCj*jFp)7ymIz at ONa@T#_^`V-8dTR%L|wc6>gwxJz-jOLJgQNZo~Q<w z*N@(XJ=0_>>WP`9DymYMVH|nx>A|SMARLU}WWe?ey2Ii09;_FA9y|Y}VQ_VO?0A8y zGYz5PK&ad69)Q}&LLjU;EIpHw(jYLsdp3CSbolKAzIBi93m|6_eslN;`9ibl{Hg`N kqeA~6pUaO)$Q<0r9PL{_SbCB-&)5C@7G7Wc-0Lg;H_<(7%m4rY literal 0 HcmV?d00001 -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 12/19] video: Add the Rufscript handwriting font 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (10 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 11/19] video: Add the AnkaCoder mono-spaced font Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 13/19] video: Add the Cantoraone decorative font Simon Glass ` (7 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This can be used when a a friendly 'hand-writing' font is needed. It helps to make the device feel familiar. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/console_truetype.c | 4 ++++ drivers/video/fonts/Kconfig | 9 +++++++++ drivers/video/fonts/Makefile | 1 + drivers/video/fonts/rufscript010.ttf | Bin 0 -> 23080 bytes 4 files changed, 14 insertions(+) create mode 100644 drivers/video/fonts/rufscript010.ttf diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index a18611b..405e065 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -446,6 +446,7 @@ struct font_info { FONT_DECL(nimbus_sans_l_regular); FONT_DECL(ankacoder_c75_r); +FONT_DECL(rufscript010); static struct font_info font_table[] = { #ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS @@ -454,6 +455,9 @@ static struct font_info font_table[] = { #ifdef CONFIG_CONSOLE_TRUETYPE_ANKACODER FONT_ENTRY(ankacoder_c75_r), #endif +#ifdef CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT + FONT_ENTRY(rufscript010), +#endif {} /* sentinel */ }; diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig index ba1ccca..d3bf742 100644 --- a/drivers/video/fonts/Kconfig +++ b/drivers/video/fonts/Kconfig @@ -29,4 +29,13 @@ config CONSOLE_TRUETYPE_ANKACODER License: SIL Open Font Licence http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL +config CONSOLE_TRUETYPE_RUFSCRIPT + bool "Ruf Script" + depends on CONSOLE_TRUETYPE + help + A laid-back handwritten font. + Font: https://fontlibrary.org/en/font/rufscript + License: GPL with font exception + http://www.gnu.org/copyleft/gpl.html + endmenu diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile index 58b1813..a3f6ea3 100644 --- a/drivers/video/fonts/Makefile +++ b/drivers/video/fonts/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o obj-$(CONFIG_CONSOLE_TRUETYPE_ANKACODER) += ankacoder_c75_r.o +obj-$(CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT) += rufscript010.o diff --git a/drivers/video/fonts/rufscript010.ttf b/drivers/video/fonts/rufscript010.ttf new file mode 100644 index 0000000000000000000000000000000000000000..887a4496f3c8dbdb62efe1c95dfd9b2604cb732c GIT binary patch literal 23080 zcmc({2Y4LSxi>y%+V<XewY#%jb+x<NqTZ`jEUVdaSC?$b5-RRCU}M0<V2TZ)cWy!r zEf63fKuAJDxg@keNFWfn$t4BCr38ESd(X_SWlHY-zW?+8o=^KonmIFb&Rc))d){+q z2}BTtnmCJ~h|SH-eVrZ8%H|1TtrA*uS{j?0i4ZYG5Q87Uxoc%_acs1D%P>KpNjM)E znHidU_pwK91VME`ThGY$MUH5sQv^ZZ0N3e>p at q408hR!M--UDO#PqK5KRv#og&>$| zf~dY_a%^byZzmrahikb1<Rmnxa;P(*FF&07CubISPTXy>!}*Wl_sI0@$k5-d>Fc2D zTj2W7XNGpp5w{Z;!?jj8=e7;aj9nc2>njA&s3i#FALnKl7M1aJXA#7j&*1uh5|~%Q zcIDKT`_9><sr)ZOA;k^E)6P5Z&;C!hfANWAkX?jxDM8{^_<`rxr7u1q2+8y5_VjAW zpxBdmQTRI|w2TlJ5{z(;pF~1it#A%!)G>51zRE=DUqIUxOq4kelKT}gif<+f7UY!q z5$MdGbdNWU619Yi*w0Sz6POZO4%agG(2o&Y@txH3(8m=ZnSF5Sed0=To^a4tLwg-z zpsyt+>2jh5Z6l_s^N29~E|QO@|3N*ReueZB`^d+MI`kW2J^2*TMqNXEkIW&ir2dmI z(+tr?>j)3|S0azzNw}%kh(g*)tfA)9pQ9Iv3rQEDr9<h@sNWDxG?P9_uOjZ0JP({T z5_<X|QAw8(F?x<@rcS~$lSDJ!L`>0tPk)NWi4{~iQBM7gaMMdfGvg;}nT<q<KAR|_ z<_V5T5o+pbVm0*vVWf|xe at 9<HG%!k{jD3@E(gQ?@IzW*0r9>^&Obn1`0Ur&-UZ7<c zeGg$omlFfjN6 at d7V5l~NqvnV_pu3O!Hesi(BYf1$gd1&w=Va*)s*gAWJw at ze-iQ0% z2Rdy;fI0 at _K1x_97xW_|wB)D2#{^M9T?=%+PDJT-gq=<h0r~)8C8vo4Xa_L{|5woY z#CEKP%x_LcKONA5HI+lqL<IgzPgxb{6MO^zC<zXZQ2LYfyIHN`X2hOSGJ(KE$YuM$ zmxYTo!%Czwxk9N@YqUDO!DupDtTwwN$LVr&9<R?I2!?X=@(T)!!o`tjtR$XDrb^4o zD=Mq1YijH28^FO^TH98%cXW2G>|WK=+t)v^dd=Y4b?Y~5+_ZUUSRCj7?Oy>;2<xfO zC~*raMQ@R}P%)~HI+uEwj?sJRpU{72luSKy0rLoJWRI{POV&!hBl%c5Ed8nUpR%>G zt7IR at 3*{FmIuuVT<H|+leJZ1Bv+8&1i`3s!|4tLp?ACm&?a-d9{i|-H?g1UI59{aj zHyKz%pW)kvXN`n0X`D8G$M~@EHB+hSEYp+bQu8eq&a&U~S8Kj?zx5@X-u7+VZ|!0G zcKb^X&N1nDKBq6|qMVPMjn11~Vb=w&pS%6;9&qxl#80Wmm}|f)Bm`m51*m{W7kGs{ zk2z_gR-U+rY53w*iq2jAMLxy^xc|!ZXXp*)*Mx?M5=4OYc|&0oPbEuY7PHAn1w%nH z6b!`^nI_yQVNFJh(JUUKoAUiidAO%4Q6!OW`}RtWOlvb!g({WNRH-qLC}^gU!Q%AO znz7dM7_#oGM}GOiCUoIlSIm<rVr13%srn9!MWdU1TKl6mrLwBDL7>Y)eMp8_4Phkm zz!1YI6vQ-S36lV!0kIJbE<-JDM29REuN%=xqoF05%J;Zj{GUt~pNmNv^{u$U?V>}@ z+q+weSte$Z?moP+Cl)f|hA_)UEJ8y`kRZWddg(RHF+xT_^gzI}8>NCiFXa^pu>k+6 zcrn6MF&PRhW(jLCm&8g80zra+L at 9-WKA{IedLUwo#k7WksU%$SN+{tZ5l8o^>G#z) zmKfhRc{Z5_^YV%msQAXs2q~lrNr_kD2uLMmEP4F`GHW;$wNWcC{lc2uofA=6EicvC z+e&K-I%O+KsYPOB^i*P~+*jl*m7{Ws3nZN$?}bVJuQHoQt83G0wRA<iD3pqkB)ak1 zO^V_jcEeRM6S}FNxgbAh?r46KR%0<2EA*(`rf&$l3~FC7|DJJ-tb5HhO6B|c-*ln} z&RqjmW=L<Kn%R0nPxy#kAgQ>|t3kjX*a(Qo0`fu0L|kAq3QbXv4}~un!I1DB*q*Q` z67hs6qEk(ljF3RW61WU<g=bTdXq*Cz#Bv9*3#tzZ;zs8q*{V8+R>mlX#*q{`bPive zR7T0P`n=<Msh(18N#<#2t%IfyDA4WmI;w86U9Y2Ma<$gxDv at 8^T1L@9cP;05?i{<! z=5CBMhutPMWwFq-gT4f{bw?V#1~sE{x)cvS<T~-ThszDc5vur=j<J*Wx?Y;{NEFUy zyIrkLAWs!Z-gs+c<A&`cMtxt?tYqpkwXS$oiCO0x4(E{)30hs(R&DWDY&c`QIbc;> za-8PC?ipf|7^Vl9y9f*TV>@I<N3ivQfWX+ZUg$y@tBS@_S=nNFN3bj<;J78i6Q_#^ zOI#3j2+I(}CaMHK6vYb2xHS?aDRcwE#~)wB2Q~`A2DzgokxHUF^Ah$P?=CW!Tq&13 zrMW{g{Z^HMc5+sRaat5=lD4=#=yt89jAN`0E^mv*WOmk)YP0S7{Bc%l&8<;V2Dwx( zadC(WS&>X4DPYO4^orcNhwrfN7&anW-9)0!)o7;PPi+dy<S&I7x_9L~$=Ka1j)&u; zo@tXl5iBGXa;X$CHaFQZaT3D4)R4b+vT9|SM5?1*Zhz4kw-xQ6LRC#Bjh`l!QWa$| zQjDq0XVTLlRHIbZhS`_P`v3ANch!DY(`lwj6}oSMl&%IG1YE31-$Um!ZNw^K2s9wb z1<O#BB1i^-Adz5ySX_d&3r2d%d_^0jMcYm#YY;Rn8?NDW#o?~L-xX3yB^$bEL?gAb zyWYnCnpAh7+m+85)!h8UNb91ca%+vH#>9}3qItxBbZx%ZMN$WLQHAnQL}w at 69sPfC z&=h0eywMsDl{1boEm103E&KKQftK=_!2ti33Ju6tOAE_aA6db&jrVF5I&M|IUFjmt zn?JUb=pAiEkxHrl;VXw}MA1g4fu);|T+6?=C8VXv+Pb*Sf at Oms$NMwd&wNH0Ai{hd zguQQ=tzH*KU;x|#u9DUQytaiICTf=LId4;MJc#=Ur)Q`<whZzX0%7Z;L5Nxl1BW0n ze<(;{qa#XU<d3#?@Sks9a}BcPxL7Iw+r3W*<ddl2Z8e)xg}9P?Uw`gNKK-}f7n-f; z%;SGSiuR`8T!kJJ=v)MBnq~9x7#Na;aYzZvfImU!fus&_DFEZVrBc6*3MLhSr}y%| zm9VZHWV>c?6aQH|n~V6StAEqj&VT%u<NLww3g7xGLQg*T`n at F{-~vcbe?%Q5&t<+# z^uh_NL8nXvxHx4jKCcg}UUVq0=*Oun?1?az63s=hGl(=Iuz@g8n#t2EWMhFmlbfrn zw`t>uQp$BSRak5Z1?&wmohqNM@)a9QzQ_t2qV4uJkGoAlIgC2F+#YS9R$JwAox#Ek zIV)`l$-VhT4aM3yYgd$}0y-7DDz`M|?2u6&n#!;Cl_`{FshgGfR4RnfdYeRI(kU5b zY9+nXAkmpDc<zDHFB1vsHo6XH45AANMxS*K;9NisF;WWPF2c2tQ$)c8?FOr_*e`qU zCI#BFy?vdPQtN~EfVM!2RG~`;a|)!sE`{<P>gfTK&Ts$Tn!T)T1w&V`68X!QX}z^~ zjTX9Szu6jaDCI>+FC?vq*q8R9kI;=o3}}jp;RW(ElK^j+9Fx%`;3M|dO!TNGL)Z>L z*621viZq&iI)hH9@!1>+l`fakYGn2t2IYsfx#)&+3rX5MUL#1;Y%M5MaREdkjmIbF z@|Bdd-DE$mkksTzkg~eP3JDB?`Obahwd9$E15Sbv;{`d0qn)0wkgoZ${7^@pSE*yv z1ry-Y8~yEcM=p)-_p?fg!lKX=8uFBsC1z#lVd!@~VMn`A3*>8G%hpg`en6op>aC84 zAs4IhQ=0LXiWsu(t%v8 at U?%n^t0&x8XF_rZX@`WRBB$vJq+zh5B)O`zQAw9=Xt0D1 z4iy;@B{e6MXve;1Dp{n{QHp{adHzQ&JNR4pB@#(FyGns9D2bZLbvI$3Ac^m#|3aO| zoK4t>A~+X>55^IL$Wx8<sZ2JYMkuLIW>rP9ULnmyXO2)vrU;QJcX0jL!^mlmN~KkU z{43Ghp15-u6-WN<i93c#O8e8*218v%2boCTjih7rP-zUwcJOaKiCTaB<6CI@#veY( zKlJ$Hx55$dekBn^_t5VXPQU^3G+Pva+n^WY9R}+ESV5zznxIzUBuUOqDxxf#pTi*r z{ris;%G;Ye)WO|wSN@&+C;UIxT9u_N$}NKa%|tczJkvo`5kv~ZRy8<qhD#Rk26`4k z_p(<@NJ<hAd|enD>>~<y`TU(lZ(M$(nf2-&;pW+<7KNfZpkQNT$ESw^zMGKb<=c_A zEHYjhPP%gU=+L9N49OqdV9DR~<T<}x^ySoAmHu-6Vk><RqT*y~{UgK8^5PHW3jPHD z_tzb)-4M*L@o{pc1>0vAQ9!-JloS0Kngwc>bA#nbC9)zUa2UY6QINo?R3r;=;96Pt z$NXZro0F&|Do=ZTUnF at fELT}eb{#%b;``~?HBQESyt8sRRzs3~NM|xJAFLg?;_&4$ zZTlPawHvgQGkE;)Ub+07#L-vvN*zlYw2USySE+TJt62u1ukFdV?y(f;&7%QhqJFJ` z%FB<-<&ShY*K9tJ7wE2i1W>pxbkuHX^o7F(v;$XeNWw#uQh%VoAW}pdcn;PQ=p at S( z*1c#KL^1_-Dh6 at PQ%MYf1MEZxyhHYn)#}CGEP+;hm7HObO<|SF12=3g510~8l`)9! zUDr<2O=g8!W$@<hKhxh@SE6MCh9E)(NxCZ5;<1-AH95H&&DPo+N~w|2@?4$S;jKb1 zJkO3q0{ISearx%E*C14s)bKCqZAMad<+=m(!|L5I6>x at t9|W@^Gx;VnN3?+cuys`< zAT1KbqyeEgUBo;BwqdjZ4lKX~j<JHCL_KHulz@%mhpy_rXJ4;s-NyB1xfE%P7E^BH ze>Cl^_J&B8i{{rl&7|B$v96UHHb7dTx3UH%|Bc6VMwO|8qV82#9y(jWt>2Wx(z$mY zJ!kmb=3O!}Xs|PEqNrTj<+1fU{YTYgb(<WeIubeN+8Dn!->Xnz-MEPyx}3=;Ohg=5 z6KFi$60y96DHR5A+83ovHK!6)F~8EHyDuSK>uNV0ym1A|PL&q6hXx+<x+~)A_&;m@ z{EK(`YVYDgckqW^KC at vXy6X%dLxqg?ZLuP$#K_We6XnPmMT(Ul@_%S)*(+=4ZM%H( z{Z;edLW>~h!t at u^(+tA36>K#I{j$|ynPp at gNTxv88G))%Mxf|Q`B{e%rO7|oH60rA zm`f;0- at wn2agKI5OvOgZzQcJ~VO$@`@%!|?v(Y5l=SD~pG5Zt+>L#r*ky}vG>f%_X zvRt9?6h8ADZ%J&bgkmMG0<9q;G=jR80qUpiUzZw|8TA!O3U-^Z2(WN1%)d-gLGzf8 zM7#!U23rk!#OiVtteCsV$@bLNmX!27!4BjW6(`o`X!A9;_~viF9k}G#7sI}Jn)=Rl zmyuW8`wk-ic&_96Rj2`7))tQIbtQE}W>af%z#2k#8T3sti?v`R9<0rg7qx8LyWyZm zvZ5fDf4+NSpX<l_k6lWU|0wI<nk1)_xa<gX@6Vu0Hp94xe4-vW3&Je>>yGLNVZ%g7 zFS;sr;6$7P=CFFQcV~^AyqBaYBxhwbTk at RKvvFsaQ)%YVwF3iVtuBNt+Hk%*Wu~5q zc^pXYR~x-0>o<1LhGCg(kc=qfbuo{HEs01{{%)@?TvYd-MN_)OfBN1%JDv%6CUvD# zWP#RZiprH^Dq3moYY54u9twe7Ur4a%4tgWRY8-V%w>)Jg!leJSS_ at hzfJ#bHFH};b zl5MHePpE+aEyQxty0`wst)~ZeEH6TvreK^T<UTm{$c9uSo0$=UiZO^`7{k9AV1Vo= z6O|x`$7Y;`11O@D8cLDT<VSkQhtz(XBd*dzKBSe}oopVYLo$C)RY{>tI#-sPt3hX1 zSP*IFyhfW0$t>3VB9%KpLiVWk_~q^b1tsY)*^enDH4X`&S533E5nZMjcjs0{k!62< z#0&Z4r??7C-3^uudHQKl5;Ap+OrjbsG$tMt?Z?E<Oz!$9|H&zUs$?$z+0y83c9P?~ z4@{vNN3`zZxA+j#KpykO$FyBlc1zr4PMEovm!>s#t8E{*sZ^&jZ~OCRxonFdzb4}6 z)c48PAXkHxg+LJdiD*z+{LC5|_1|4%NJadMX9o&Yc8Rg}_-nDk%ldm{g(co3sVF(| z%l<-@oi)}SgZ5*6y>e*(3^`1U!lKlK^@R#5$Ki%>kLHlW!YZM$$Z)C=Xhi7~=})Qm zsV*!dJjpN_natD at py!m?XQLQaC1a)*3hNm@#D9EY#1lfjli&ApZpwD+=#H(|7O4z_ z>uy#d^&YZUyG177^q+^c26Mx>d%w+U)6%pz=7#!~uE4?-hUxhj*w~%vo2e75hbSg$ zfC8)w66*y|*KoAQI>{D#@cb%kTcS3<YU||Rkm|*cD2oT7Zvp<gP@;BBu9)MWmr6r5 z9#_w5wq|c>v16a$1U*%8 at Q67!Cwcqvzw!ThzE(3Btx)K~1vM4j=;fixP6K%h-HAwL zE*DQSl^*ry-^q9gb{Fg)SMYC12AMR}sLlo2F+ at ahkqYtzG0Yf(K8_-Fzz!3 at 1}CZ? z3sV#*E-_|5ShU#>HptA~QJhOeq5|s~afnZd_ltL8f#DH>0Fqv$N=@yXm0Q~t%8uiC zv30*cXMw-=7q9QGC!a-c9Vy%Q(qV(Cs(Z)&rxM}Hx*Q{&)3l-Vx(ipXyf@%)Z{6E* z_CR-4nUu0}(OpLa`Rf)YifI1BuI5;{Kt<YOeYgC0<p5`O8B#iAi`ZGmdHoUYX3Ye7 zn?_lI`VOK4Q)Iib^8)hMZvGYiG5*(oczIDje#q}ms!6Ra+Me$}>&VW5En5AN?x)UM zb!fYnQ&UP)UekvDLwmO773?_tNZr->iYT)?`H)Mip44iRsWXOu^xYPhLKEIh(ats2 zpM<?Frg84Z*Xo+ld!Pe-dXZ_NKLqRPhB8-}#FE91202OA*Ah at qO;t-oByuBM<`T+; z7yyJM9gr#JPsZU{cv6ipGSJV at xdRE6Q=*BUcW;_V4mX6H3YiMA6 at 3*$y~Q;vY at Awa zb`<a0RZ(Aei0W)2M@YL<cH(6dI<xNf9Foi_uXR_34$drA++Coh^Ad^mx0RZ#CTlQP zsY>LQ9k~6O=EaRG^is9$vy;1;71laLh7DR5!|*42Rnh at 6)o9y*wn$KW&$_FIeJ1c# zil|I~N}tU%!HQav!0VQRe?s;M^g<OPNRilEZ~z1M$--OKma^8DMdal~2usUkB+)}A z^7!XTrlXlZ88kT&i>{x3JL#@v&b=r{Ti;NR7WdEIJn6PobN*e`!5nRMTUGO=<0Zwh z{sS*RNWQo47QOZ$f5c at SJ9(@U0`|R^dG*!5-&Wl;GAC2SSFdZ&E9hS{s6(szb#{i7 zD=h3odb!tHvQ(KYDT*wNoEZidAuo!h|4uhCGK@#UoF|E(Sd+y`L|{|*WX?sFe8E?5 zEvrV2C~^5_PAN6)xr_gT|BSSvckieUYC?w&$%b!ZnDfp3$JT6HLT8|j7gur)nTdaq ze{o7CU8Rz!qzZ@MPZ#6WE|O?Ye?f0xR=~<KtmGQ>7CPftP&f(+={>|VhR70xD at wmD z+JWlF%*vH^%bT~>^0$!nQrShwjP^~G*XP_(5I=dcsGsgW at t`3|>DI>E8xi at 0r#s*O zD+&4sf8TotcAdQ=PI1i#f%i4(|Df&kr$i%Ia)c36LaZ;$FfLPLU1Fp5ahUat)0c22 zV*!GeEDDHrjwz&GKXE2YBQ`gL6k6R?S9f-9e5y35*Sm7~pZirBnwIZ&2bTts`DvQ> zGe~9kvux<&`!>xWlTxaHvL#w|q)!#i-4YHXRGD98f=GKT?pFjIF-pF<|JzRcro3)a zDPbhNCY2m!!N5av`aNnZ{SlFX6PPw&qOxR*_L-fZ3XBM*CxVx#Wseux&1J4Sqprh} z<I>4HQLw-E8tX<ABYFJnBaQhI)!}H(s?x1`l5fvLZ-%OG9!#xLP_<nsjm&2a%h%*c zqtiYevbzjtCfoLEj4cZJUQE|inx(7hyCJ_N08BGbx}0-kYIO0GUZf7wEYb&22n$@G z(@kbkHiO?FY0hFcnB77JkfmO5At%x*O%kcg><L^u*1f6jN1Affde3gBt=))%EbB5z zS%l03!N8GSS11Z at t4yuwtfR?~r;GFj#jha0!(o@Q44LwL%;u5`r!Cl2;c?}f%(=F} zR%Gp3k2YM@%cyQw$fJmHDSz<(Zq7Q=YO6^W$mNVyQffB3Y|dDe|F<1?7*z5wEv&`i z_1RAPIc7bvA9#m|Dn?cOU*t@}YDn^QxWpF<mTUc>nM@{tjWL_hnZN`ElgmUfNcx1@ zD)<M;L;zVq^Wu!w4XEn#(*C%_A!oIu#?)ajm9#epIjPbW&2?BxXcjRVT at JI+$5MNl z@8(Ns<Z4yXYD1q~>eb7UoOHU~a_X at WzuvxCnbPacrdUE(C?yMQ#sX5wDy1R2HRt;L zb7 at 9d?98_WYbMmG0al at q>oqs=Z|Uqx$11ZuQswrHskK!o<Yw8kXi9GB+jwat(CjHN zlAQilo$==;iq=}7qN!v!O$SK^P+Q2`lB+Ql^FJTo6H?U&Z6<}uu25`wYV+)Fqgt@} z73nnE3^jeITLVOBu^0p)2&pjy7j#CD+oaYWqaj~0ntEoD7SX>u at -Y)R at z42zx4c4K zuRQ%<RGhgAYFmKo@U&j11&MJ~3bKMMR#POku{5S%z5UE7{+%<g87L`kRcpqSGAkpw zbjQhG@=x*?d4tG0)?ZUeYI>?R-+(ZtA;O*h1*|rINR$DlLXxnoCg?g+a!R8T0?L|@ z-({?y at MXLgdq5(-hRV}NU6wL)5d>Fs-TBW~f~d~mXK6I|icC^evW0(jrb;c3Dm9mX zu6FH4yKdg{J)OMrz~S?HFZ+Ft(|+9m|6Ho+^)sVI?(Pv}T3b+C<Zh-5ukZkg0&cuX zJDGL@!a#PJQ>dV2=n>2lz*blV1bT!*KF$uQU~^Q9$o8Polus$hrU8qQB<&={pJ~Vo zHk6L!MWq%y?J-Ijj$$;GsCZ8;{~=GD;iQx<S&lWKH<?k6jaG!^a>vzXRCtg6p}qzN zNsK^KE&m>!X0{W3Kp;j3oL~ucO-VcoMs5+7xCCN+kuqc!D}Gp7qJ*<R4C=$78lReB zZV5rfjGYJEq1gk4&fc1tHCn}}AElfQkK6v6@6(jVMjFTBCM`qK%65~!AZNxsUaXWN z51O#D((Q$IsZ1r8YYZq}={NKLjFRdd7k_KwZ$;bs*0;;iAg7b(n?0`AAM){OK6Q4k zY*lj-z53+tyX~EFN?l&{Qzi1yOOB$l&WBze<ah7~21e!KqNJdQQ2Ge7hB-o1fF3e- z3{e+?9~L$?OzhN9tTP#bU6|!#d_)AKsH|ccF?8_?6t(%w$t2pf-9jU+#})GBBE%HU zGgr%GTRZtR{9lYZD^nVz=@C>!6MD at itN-K&{53jq=XI|-%zO3zin^<;Hd`D2#>pF% zE2OpPXUHz$r}#T=BEv3<+J|aC#qqi*&C@&RpAcHY3t9pg6EKN0B((y#Who{+pcbq` z^!)pjc0V{J at x<m$1FL6$cJqUK4&1rK|B+v5w!9&usB!*5^QP8k^(-nphKQ|0#UE_F z=+Ubl=IfCHt!!N7f-$-DKdBp-BB(+)<17XAO^V_X1%!nphzV%Hfm)<VjDq4k^mOdS zSHZ&{*Q@e#EJjUv?8tRPE^AF;Zqp)*4CdM+H~iG1Qn)!YXPte2VkM0#>eo8lR!4mz zC!`CRrL06>j~+l1?Wv-*u|jVyEf3aTAd^-G^EKYy2UZN4l}<;&Z0icQnVg&7zp-8+ z8)T$vSkKeY3CpVU{|<SjIz7o$(T_l0Sw=urQ&?Bf8-OTm@km(L!XB5<W>#frk-O9R z5 at QK~d`1EAXbqyH<LC8kn%yHaRNqs~_ec1{jqyV{2?n9o4Y%$dn(0CgyX}ElDsQVx z<}NNQo3{HTi2wZC-o at yeaFyGqyKjJcd^Es+${#krUD<fX$wR$iX==-mLJ5l`ckK@5 z`1YPLyYb|aL1fCUTjDsquA*e9caBnA<Ne!s^X6Pw(rhgTeXUNPpzdOPM4^Cy5&;6S zEd;O^!;Dw3nXJyR|BBoIGdd^=WW&GZS2Z%fhoZ=tg6S&$<gG}3<DGRjm)G8|c?sp_ zdc1DVPN5%;r*?O3)fp*?EU^VSK1HQH1LsI#es#;OlRYfk+0u2WB*#UPntz8S{#c>^ zMKb)?%Uk0P1(~Yc2bg0_e at wl>XmDLH1yMx6oS;~n$T%MW2c|ERIx!h#;2GktFL-`W zcTVsGzv0ejFWot~QBmfms4ZswK+~&GHwmG(b%hOE_+M{tolTAOCMtdYxZ3*M!RP+b z-dSrhjU%oo5Loy(mgNeH$0L<(gWqa~MJ6zZ^qc7q=<Dbo;dPDx#z4R~kPD<rko`cS zl*v7{J};?93N9h#OVDo*&!VT at k{9 at YP@EyR=KRRYx_8R&M)5q?C3-Eh(xC05e66Xj z+fQB!JU68OMh`Ozq5ykv20yi@z((|Fkc23UEDV8&U%Kn%hhG`C)0G-+BwlE+A8+d} zufC=v?x}Y)MRe+|$i~0=<`}8!X&};tiqZbPtA-Cvo#@Fm#h-X)|7c>WrHJd7cknH5 z0S#q at jQ)gC!~O_};UO{vk<tQ@2!pkR#2Zf}EV<WHdwdIj<%<I`)HyZ0>4|lFN{{dl zvIc3eBou8!6L*oFN_z0*clcZGL>jVCq53v|{>fRno|GvCY;~sJWS?cML at mz4LS!)_ zDIrA&WmF2o!W2$`54{K!f)Ml;C=cT!fDiyUoKX3^Ow&^6{`TN`KV(^zLS-=f^;L at Q z_|G_{zApH7_c`TND%lQ&d|m$U>epV_sZ-a>|Inybn$@PJ_2>ud!u(VGVG>#7a#n8g zx=T{%;1YW3>=G$jd-9L`jYnSmZE{eHww!!n7m1FLV|uF36F#9|V~`_~16hUAKcugs z{|o2%cq5K5QGwW;)eP+Yh-G3pmYcYS11w@((U5S^<b3Oo6#tp~>Q<U9`1aF-s<*m| zEt+6{{_%;bA6`BiOkB#JFmfv|-_=kOH&(Om)vylej56f!^lMj@#>sQ;(4d at ATWwkL z at Wwlk>Ww>Itwc}xe*e&<3uq(L?^5T}|G+DRIM)^CkcMUd5mpPAGakXVL}VuC8XP*t zb`QT^pBs*(wtRrd*1TL at j;|=CZEqmye67{4H^jNHY<;W~t;rq!t}oQP?q0<$w;G)h zP0{8*zx2G@)mQhVO$NFqiQ4pY^u;vnyu}%vK~h5oQf^X%5EXDE4PuIs3^Addwhi}O zg_QR!l&SK5#y|Ws at -4S{-M^jmual!3bnN7Wm%db4EK_(D3VV3sb^b#BL7O}|WsG@O zp*PS;WeoTofV~JY*oy#_U~Dp{VT1v1dcbxo;03AE4okNzj=X+Q>t`=G at TrpY*jP;{ znK<#M64K5b-=mY}vL;WHQvT1~Mw-)@DiE0~t0TM6mB<bl@KO3d$V03X*1HG~pbsPj zQ0I*Th>Gi4xM04Fpy+RuB{16F^*Cyl!+b|y&Xt_gB2C0{%P6TmSXVxdir##UR3Ida zshF#K&*#7Ud&|*tcixgGrxd``-gGy$hRqSG+h(0MyQ at vGuz*3}Cu?g|yV6FL{OLp@ z8Pc#e(yRFNzy(Y|ci{Drr77mTpYZSV6DRMjmF1DB!mM%ACgxm)vdLp+@?<(t4UDym z9z?G(pA!K%IgJA`284n*oQV?5j)*2mug>MNYUF0d#;8?pQlip#=nX2BG`RuN85{F3 z)!^^w%nL|a%be83tsrTwm7*RfFe;7lnMX-HFyK7 at OZq^11?;XR453uOlJxlK3Jw2) zzuF4ZG|(&kI=YjM5^%S;!zVjFhFo;#6?CAvo2qX%TWpSD`VC6bwqXpmL<VTG{rqGz z*q<BLm;O%O34Q?63+x4W@fceNu#>G6oMtRRVT}*hzI<LW@{4#I4dS^keq>$cibH>V zXs*B$ugsMz3-zX$O~dMbAd&V|1gz}Ab6jJ7d*LxMK-0AolVx$0x_Pm)sgW%FR`I|u z|MZBX=$6Zdd-4JyR>df5zH3GcEzH`r8iqneqokp#^4^;bBzsn0Q)4-vvH<-Ito8qm z`6Xc|a)EwxAn8xywMPjHnkG~FP!Na$qXS&TW`{|{D-d%UbVUOiXi{!?;iZ<_Buoj? zj+Pfn<=Pn1{pO#wN3}lxxM_ik*0gZtGIY)6+lO54&^q~6H)^w^4+G{v$Hbi{UuF2& zs*9n79_QAQ4COL|{X@?8DYb$Q;us~?r9^3pg+%OOSvh5=YZ$IoXu6}O`=KrE$zYWA z76+VGr#C;cI$&_6`YPHsZ_9HO^B3 at +ERJ1t;N}<4Yzvlr3n8z?W3Yu99~r9e^;9~8 z2~+(KT)6xPGRY$p)FHMQaCa40g^<Q9ub4YvWyNSQSg;0heIrYem at Np_16II5S|(l- zc2oMI5uh7C2-zOlVO~Ki=V&G`pv~vZWvU{xi^9b?1ua!O@~=NKsaDOIXp&Je=%VSa zFe57?Nl&V)tRx1D)l`lQ;Nn?j_ho3O@iK}oc9Ns{d=-BWBZ)8YukCZF>57di2CBe? zNYbouS-4J_eBnV?T`0MZLf&FuD3oHM3YXD;AMASn18syo5GydRHL#Q<%>6`T#N?JF z#R<phsW*<0J};QDz>=tn)2$qW(C=4u*KhVjY6~k9jFMz^B+5fa{54be- at Mj0ci;!J zjfJ)g^M9Z6=AKQLR$WqFSj%a3{y?HW;*v{f)<jkZ`kUjWff$pDQgo5t+OFii+M@h7 z2fB~$zNoLnyk|TWO%p3sQ2BS+>@rG98yy~p4^l(WAtLPQ3Gx_IO{|3zPyv>F)`r3) zOd~*fV9BQ`O-OqZr-Ex1jtX#fEvw_x9UAwBeif3CN?6{PyEb_!r(C5lQl!<O(5f^p z2k8o;9+eWgTGlmm?ULqdDXLh4BzCn@CJpNqu(n1~9{Wl&%%d+c#|`er{lDC;Rc|(! za`!xgZm^jsqk>h<NF_N+4Qn^=HJIh{i+uydZBeBnLBggomK!ejd6lp$L92Gr3|<NY z+x30%33V^^L+~Hi&4Ops2?JD&vJ5Qq3mYY{!k~nthm~XyM0WLO8MY`1$Ck>~v2{y} zYfH823O#G}p*8P#kSCe)1`NuUfYrf2I1pxK(UD<FTcl9cpYR+(n+uA_Qkm?&pmhCG zV}dcRb$RML7tw|>kmQe^;fMKm{+*XraLlg#t#K>0e}9ugo}w`P0+v2P-OrW_UI&T@ z2s=3pLUa{Z>X+wpSQA+lVZ+29*jF4+EvHAYVv1|GaF at OW?|y~)5V+1%;=-uJA8$@c zBu;j~<FZ-q*DJg_%}S@&A}v*uq+TLZ1T@AyGMR!3!Td)exmM~~&A*cuT+=l>7Az@@ zj_<R_jp~Ufs#^<kqBI$+sL5TDDG+%cI{FOL-*a12K}2V!Y+z;+4|Pq|a%R4W5_B_= z{sdLQUPBee+GV~H5HDb63mjzg3X@>3B7eb8s_Jq~9b|z!5u<YRyn0O@vS~?4WvqbN zFj!gRRo^d>9R25esAR8HI#(P~avPN at YtFtG52p;WKqc5jy8R at NK4xBnabbN_tf2}! z2JpD>i>=`Fh4^I=W-!)Ekx>%$RMk1m9aO%1ohwn+qxLXz10v<4cU<_GOO->*EROJ1 z>LAx0NHQ{)CB_^Xtg04hyXfRXeW&_e&u#oWu|2Dw)?7NO$tktMrWtjii{Xk7qy4B5 z3Hm<+YI6>F*9Pim0+nJd2a|_IAsQx>&Q8-j8NvvpfyfPcmv}p}>h!<3Pa<{d9YqL@ zuW1^zq@Icg6(*yzLiahW<pw7xR!;`a`YPs{XCHg)j_=>d6&pPTFiqS!k!q>tT#fW= z<*|aGLT)mll<HHOX;#bB*Q0Gnhn{}YpkM`j*#|WCGe5&`H$Y`V&>gl;tiw#i5{L#n z%~&e-v4^5k3A~X*Q!%aDsV_7sBoYk;xvI<DsHL?}=xsyK?VLNXYw!9MiCT-)Yu??^ zSXLJA+4+cmZ%t_;ytyBC3kfmu6XtE|24Yht_ZH@gSr<R$6~Zc&pxslTRuaq7C~934 zD+UFT?#wJ0z)=u4Ks@;qgHB7bk-8{Dmv*jk71%wjC9qx^_vPrTCFq>(>zCR)!1mEq zU1zndJVVELJ!?HVPK%!;+dT%IxwMO-d}~(eSI`%08D>T6`P<74%6Q8NQk-+;LwA at x ziAN8hhB2qSv-sL2ne2j%eTTnA(+ZNx+h04d=l+tCgS&QkRcdQMCYQH!{Y{mP<(H{Z zO?lp3GX7ja7R_m%dQiYS&_lN3Dr}d~3gukAz$xsd!CX?n5uVz?6uzfKO3R97i?wE{ z|GPK!txCD13bMeHV&&%cinULw)XJu+=Sj9M=ukQb0XgjablBL7&KqxhUCBrLBNF6C z+kVHp@rzd&cLM}X>8LMY?hX~b>~7$!EnqogOMprvrUQa1z9Pahw2OQAh0R|k<6q9L zWD+gmnPW}K{RuszK>W`Ru8`7lLwNkCR|Wfj^@@f at Qn4z|m><wN`XBkP)V`=?N64a- znmTlPrGkI-&!aCXJE!k$h;&(9Hj`g&wLP`3H4kW9eZov#NRL4M9A<4_-KT|hE}{WY z8=zrf5_WIkNdTAzsYO+V9@v#Q^DCD%qOuhYT>U<5(GS1^&>)JC{IhS*>$V59q>-Fn z)mRaw8qUt+96`IpR)n6=noXM;Cilc2AV0ih?9hqP>)moCbq`GeJ(d$S^cc{SO8`tE zJOdHXNVG~=EfMk#15{<Oe+7hTCWfp`%7sIK2v7k1WjTu82DN(9ww9{*D)Vwwy7(IX z>>v5(C=^A5I&aYLe$aUJK-i`#wz_^bZDU9e-P3*Y5WjHps#eODXS5EGp*l;xOSOZ# z=BzCjeT>ip at ja6bn@nb29w at R&>_K=J;_L=0S^=wu;DZC{cc^aKM3jIJ3bq9~#WH{k z>W<;}JMd=GQ#%sH9bFi|(EToFjsxH(%<9V2x;==}Bc;pTp}nb(WmYKYq0eVX32XzG z8V$MV(0BK(aobn1!HrU>+fpiPX|GpZPC8&;S<8L=#}a$~y315eW{E(THvMNx0ehB; z;Z0A0phTP#D62-<6+yi8E`Sq$smWr_6t-v|I at +DMguiEJp}ndN?a1}zd0@xjGr4GB z11Yfukvh6yQu8mkVcF0Z7+KS{(HnAk+K+x>5890^YdcpC+k;v<bZWcSmS?Y-4krR1 zft)JS?~>25A-vB~ocCew at vgN~fWAEM11*QL+Rb1Rq at VB+d<Ekf9Kj0w76oFwbE_wq zJcaM-WKN+?UYYCA=OdMt4ojs<xzwih$Z3wENz!lDk!@PkN|Hl#{iwW>Qp=PqVtXu< z#&6uX<FDtH2N}1<XQ2Rw<)i5OVGSu^7%1o!>OFR!N{y~KScxP;u8fF^^y_pNyc;_N zwJYqy)nqEEKMn1 at Mp$?dCF at OyR;or`fj?hR#8iwzBHqFtNU)9AshE%D;q!{O0U)3) z`g~Rr-~KvXOcn&aHcL=K`b}yz8;Y$rFe-=1tJXU9SH_AV2vd|=B42b_8?|O at u~nhX zFCTE$=ap$yoKm;KQ5e)w45foDM9;#`#<nM^4n(D5j_|jPhACUc`IbYSYcx*M_`oH$ z+;zeH5Yn6Fl*P3#6{#qmzd2X6F(jol#@4gFs>(POE#>g?Gw8KB{Q-3+Ev(+tVUz`4 z%so^nM0`sL*bCl_YmH>}bzK*p>6Xe$Mt5FbNI{ndUB9llOmFbVF5L&`+iphc4 at aUJ zYHU|)1OKv#4EckP>_O9^mTd<fI{6R$u5>j0Pv%*AldxwNY+J0uV6?!p#ilHdixD5r zvUVegRFrw at R4U=~77KIma at kuq9S+#aBm3#l;uT2WR6V)&Ku3W=S+GFUZO3MmdBqkR z!>ClUeUDmH+oQDy=eKPf+W7pp4nJz;u1D)mY_J~4kDmFxciSXPe{<z^8?~kVQ?ij2 zy*bWd8xwLnhGT7)_cXrH?I~J2uy*ZF1lifszopNl<2X_TAT!195@k6M*Tb_wg8323 zkzleS+-29xG0%wpN6)V}w?y>CMYi14HB0BzHdNH at yy$361R?&_ah2)h)tlDd(_fp) zJ*%U%*obPkZp+<J<J7UL(v&{9u50PFSi$k*gK~wF|9tJf!bpFax9jNH)#2k<zkw6? z!xpE at h&nhCX1PEU_D{S^N^oAUC{j$SAaB?xQ4-4*KlHFv4@Co0#0PXHg48Rkl?u0o z&ehWMOFKg6lVr4Y#ceaI)$H2YovVJlSL>>*u}cbQmRU(xA-9aAPCT3U-P?b9;j_IP z8M?jOHD8y~OUo)RLgdBwZuh7(HCxWPWMa?i+bVT at SmAb_EYS16me;`R*ziLV)|2Pa zw^ILrwJPkXnZ!}F4d5WVMtFK&2^YUo)f1l*lXSeM1tCZalZz_+a&0zr;}8XQNFF_` zRh628i8XGkmp<`F`Iaqb<hcsX2klhZUJ at 1Bb5H&@49+NLJkETXd|#*?U8k;oEKNwI zN615$ClWW<jVeWv#&Au;d}Y&hot4pva&x|VV0%)hpzbaxO_`<Ip?SHw{(=WR5=p%o z5ii(eNsFbrI!TShhvO{6zu#4RZo@AS^|D5TuIV4D8vxjOEf#WF4SG`NU9$D!MLXgE z|1wz!-&|0Rb?OZQ9~g{4yQ!q0Vr*<la1kN&V)0na0!+htY7&>{q5c3HuF+2ou~pif zYL8xHTqpB%B$dirNfbUfWXuU^v=Xv<eYce at +NPmP9TtlA81xSB)P{vgrvU}|zuL}u zq2Xw6@vQHnJ9bXfHCH#6Tv`gR;;eE9l948(Idx^^`xfKMU2#Kj=b!nvr=1QG=pN%= zL{a7)Vb%nM3YI9W{$o`L`5j&oJ~e3)0O6HRnkM1hC$>DIAd#C<=t$W5V71z%{418} zwi`yzOC(_4QS<IlwUi78$hAYThI{qpCS6(8y3xaDJ9ApgOR>yW at He54**YvULo$Zt zBuEL14hByNGi#wnhIglkQB4q>&&vjJ<;>?b!~V$c=}SbBsZHyo-p+(laT{5bkr+#5 zCB~H5tt8_$=uVT^qad;Ri$P-Eqj&6>`BxI_-g&yjFx}g!37VjPDZalXR#^dKd;rOA zyze1p5pk0${Ent~%CrtP^vhNoe-<x2Q3-o|_QWa%q|~HV8g;N9`398ly at uN=H!iio z3O)Roks3x2e=dP<;t-D!Aw$2&9Fv3uWxuO{BD^H7pyMB!kdafFV}|gPKg=An(Eds0 zSi;P!-pd at z2%Q11*5Mx|p)uT(Io1&p!wayYifC|o1jd~?MuZ-HKXXhHYV=a(m?9d{ z2bp771Sk75#|+U*E@X~bX#YdzSR#KHrtD(>GQw*Z%p5BTm*HsUSVyQ0k3-dWmY9P# zr|02a-wAm8cah-0PDfx~$iaT at 2z*j-)B#r);p!3`w+p{Z#0;R*Jgk*+uogT8M?=Im zI36Iz;Qtczh5H>MrZer8 at O1XBFdX4~_kO8uJFIt22)$0hQ(>TC7S5^(IehT=BSOy$ z at OKoRU4kA);TXP61LA2Hyhnupcf*|=d`|^#Uw~(3g>g!tZC1E*M)*Gl=R4pk=7 at v$ z>+xNf?=fPhFv=X9iO+_im1vlq+ciHmF}cX)j^uH%NF>E|Of60>aqV3D(#+6&A=f%J zKeUY-7~8fqF*`Rj4ab#}xG6lfjoT|8ZLgh}8Je07kIc?g%jF$YBV*eZ#zwiNZKGrJ zTuWC!*V5g=Z6BLon3~<jm2k83+|2C!7`Fok;>KsUEplT!N5<wB;c6I2>H+ at 8fx{8t zY6|2G-{#V at G`=u0KQ)IBfs+Zi1Ir%1>!xRHVrhB^4w^HZ;{Wq-2bM!Q{C>SRu{{d9 zFM_u$Bk)(W1%Q-hAf|bCeqxL(=T40Y$I(<#G*Sf8fqTB*;pyaI6FFTy5l}WBBmxOV zH@wKo3CFk<w?(0K5(r)t3}PE7mxDHZjTp#?D-w=IA{E`eTqF_+M<UUR$=St`*=^go zXc+GL?}_>PRqm9W<QY%+pY0oa!N2wqY{6XS(<j*c0-Vi(5N1IO*mGv!d<yi5t#Ka2 zgEg}NSI2?v9fB6eKnxuC=`84#15t?HhVLB}o?L|Irifv<x+px2t(z0>8il#kvi0M; z$DtoQ9=_vrKf?ZiN5(zO15KD7OapEauPl2S_Huj|_HYi=wgh8OL!TpX_GNFyREYd$ z?^zIf!(KcrP?GJv`4sQHU*TQUiAd`ZP$POLmdKdE2c`}q!8d#U=^T8SHmo;MHn~70 zmWL=|+!w~L>_`|5vBa_5uvdu^`s#RLs8Gs<-~U~0f0OR8Tts}#$_1PM9B?S=L8MdE z9!9ZgK?B3W^(Bz>7-$Upe&LrW5iNrwR>S*;eSp|20Wtfa$2!2yLHNHKj(XsO&-<VS z%QlOV*ygZo#sv9d<jzty1y(W#V@<&|Ot)yOVk}q`q$S!<C(#6bH^8`J|8?+YRU6m| zrUj2K>Y!OT?}B at K1sG@sDtq8+9sG6+^!33V4M2H^aE4p^;f`*&uNR)eXwAWq=s#H+ zPmd4(CsvE&0=9$Yn1SQXB4{7Wd;v!Ls<sE=3GAKNzVXqtAd^XW0^21gST~MhQ^Glx z*AV>TIEN$f|3AKl`<#Xzw*j&&Yf#jfh%jHq9js@J2pl|tK^A+&4xkj<muPXb(2|wq zzxGfpi>%e-`0`a>#r?Ly7$YDze4GFf$6hVk%2vUKux0gTXlNFw!sFt*Fh^J$_{n-W zT7bJSkNCV(un!zGV$i1;t)fuZ!l46G)DL~uozj&YV%fj`$WLKE2ln*UY~X)-c3A>- zK%uDJ1;KMifEy7hzn)`Y9z+c+qdUfR5oN?)MuFQAkm(Gxi at C}UkPJp!EFaP9 at rYPn z**GF*HMn<-T-e&i;CYM_`1tF0X1x$=4$~@PoOt(~ApI;FemO3Dc{~xjuy&TM=Bw0W zn!ag#eBYQLInfrg@d4W+#<gj|PI2rLIbI$C%WDBzG1VA5vF(3#RD3t4N<>QRbz-y- zIs9 at Y`u`H=F#7bJlK<EBEK>Ak{xD9!_p4<n5oNbskp2|Vzia_w<iY-#jdY@g;2dF_ zATewY7(>K6zKQ!8kW4ni|Gz1NHbF~STgK?QoO66#>(~~S?;R3&z_P>ECtB_DGv6dr zQKw=L-;@D-Ie)>Jj{OIZnvICr(Q&Sf5ie_dvv3EFY!ewhh+cy&2wRqzpJAPUnP2f* zwhtUZaWu%<-tv=}0vu1K;Wvx4S>OF8J~85n_OOfrI9J4Tq^tzL&OfFPUTR*>({c7L zhiS~$W-NtYp3}(X9Q^c6PAzbA^RpB4Lo*y4j?a&caSOBKi#vwq$11p8vrF6vOm^nS zMyD1Q=ck637RR`$MQ&)@Xt6MX8J!y6g&V|K&f?@4w>UOGv%t-cbNED<_Ka;Cn;)9y zx|fEhr$&GXVPeD$!MM0(VG<@u!@GproAKzqnbEoCS?F~LPlzhGF_<O6H0reJlQ=-8 zw?aIf${kw71kZD`!YnEesNBU(4*@~h=fWzv>KoZ!=5CaO8675la&``wnS?%py&Y52 z)7<bFx3n-ezBFAZb^^lSR=4%FuI%sQ>beHG)pb2RbzOaf6~dfu7OswM9}_#9nwguP z0zN^iFlk)eh2_)P)YH%k_t(|8b+q*jVh)<y`nsBWd%5P7JzO2vUDwms*3jQk*TZ%9 z_jIr9Z3=T-FGvWJ_uq+jJUh1>9b1GM>B1?t2SJ?+le0_Hquk`s_AyZF$k^0&plgU5 zfhq9+hSs>D>Dg@);-q!)G~21*rpCE#vx|k?j`=CDk;U1sD^!%)vQ`VZwrwNfLM{>I znn5aCr$O?)&_naoI82F~r)Otj;$074fI7Rakn61DxJWDt6VNE^mEgGk-a4#IdC`}C z<k<STrtC!et5*fx*DV0M3v**5Q(%;*=G=vt0m0no$G~LZmeJXfrI|74ZpY-*$Rt+% z0`P==1jZ=DF2Ie=41@4aHDVPmZ5x^fi-YF5*?DZc8DGde2iBDlO}6{O)1Dulg)u;i z*dL}A7sjT?!TVrfL6-Al3ro|BQy~59qgcvdlq0j?7BFDOiT<_Eg++Hi#WQr28E<lS z2iWR7P`7<(dJ3El1OU!6D>xkpWC8QBV^Z|eQ^U^)C6oU%3fe>|&&&$QaY}TBg6wC8 zc7a9~$3V2gm1RnSQ^B>f0Fc2KQ5Dz`vFZS_1nJEJtV{q8(910F2zVCeGULJ-mhTRL zol_SJxzVxdF>JJ0yUSxPEJ7cG1&q(n&U|V7FbA0XKNk&%uYoWDp<)p}b5NEkh7S%1 zVuCD|%!tXw#kun0;%spV7dO7S)B_bo5`N()y^e5+M_>6n0_U&~8a4n>P}gJ#7QV4l z3g0&=ClpYtRKc6A8d!nQ!CUJF_!dkP?69-Ica+*-r=<hF(aZ_Gx*@uF;C&h&yaO8` zg77YGF4PV4VeeKU<k(?g0O#N_$i?H3@58tE5v8y<rX1#$m9V?38s5dOh0MGjW^|2^ zZ8t;xt`(x`3aBu4Kvv%cv$}5BOVtDUdmm)+129Kj1GB}o#5(vEstv?O*gvwF7$O^b yFk=Y5F%#F?ah(+^yzq<95X5kV_s(XeA>2VcjRx^5IPv6N at NK~G1K*E)$^QZpM+Bh& literal 0 HcmV?d00001 -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 13/19] video: Add the Cantoraone decorative font 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (11 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 12/19] video: Add the Rufscript handwriting font Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 14/19] License: Add the Open Font License Simon Glass ` (6 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This font is a little more ornate than normal. Example uses are on security screens where a feeling of formality is required. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/console_truetype.c | 4 ++++ drivers/video/fonts/Kconfig | 10 ++++++++++ drivers/video/fonts/Makefile | 1 + drivers/video/fonts/cantoraone_regular.ttf | Bin 0 -> 163116 bytes 4 files changed, 15 insertions(+) create mode 100644 drivers/video/fonts/cantoraone_regular.ttf diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c index 405e065..c249f04 100644 --- a/drivers/video/console_truetype.c +++ b/drivers/video/console_truetype.c @@ -447,6 +447,7 @@ struct font_info { FONT_DECL(nimbus_sans_l_regular); FONT_DECL(ankacoder_c75_r); FONT_DECL(rufscript010); +FONT_DECL(cantoraone_regular); static struct font_info font_table[] = { #ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS @@ -458,6 +459,9 @@ static struct font_info font_table[] = { #ifdef CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT FONT_ENTRY(rufscript010), #endif +#ifdef CONFIG_CONSOLE_TRUETYPE_CANTORAONE + FONT_ENTRY(cantoraone_regular), +#endif {} /* sentinel */ }; diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig index d3bf742..3f1398d 100644 --- a/drivers/video/fonts/Kconfig +++ b/drivers/video/fonts/Kconfig @@ -38,4 +38,14 @@ config CONSOLE_TRUETYPE_RUFSCRIPT License: GPL with font exception http://www.gnu.org/copyleft/gpl.html +config CONSOLE_TRUETYPE_CANTORAONE + bool "Cantoraone" + depends on CONSOLE_TRUETYPE + help + Cantora is a friendly semi formal, semi condensed, semi sans-serif + with a hint of handwriting. Perfect for headlines. + From https://fontlibrary.org/en/font/cantora + License: SIL Open Font Licence + http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL + endmenu diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile index a3f6ea3..46137f4 100644 --- a/drivers/video/fonts/Makefile +++ b/drivers/video/fonts/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o obj-$(CONFIG_CONSOLE_TRUETYPE_ANKACODER) += ankacoder_c75_r.o obj-$(CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT) += rufscript010.o +obj-$(CONFIG_CONSOLE_TRUETYPE_CANTORAONE) += cantoraone_regular.o diff --git a/drivers/video/fonts/cantoraone_regular.ttf b/drivers/video/fonts/cantoraone_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..57446a27a0372942cc3321ba3e14123b0a1be1d6 GIT binary patch literal 163116 zcmeFa2Yg%A**|{H)s`j8mSxM5tZiGiy!VVP+ZoPqb|8yDAS4tBgjrTw2ph_VQrfqL zw`FuOS}3EXivnfu(J~5UkFc^x7}o#$oO`dfY$t`jzyIe`$=CPl-ZP%_?DL#)8E1?Y z<DZrl&04r%$?spA{|RHfg0VT*&RVi;_^5&PuP`?E&x}bIFIdvl`d!<`j~Sc45V;OG zXye*V>sKB#fZw+;R<QY?<Bttk9?%Ri>9~1#-n at R(hK)lf#;?ThQy4SM-mvzVO}Jl# z-_vn6ZaDnJ^-o{uu3>CtJL3;rf5^dW*L|VA?kFaOUc&RvLvX_|uCp;I?7;8JLpC0J z!rl80T#et|C~x^;2OoVzQM5S9r0B(rML$0L$b;5?_YLn^Oj>p-W15>bu03Isbh}|2 zlU6h%f8dC<8xOv<cHU$7y^t~6?>8NJ%(35NH-3vr->@)d>E3ko!JAImcm?V|<pHE$ z%Lq4S=417YAHMe3BbX6b!Y3XVPdL?&dq9^sv`oTTV&;<XGCtHQ(%!(A^W*t7{C0jf zAD6U}U8<B;O2<n7)GX8-s<}q<l;$O^U0bg8X)Co+?WpcT-7UJ?b$9FT(LJGiP4|v& zr{1A=>o at 6-*Po)_tpC3L$NHb?Z_?kU|D*n1{Z9QC2D72aP-dtwv>IZDeGG>i&NKXv z;Wvf{3{M&UZg|hI-7s!kZ9LTYXX9JO?Z$Cak;!eUG4+~arkSRBrp2Z$X1&>Bwwue% zGtH~bo6Wx}IKJSNg7XS4F8Fc5wFToAqovT|unbtPvD{?2+j5WP0n2-qk1abbUsxrp z(dxDatov9Gu^w+d!+NRp8tYBgyR8pd|89M+kQJ5{))x*JzESvI;Vzrr=CGY)JJoin z?ONOIwg+r4*tXcli;9XuMKwi at McqY*7X44r{l(jhKeIEt-fppn?CthB_SN>I?U&pC zX at A4M)xN95S5jGWe90-yz+CY+nJ2!JofLnMy&S)ux5uC3>*724HSv!@D_8szJpGsr z#djmcF7}=HcJ at o$yAk(pj=#^P_y=4Yf1By}wD at Oy0LKy>>u{VA{|t1T2YMDUP5cAa z9Df(3yu)V2x8ZJ>9fW%)q2BMHq+g=sr<j*L!wT7RsP%bPh>{-!4Da%v;2FmgJJTfU zSO;jdDZi+;&zTkVybFjw2gDzuuC1tJw*u{lfc9<FQHFBw0mN^h>^Jzss9%pKJ8|dz ztn%EfD*h<o{v+#)zry-)Ov5oN{yrN8-sZ6uwkm!H`y<l)DgFS;jsi*#dlv9N$6RQG zhrJ%Zlf4zchrJvBJKC|6KM{Y0KZ)Zh98cqT2FJ7US6Kxhe-kym45*((4IiQOQb7L? zU}!fmw3}Us)?5tSUxM?cIA4zRR{)dW6ZF`M+U=;l6s`Lc_r|xOExSOCU7*A+wDd{T zz74H?hd&bkm_HhS4XtjDzopQk47p2?+ksS{3M}pf93KLPB7wn^P}k+C_bI^i3=5)# z7E#AF@!kA7Q2r4-v!J}kP~PjvWmTYYfuBA@`=0|ep8}ds0nMkV;{&w+HOTcmMm5`! z-X&VP1GIVtob?I2CB6d`+0L!;Pq-U+^Wd6z>$><B{ur(wM|zGsJH-D_@&63qjPC3L zWky*wo5La~e+2N(2E0*F#1D!D*&<;2b7+zX)8WwLFyJuZFykn|VZmX=QHaBaqX<U{ zj#3<Dc;aN=VCAd{6kfyJY(G%>P|)u{aPMpuLMfHt)++FK4{Djo+5pWuJZS?&tJz$f z=drWd7|yFu!-**U4AgiP>tk1=%o~yFW;UPQf)Z|Ji`i|ggWZnf4qV?2=>NzX*`JW_ zUO at gJTJR7cd?fxQdkp0~jWVBM=b*KRqP1($+Jo3 at c>X#deGAZR#r1YrCM{dd3)pOK z!C_^KxSMrw4{PFn7U2Ud$fx7H8pmnu8~l9SzmgpYDzE0(vnYRzwe!c>{*dX3aQq58 z1~_;H7)$>5Fb!y30mw{%%mUmDfj16{e=g{H4aqpJb|Te{!0yeg2ynVVn_GeLJD3#^ zn*gy15c>g5C7`H4>0@|W3Tj)~bYNvB($B>)hGRZ{FTimXFmpBPyb;$ogO0bd29(#p z;?OW!7Dl;Vl<P&g0brsKWp|<MUIiM0X`8s-0c^XVUB+O&ehH4e4#(r*R2gasTuVBH zIFVW*^CNM%39Z(PyPu-f8t7PAKfR&or(MYTE;P`akd at t#-ltK@%cOfzI+a5ltwGvP zfGd*ZEoe1TDkaMJCCIj+oDWdWdnn^yDB~^UazTnr@vZz?l%%F1t^G05kk<Z4+}(<t z>30o~_K(^3p)sWRqbT7`UWW8Ph1^|>JX(}X`hSN~O0w)vl(M%Z%HEE2AK~tYNc$%4 zZh_PjR1&lcXO}^5P-!0nt5x8NDoF8%NHGS=KaWFV)o9lcbcGXgVZ?nqus~MhUFgFP z*hP4H336YG^W`Y}3e@#Il=&<CeufpJ3_I5VYFQud04{g%6|5ZGUCbf3&_+9e(GQ8| zkmF`xN{iA+?`}aIZ=#O(P{&r(@sU!;TS^@tqmHep!=#k|5z4JZxn7i8#*YPtkHhgJ z)cs=|REG)ed<rcnN9km1Uj=mKXz%k#^*K^~j#Qrjl6hz=tQ5*FMEjot_dWyu(Smo% zklTV>7QP18$!F<#vQv_jECRn+i4W1zFNNHgAvGF6RShZWg<K5adJxBSwh(eO200oN z-}(5x0LQs_b{>xNaeNoY1;}$1QeDlaLADNpZ0!%NRSEgp2(8rvt<?iL+sJmK<TzUh zSz8ZTI{>nF03cZgS(^#1)5?Fssv&cSLAx|TyYxZ!jsTpuf!en-BU08Nl>sSycw$E@ z9v2b-?h~zOhQ_`TZMYdyc`G>P4jgf$*CJm5sBghx6;d5W{%Y|1ttj~pQL+Q2+HtoO zt+ at r*z71Em!-5ttGxC{{uM>5bAe9r4l|ZxK0&3g_`rM8?1)zfkhn00BRVh-r&;~tH zma<l0)`r at a0<-0;AMKfjV*pxeP-v=sfZ@f!-C|&PF*Maa!0<l6uoW1#0>f5d*a{4n z1H%h};Ssd)5VY}NKoCU>js_HifMO6WIU2U0j{Os9-$2XWLwUQAKF;<54h{p92LsB3 z0p%)Sd@dmE0><kB?Iu9m0ceMSfn)d+fc!}u&{h0t9N;<rEVL+x#`-162k?8d(3oz) zNe*$3Jf}}lk4Dg<99&rqshbWu%|!d>;uym*AHNsixC%JF8Zh3-Dp6Jt?Pv!4&4Aeq zc<l<!!hkmncwK<k1UMT2Uo*Hp2Hqb79*LW4h4$Nld_$0<dBE*K at a;|lPh1Gux*R^l zQ%DUB06o(Qx%wBdGX@GC1n={t_&<^AEy(ppkn0a2*ISX}Rmk-g=#Gyd*B>LN9dcFz z+5QB&<3q@HHD3vySq<B>AFhvu7jhhqA3?@{jN=-3grq~Z10UO=Lr9J<!o7=8)@AV* z0W;YF`M(h`@4)XGKtn!nJuG6GtP9X8q2Hkupyl!ZCGkIT4LG4Mv%FM}`)`6 at T!7vK z@9{F^z7d-1W_Wv2`~|@CYDS)IuwI`)l0JrB8Dk4lp6<@@vC}>7uj3cEC;pE#2y*u% z{{6c(%npykfx`z1_;G}BL_oEA9IZHFumVHC$}r9g at oW)}Ww>5}V>PbVAkF?buf=%- zsCP1sQ^9d(!tS39NswScD&VC~gC!rpA at _uK^Fwj(a2y+P9D(CV9Gh?)h2v-($KbdI z<q~fPg=D!PAytrwi1<bRkORNM;#UB_>hY@)ew`Dvc7ea$I4Zyex5l4lcff}zh~LjG zIIOrn6`Xn+j?Fku$8iQM at x|~FF2Qjrj>~X>gZLFVzK7#V9N))r9qvDla)}e~&%t-u zocMoxhv58NK>IoL1$u1M<ANSY?YmJw4Sh3ulD99m=Q+%&bxc_HTxNvtW(VImaQNa+ zfDfMlA3gy-e3(U{kLu$Of)^iVz2LDv9Q`<^;TXU%h$9w%96b6k`157(=gZ*Fhryo@ zgFjyee?AEQd>Q=t96K0gtw&uOpcM{<eme=2JQ;fLJGed-$C*fXF3LL($N4zEi{k=l zFL(*u7Jrc!fm4ccw&Pp@&$AQ!+=ZhXM-Pr(9CL8Y#W4@Z7>+}c?r<C%aU6l;NF1AR z9EIa(9LL}|7RPZou8BV)F!f0M3s7P^dd1t(E8LD=@pklzx1(3Q9kitWX!d{71=;;c z;=Z)IA|{b`NAx?Vn$8F<9R}XM0M2%icL>fQpV9$2e+OFiLs$vww|XGC)R!TTkzyIt zf2Gv7pcNAIEbIZ~a0mLaTe;ki{RFbLlV6W}a$goLh1UK^SdE>q8awdoEwr0K?~vcP z1%1pA%15jO9_F#k&5y%z26>q9i+&8-%#rF{=D`2IK!-HLwtR&2&%x5Y4omb7EZr7Z zx{pxqC$JXe#l8b;_ddH4t$3a_!^*t}>}`YA+6Jw)4W98fP>6i=S5P<ohsW6@!CtVY zN7_z+S27z>yDjlo;t#~%i$50M7T=EJ1N?qF{tm8pB5wD8 at QHsCe;-h6g=Zi>@s9!B z$GGz$p#1<cwk5t3-z~t#f9L~<wxIrP&_l{6{x0zIzJTaG)(AQ<{C=0t|CvwxBT(o) z@PSUQ54FDuDBp(1z`(%~^tKpq^cpzlKlX`#7XKVnOMD=23~+vq|2uHt37{nYLhKCC zJ{sSJ^zR_W4nV#gzix~p$`pS({uKW2h`${FNBmQycsl+%er=Dx5Pv37^d4Lg6!M24 z0SuohKa~?|*aa;`*xC!~0m5zR<xTu6zMJZoA)%De?Gt{|We~MN1~NbCEfVDe-&6aj zc?FdOe)8o5jXnmB-blhuIK%O3t{f>?-w6so32MF#8h#F$*@fe6*s%#eX|)gsC1 at vO z40r_1<d1SEp{<}JzS0kP^*unm6B<{1fCuOv{Qj8uTK%L#q~4RyNl58;Iz`o#@fYK_ zB(DFKxJtB1y|d?M;QKR@qx76=Thj0D{V!1VGa>&9KjXLH^!z1e!jy|@nNa61E&B<e zk!4MhfBfDxv9y7rX}STHHtUm~f*z;xKvwdM8`K_XK!p}mAMmt|pc2QYqC8kTHJi*i zu<*(!H}9xV_x7C5pMV7;oeD&_0(}39KODb1eqa3Y`0vG&x8u*sbwM}(0vi1H@)v1w zB~tu8{yXKI;AJ^S@;`L?Q^{ZIm5N`1DFsjJv-C5x$tS?zn`#dA>Esp<`skVXKVhwR z0{dIQC0o!2*vs4>!1)1DK*kECLH-G}b{00 at O)WQBO4{8Q;@66j$u<bgr9RERmN`h} zBjicr+tl>Ju6~foH at U1xYOcMz2BuWb1}6izf}fLRLH@`G5NTwHlD0F+^U3t-SI`&m z`P6$tUQ)ELdS5-q?~30N|26!Ho8>$4-^Oo_-v%vvFL;!=TG-L}U*mTra{MfQC$#(@ zl=Kg(_i-*kk4${z)PO_O46FA0xWeD at 2NOA;BHIR8QDj6d2k_&ICy;;f-35G*{miQ; z!QW|gMXx05laXR;{CSy^QG at WrGAW;0fbg}R22`pvzY%{HlCcX|`$TxQ9};~4Umk8p zWGm&9+oHqn->oK!-<P<Ov7gl*_=PW{N6XZbA$cz%(g$h*=j5ZoOH|8{&+)&*BT4ve z&;}_MQ@)S(pafBdOtsgc$sSU2KOvt*$<({pk<EvY_HDqC%GsYmZ+uSokeYmLA>Fu_ z$Dfg(Cu>o!_Wm5dC;q$m9q>r*l*<68-3f007byP{`C`B!qy#=Q=BFqrZUfi9hVMIq z^Zx|DMeqhal`(-kqO|y{3imu5zadfnUFc7U4@!Pgl>a#Bu?2_d|A<n6oh0Ugak6N_ zni7A?dLX_vY2lSSiZ6@w!s5uad=>vyYE51iaT=)pLhh7!lJGJ!Q=t7qD+p}Ak@<9Q ze#0C10$BPT`aQ2>wu1Pm^#hx)g8M&$M0`kk0KY}QL-h^t?C+U%?uloTnOCWusJET8 z9w7QtdKy_01w;w0oSx#V{Y5l|<Pk)>pvj+4LX^}`h?NjEw*iZ(dLY4F&}=&(1#go# zPtZZny$9LN*+-r_4~dd)gEVZ*_Gpq8E)^R|%E&rBDUX2ZW0aGn8B=plbS<z%()R@p z!Wi&HANnoMyK;H8^0Tkwzl=p0Drl(}prP-P>q(&+MNeQ0Q)z}f3CRQ1sBe+5hOkH< z$rdUbV!6#Ii>yV$Zl#x;lB*U`H!lkjFQ;fHxu0i8Ix{OxNZ9+BtDt`T;2^{>Fpg(L zPO<?mj2QaV$9s78W~W!_B|piudfDms^bSdqkR`>gp%n_J;7XP><w^W`Dm#@!#qYE} z_Qd)-S at NDjnVDYZrp&wQ@1#_zcmCZoa3pY{mIKaH<Vd|EpW}ZKxO_ho0+n}CQ%~H6 z)RbSxbyBMKy!7}Zd!C1K2yfuC_-`QnuOYH6K1nQK%$KYgMfh^->@4j(l{RGZoJvOp z7TG`fH9X)K)gC~ycCrV;hHRB<QY at X~GeJ}R3-G=NJMxyC2R0dz-#k_%p at ZahPy8R_ zT97o1Mtsd5@UgI@$!b#hI!PNqbC2k=2oEsXYeerkrEjN7e>z=&rRU(Czb?Gw7g5st zz)`{{P1Zb>D;Xj|yD#E at fG6}Wyk4Q7l5pp?Ye~JOc<Kqy7Z81nui}GF1&VxWfDN)A z;N$d<ikI}eU*9jm<AN^GM=!zBKAu@uZoeW4XR;@y<^(rR<OA=-bMi(LScTlAaE4k6 zQv8*!(|besoo+>P#M_W&i|n0?9_gf#0S_9HN90nhSL#&~8Uc&wjY5veqfB~;xuH+k zMv-$XIPfLpq&^_|E+qf?<43y`k3CUFveg1o@Q(bEeg4cIXu4j>rjVL@>Ss{&6Oxt` zs%_0fwZ93TdLLB&GznKS3PW|O5|l1~L^a6&TcVy9km`M0i+=dj;7rQ3nx1fla_$1& zw<7MCW`}{{gtv$^3GX?1H+fGj@2fed!?9;&QeR}xa-eNVP8Iwlco4V%eefGL^b=@Y z;2GZO)49rqd~Q!-B{M}*YBHZ>{@yc>6!fA|zE?9#`Feh5d0|332|Gr*Y8#~WMMSjk z$2j9%G6c}zG)krTAMvx}XT;C_dSFxPOHfhyO=HRA2fdvx?J5kUF_KzCy{n#)ONr2^ zcfQJVE+2NX at _}PS@#IfZ%cyt$-E(G%D&79wa`!%ex<;I8Ir%9%)e`cj+Z(D)#AEqu zn!*!(t~A;HQg$lz7oJq_%KcFF*S~*;K1z(MO}#YeiLX=E()^*RLY3)NO*P+C((Mh8 z0<(F&p{dk4ahko8_OHkO)5r-pBCCfwaSQhLX_nq2|1*ta(4Tn_w(PA$FPrS$7F8Ex z{wK@dhldA2;|3TNCHS)2lo`p)`b{<p<23lcgL%nMfiLn7zK~$}g2LP0+m=ryA8LDx zJdC&!i-(u^HepBf%`ozl?e!r2)}#j}k6f!YslKZGT=lx*55=!fj3}O{rd7|`8VdM8 zY%ob9@`BXVxz6c6bgmRAEv=skALdtTy7(FDRlYO&g#Q%dYl<h1p35tQFX5dLH>oj> z38Bnax_loH-8ylK{7>_9XZ}22{S#1)dUc6Wjj2#Ag~rO*q70X6EfhWij_DJBBK}Ji zOYyVid*Z$rMN~Ohy$=nuRrI5j+{nKzkN<5{^X%yvBfrW>`JU#SI6YcQv&LSXIMrA6 zG)pIu+$Va2R12Va2$89GFxOgqRLNF;kp|3b-(lBCRutJtY7RN?|HXgw^_0<+ud;T~ z3voK|+^e}tC9f$zj<80@A##~><=y*xSbszI<oR4B5QUS{nw?@#{5;V31IYUaDf4RI zBf1Mq@)k3RxgD=#mckYht9@AQL8jLlza0?mwH8b(i?bUrqg)Gk^jOJd#oTo})<8M1 ze$9ndavqFHd9fzC8f#3VScTNf`mj=M8dj+dvKSk}Dy~^r<2xI3;1{xGSh>6g>-!GC zs^Wv$daN%#iJgqO=%=zXv0Cjyb}_qzUCJ(Fm$NI__t=%}`|Jl;>GezYE3EapiTxTY zy>4T_#d at _n*q!Xp?5|j{b|3p2djPAKAHk~SXRu=VIm|+TfxV1%%I{)5*cSEy`-tsi zaV~KU*K!jt;8<zLZM=vVb2~5LUhd<5UdQWs18?L_yqUM~R^G<jc^~iR)A#^i!k6(C zd_TU1AIR77L;2x+BR_&4$v5$%_|g0rek?zZpUO|;oB8Sd41O`cgkQ=p<CpU*`1kmg z{QKBp;wSv4{91l1f0#eYALmbCU7RFV=LCS|5HRM$Jo_%JMeAY1*a=`dR_OHsGe-bR z#{(w^11nduZvzuQVrL0loCjRogq3~2#`gl?<W8izo4p9Ey^OVcx1sI3@%u}xg?ob9 z&7S5uu4B)l^(OWlFkofR0|R#U0(Ws2`zNp9UiJ#G;Aj5=CJtq<p^ZQ0AwYf&+Wr%K z_u<#$yDwVsFm}y&6yFtS$+K9?hIM^tPZxN at fN@9@Mk@>O#d=2YNujvbV&z^D4wY+4 zu>!ye8FdNH at c;)N7Qo>F7x~1ugLQ(R<WD85#M9)L;W{Y3^rRa23*%tmy$F7%f2#yf z(i8gDDBplMdr?O%_%(_(k9D|Hhqdu~(4rn?H6*?Y5X#Ag^_Z=A)`<1>^0yiEQor<! zzFq-I6-w$xzMc$uprVFAXZlX`;dm&C1S9xAoy`EeGjQ%yG7-MiZ$QDDA6WCJ;3@>n zghbnucp;q7mnCqc;sx8ZB=A8vAzaWmiH!!dx&<wiKeWSvJZd}>+JPn7GYi+FLgKMe z4c2bbk@}JS10y(sL3`x>XrXN~zednU{Tje+^fl)En!$zimFY$NnENa9D7Hiq-((ts z%$DO at 3?8O0HbY5#OP~qmuLBw}`&$NXr*C=U>jGT#CA>52w7^)qf`oa>`QJZ{p@{i7 z7J$l0ew3hFHTYHlx3sqfVb2DcEx<KNY!PI(2-nocHqpXj(Y8_1u42qCD8*B1jU5u~ zR9aOcTH^#Q-Do56WG8B<z+ErCUEs|Cv_uH&^10x=ZtzVlc$>IwKuBMY;4=-hU<=@C zMIH&V(+%07uLKDi1jI3Xb&#DQ)I-vv6H=iUYs_hvhWX%*1^60aXO^N>%djto8TMv1 zuJ^;Y2bO0IT2I!;1p9L!+I<kd9_*^I4%Y|cI{=%s9yr+m9c&Rg*vd}8SA+dEP6T#N z!q*Fn^=)9}JNWj(a-9LJoQd5s3bF3`0>F78zCLylzJ6%#i?N3K5`3ov_m at I~FT*zg zO?^4qbOpXMVduUF3;9E6_aL<UFM+8W@$JLf%$s3HZ^1VV9e*oy+imz(!T$XgZMhxa zD%in0aQ{wxd!X^}0fzqyizu=CVG}jj*W*!KKgON_uAamSVLfc*^T6=m@zt|`z*-ow z-uqQx<6p2FX4s8)aQ!Z<W&!Nx$H2fRu$@-+DZUQ23*S;$mfdLSm-vpdaePg%GI6Z* zlemOc;99O_(}cAN3TqQ1dy93>R*rp8U~_C(O<%-|uvWa77vtK_?YJf@)Xv@9&1$&^ zE6by>NEKLf?S);c6Lx77cB!4U at ebaBl%2en*<hb%vr#^WV`VsB$QQy6Ey9}WQP}A< z*z;q5eju*b at o(V&p|EMiuxZEP|MC1p+&PJV2kXR7<!4}zg){j@SfPC}zYM!5T+Y9b zHJw-S|H10^A7MS5nQS1|$ifDqjn|1)<gkRSkt`wh8F++0f;DfjhisTXhBfdlu!@gk zFM=nq=3NVF(CnQkcqDU=Dg&Ny&?ezktv`D0L9G7pZ){l0T8}(>-4U$&m~R}ho(&vx zOfx(J{BLD*jyZ1AG2m^=N&1#br~ee$<fx7Q(>*Ge{_8+pJ7gsU2wK6Z!|=f5RKrTT zhm`XJ%KiK0- at H{hPZwoNu>OZAcQ#=y;qka8y+L-g8B!VpCoE*kAzSMpF`MACoWeF^ z4*(77tcD**=b7*k=sbq~0O&j)`vlN=0q{iU4cIS$&WA#VuvZ88o}~Uf{GSe)lprPN z<Np|Z9tm<ji2n;9YZBy?gIlTH>VF+H80v%6AA(kyWeo6pz#S61nBOPPm+-%e^QHV= zalRb+wfuhh9!k|?e*(x4a$LgymXhKj`JPDqpmHt2Co^LYu{-#k{Ez%D{s;a$>?igo z{(F>jIG+59-^g#`xAB|d|J}lW!+*<vjh1rkuD}5!(Fxn7i90cISG^+vt6Io}hYmd9 zXchgZ6`CA4C;DCl?Lyu%c5%b+3vs6060|p^lF;^LIBSK)^5I$op1v6Qm~^*v9@2hS zKJ%Bs9~illxLzU#VVY|g`x6*#75$*krW21o26YBL*J|F0gWojoP<qsi58p1`jZ*+^ zzytZvKZAPdgWag;N~&ghN%Y3>Lw+J2i+?h|(k1cjL?1b)hFt}%ay57GKSK&F)Hi^B z`6(>P57~a8*@X&TE(boY0F0MFzecfh#V^?R`7hZmkkw+~WIgaey&2hOl|2FSQ{|pb z(nnSMH#b49Z)U$>KZZW{fkPv(StM04a479^G9Mhd5WAzOJ)E`R&6M5^yzr}`8-I%C zQxBt)5A*ftL7c||(BFSzWF7wi&D{pu_b~i{S at 4>k0|zX{ZXPS4oA(7R4nvP%2Rob_ z&|@j%PWHd-H at py<=`m=b>tH(?P__$edKtfgkjP5~jTC$eE at IdZ<45Q-{D6ao{C`C% z^nxJ68uWjf(5I<jE7<?Q5>XH9Hg+rjInr!KKZc>Fggz7X>9`TSh~Kh1`CrgqnuT7| z5FbIGU<F@^{sDSMDEV-H4nLQl4ef9PcDTBpT?>!-I(8TP3Hu%U8PjTSLaB!Xb2{K~ zKHB$f{%w8{dQJDECGYcXJT7^pE@=h0^Ka72n!9~gUzKl$Z<X&L-$vh=zRP@f`TpR0 z)c2-un{Ri(5pV~5fl#12&>ZLuOb=`doESJa at T*`T7z!>8t_j&gr6JeOzkaU8&Va<F z3xLUML~Zx-zw!6@C;T(1T&eAT>F=n`;;Zyc_pS6D=sVnZs_#<YoxZz$5Bc8kZT0O6 zlmyBH6@h@Ltw+>$Y~WO-wk5$;iP~r%LUq2$h0GQ|ZycB$Ka(*2<!9sOFF*S7U$`2i z-}yeTV)XabAK<Y6>ylTWd-d*r_2b+5>V{X(di8Y5^U9_F-15&)OuKN&flbD+PXpbj zkMw6cq(l@@dH}zlke-sBk)D(OF1;YVB)uiQBfTeWk+w?PCB)0cL1krqD4lv!dIwiu zul#*ga&{!R98$u|AqUq$RvN%tZg?Gfcqfms>mfroKrhqoF`q$-OzbY`X%GC7d-*-k z`M*w!2e=@wBaqlx;PrhKULSz`CL;kqLlod_!S(0Chw#H&s06S52~r(`T!$gqU65^( z>^YEaNH)I)vb_ at 0{Ra4PU;cB*<q?o@$TvF;{?umP%KpgP*xkI2{h4>cQ|e}a<vs9^ zdfC0apZ$$bV-N6|>>)mbJ;-D5yk at gU`6zpY&t;GEdF(NGMZ<g!`u~gBlY9w#3Omls zgLm{S--kWJmmwms3es7CeSFp+7O+43!7un)_78pldl7!q3;ZDV5?{w&<{Q{zelU9# z_Wz$)x&I{_=SQ*E`O%0F9D`T^v;{u_yD)u|y$!GGE&eU+@Ahr>E<c5Bfq(TL-^@PZ zr?U^>Wqr)gXD9OSvhDl=wv}JVw!!E6grCKB!t44JKG!bjm>;sU_z&3G at V$2P!`Pef z_x=KJ?;b=~e#}p1?_(Fa4;4-FJ7|@k at IG+-A~pja-V*pE at 9>k at I({lUgkK7MatS*H zmhM~pDs~PZ#O_PeSr1=|UFr^H%lVmXBfo;3&aZ^$4jspS3yo at oF8wR?&wcy>{vdw{ z{s%>`rol`3ueSQHw#x4<{8wB3S6ltRq^)4c;e#@%6Fzw{;t!^nzM@oPk*swk?bVTB zp{CSP<_r#Kx+=J{ZGd<5^GI0lSRrZnYg{8ql4jgT=cD-t66YUE{N%BpE^3~=VEveK z?p%CoXY)6giT~sY%!==jI;6kB&UL^GUK}f&F*2>MqphK?qFk at h8|z~IjO$oWr=-={ zxJFxBjVgScOS&LjaE+wZaD;+%I(RS<coy~$lTa_NvBw|}>u>3+D;dzVwfdw|N1<f! z0s2BI9BPyz1DcNZ>Jl`<$J<&vJK7s@TcdZBru@{;DQ)fObj>}mr>rQ{S=BgF at 9@t# zd6m7Psj)=TAY5?<FNrt?7ubTpOjNRYtaFA7+;u@mMEZ@^sMq*grujUbjX}LO(A*!a zncEvi12oN}i#9AAy=wf`(PP^5hB+hd`hcA~YY!Thq=EL*h|f+vdkJ{iE}aCg%LxxR z7Atg>SBA>#UG+ALR?-10QHFj>&;u}vIM--`S}su!PQigjlf;2e-~f%3aS#qw%Wdxp z=?!QvFi_UkIv^1S=F}WFbHT~`G_>uvdEt!1f`Xn;6-OKrFLkx87_RnqFOYs?ENoeM z>ios$AKYWm$%MViimMa$S+Tjt9GJOr{=9>yxyaq)@Kcy%03VgHb+N*-QhRZc&7jw5 z5pXcp&Ap{|;qq7|^(8 at H1|n*;8VbT|G?oykhn`#nw6scLohKGdNruiIJ*-JduMKNg zpp`s`#so{joFHuk7ieCPKRf;f=N;oQnLGI}#$S;TWa=2)&~Tv2mj^};f+H>9<=X(S zgY6r$I7*6ag;s(V_?Dsdg5?<cU<9p3qd+L21+Qs6F)zx1o0Th(MgcJl=?SL-*v22+ z#ia`r=zc!_s<eyOt-fG50b6wa2*FkhepnBF=wMgJiaOfcTALbbsy(hUN1??)@Rb6a z)4Uj>^7|ycriE*DW+V1U(gsb`<JU8tRu422+{7A?5e`|=QiO?k#>&D5Pi)AGAlxMR zB?%2q at K;6|MnMKyM;Ic7oFuKIy_0wkLe}LpbhgWqR_u`PbQmH*X+0IkrN+vdqkB6J zn%8*!56f#p0c;`eC^(Xv-HqWtIvYmnDu)6mnc#d67y82W6}|JSEdB0~d-mo-?*EHc ztJUe>vKp)gcXL(Q_`4%(`YRm8>-4K+no6gErnR7{T~MyJx+>szmK7Hjm<8sD4_zP{ zMR0&2y-p$)lyD8KXgo1b@)_MECY96RsoLHO^acc7!Gg*aSg;G2Al)FHCR)h(1sno9 z{sLO75d0$Xpc)GE?!d^Rb+P`nOXo$RL(}JXH!qnxBc#aJBC~hetYsauE)v5af=}5$ z(54dB6{DC4Mh^<i8kI|}L|hO_z%O}XG!_B41<kRar9?270J2m_|B(O?pa8}cXjB-4 zPTI(KiFzB^j2QZg<xaV7ty*_UZH>fyq#P3v0E4CCt_p^DsNgOW4xPXn7s|R&V<_YX zQdSwz@k)(!krsezB}s2I>m4IC^E`E4TP~>YYF*l9Zdep(JM54HqmkY^za`pYU8`|6 z%%oux#xVxIQQ#6I4>4#U4&7=gb)XS=O4uS~<pgR$ZXo#XiE0GxAjhMS<0f_tVUR`v zt11J&1O}tUI*Ca^2&ojb6iAZaRW5M=LrRZ#Afia}CmT0UtbW48iWU`lU<*~ccfoi& zfm||%HjGX~S%yOfMKARuBoh(%HdIWT)?cyN+!UKz-LtUOsX1S(8J$;A17$_b0y8V! z?d_Sfb~+C&+IMBW#WJ|>A)UI)S%=Q4X|`1Nmabc4JmBp9>8t0>?Py=VU{<9pi~M=P zKm9RdiQNp<l;GMDK#P{aiZMyiNZ=g2O@fRelWOQOmzlSg%L<gli!?reeteUxKV@;^ zs$E)S7DBc7Br)X4*=T&brU1BaWorfQG1lt$nT!eC2ODbLt|FTr$PPxi<S&Kllrau+ zkT!rR3g<{ePXS~^q(v%OD}alA5-EjgybGLc6O#j+<UWxB5Qy$7vpYczjY+5B8ja>G zw1OLp19O|c)jmIHHEN~J at T&;bH-}fYmN(Q^7fTNeU$JJ<ajWJ=Y8LKy)a<!~gU5e- ztmT9+Pi$&B;Kbk0Z=O46X>ad-OXk&<T`~Urg)`2%;0ic%B>%*BXQAzNY*kF}Dlafg zB(v4feZ_TEp<p3QgkNc;<dMx?sq%pPiQz&hMplTa_ZdP9^8FQQy3kN9MI8Ey1P;g| zi`EjmMWnM7ee>YRx0=yD4FVrR at x1ImY>^}x(iD*8EZ+3Pl8*jCuh5RFJ}NtX{DIq= zFFtVWDsoqu1?~F|+DBuF%`v^RtjLBI#zGD~Oq!(tYMUb3^jD460A?^5ohr#c0ucte zPvbOz`NL2;C3-_!Be!_E`e)R9t7iJNF3&lI6@{M`x(j~YTX$bsljY#MZu;3$%aZGF z{@r>@$6=i<>pG;l7hXB{auf(yB7o&=z+yw(rX^O$S- at B3Fz7JKWFz0rLv{wHg!~&A zQ21|jt<hNFB#;{u(t}A<pNzK;z;^nC|4QbRub97TY~Sj^2A8cmHvXgfCC84=*f_VQ zVezrEW^9~SBOP(!nP;3InRCeS;DICI@%I*;v$j+9*E;s!EW<~BSrk6E7ZKm%VwRff zkl*dJ6d1G#Pp2MRyh>3ha;*_EstJ<SLjan5ZLKyHbT~c`imc?q56YECt4N1%!>c1n zZ%Fpib&C59jU>ENDH at r5$WXDr2wI1%ilixP at 9J_d`9^=)_zi`=qPg>j`W<yniSOuH zH5ihlv6K739|z7}`h!K}fmij-{fkDLJ!^QTLEn7BGMP5tlCA`8FfPajV?};nRoGYO zud8qsT7+L3i+G)|EF9en4Vo`?2=#-O*XSUk;2i2&*fn8d8(pI&(g=o5Qpf=KdwG{i zLeZ~~7Pwa*adh+8GL=@LffZd1tJ{i$_VI_7FQ30~;r_PD{?oZV+!pS-`naX%62-nV zHhRk1VJlgHvo1RK)blRAqI2~~HDDn*ZU^mZ5p%yFR#X>lZ;f`>b%%mpw-(k=@l-pB zS}hIe9u;t|^}(&v221Rc<Rf1Otpg#cIcOH?TG4BQr>*zI@@E9{$+gXqTcg;P0i~0n zPcbdjt<tN;rM+72ba*+~8@Q>S({NPBvW)}Io_2q2xk+=rMq5}pt+~LLAot6TdZ$!T zHFwD at bUdt;J+s1t`+01U?y<JoR!gwoQ$O5*m#8c`Ye=EG)u4}lV~$lnG}sHTnIt?4 ze3^i+HtI!jRv&ejmtbxJZxg*JyrYCSmBHXP0$*VBMCZ{SA|Xu*CXu^J?k7eX(Z3*) zmpMxt;o?GCq36}EWpxu23AYVSE30a8H;;AsIu`C<+rD48d3|(r*x%4rS~=QRSv9h{ ztNEKfT6^c<(0YJW=`Jx91?mQyh88v0ij2n=+S@8Um2R8f>}{Ua-afzG?I=CjRBSWJ zZGz7yRltsxvXPj%)FE5agcU<~P4-eDxT>!JQ|bURcz5bE#Rr3HK;1yaZKeKvM^LJ` zpS(!fOT8a2Hd@(q+~TDA?LMOCKomC&+-?9I4Xiq5u8DX(#YK88;D|XOD6#{H`jl>* z1{mxTZvTMPCA<#ukG)*!kfWO*Y($~tD1FcwX?2%1)wofLq!C8FVeY!Ft^;P(!dBq7 zw4-iX#BPuDM}4C`qZiCI`v&Lk*S~pS?MS$CWbHKX?6$;B;(=1Yc>rYI!3;4iM&Ye> zyifystdUKUrb{}Yysdk%>RVNV-EHOPghyLlbyZ>5T3O96zJb*I72}UwIOF_FzpvQS zGWZf#pv+pdo#rFevRbk?W!^HYMa%p(VONm>gJcL*FxE(I=Z3U82RUqir+rX?q>1w< zHTrU2u;}zaS95*oIj+HKdxg8a@JeTAsJh>MZfRX}SMc<ru-^siSl!)y>gI2ET9S|r zoq68HW0niXUp)I<9{7o3pSn&x>-1ilLIxb5Q!1?mt*Y3fSRw9Iu_|wc1O0}C&MyzS zigb9*6%Uec4L6fOmo2_K at rdq`t0N~-IY at Yu&M*)P;|cGk3wE>(_H=-ENo$?8t;5Zg z{T;3DE1XrKaB0Kr$}4;=t$nqf8>d}q^M<0H$Y_mpx4&=h!E^f7?lZrpW_m1EJ+^tI zdEWdr!%MGU?(OWGUN?B+4AG8a)V~^dceA#b)?|dgE;}nlPJ3}74aoYD3{a!vQ<!?I zQS^I&ZB-co#4ZKI)#a_-1C`VZN4rN4Z$76uTpe|_%?e8o#E#!|SjW7R`zm|82O{fV zJk=6ixpGC#{GVgson!Cp?J!CYVE#{U%<L(1lvs at jZ-5KntxB3g;7SUXa*ZEqMbRXp zTZa27@hJi+XlT^baddUMf)SslkMnrNyroM*yu)l;c-ZmnhZF?nFI`eK{+_91)ltVc z&XmrmIqiEtn!ZWWX{6~5CtrBw=#di4G8#^AG{L^rxU9o?(OofjLp^70E%jXuU6HDw z-&u-ifetfb>db_<MtFZS@L~cDVn~t+gBT&MHPpfk>R|m0dV^NqfmRx{cmn=TG{P1_ zKU|L|5gkGdglxnPW at ne9o3tDR?yQNz;Av1<B8K1$Rg1VmnG;HZEC_JZ)=J$%;Tn-Y zO@XXJ-X-jaP{L)`4UR3H>Fsq^*84^rep|G;we%Kv)rBS2;-Vr?VO6WnF{%kPR0e|8 zLCZcfM)xVQbvISp3J%a)jJjXhdIviToDS^)I(=<*q+xYq*jHH-4nlhgoIZn=HeyT% z@zBnua)x-QQv^3;L(tKl0J2Ph5`jghp*kW1N%1wbLx6v6xIJ849_<czdR+^hLtekF zwyDLr&ng&LonG&7_SAa3*0Q-qYektY>bANZ*09B0*X^(Fttqt?b+57&tu$E8`sMpt zS~{Bx?8Qcd$*@slcSIYiSDPI^N2T9T>@BUV0z1HMllftn;D<i;w<&PMylhGmNA&gf zbZf!Od&C$<Pi$d!al{x?DlQj)n5dtzSNu_8h;;1<kKB94j0IEY6X}0$u-rg6=WK~I zgI7rOt|ZtSm_pbaRLv_;p0q`zqBjkZoIWM!GJ;u5eX$V9>il`5GlygSy}4wzP?gyw z*(l8|vjyA)70DuIZZrxfPf<g*kX4mni!8w=^!Zw$4))D1I=2|-FIo^yEgw!b3ANc1 zTQ+gY01-uMjED)~l`6TEPOfrIqSv`s+ at PVjB!pEDMbedWy(b*9PtaW+4plgVw+AYm z;$jc at WyDn-aJ$1^k8)1PtMqfwWE#f#m$Sdes^`re9N=u};<?M`Egu;km at _!1qph(% zS{n*@Jq~+;31iIDY{VHJkZ;WIl#H68GUyU3rYZC^OL|?4tk5OcN{M%vIHo@7Xbyu^ zYXw^nM`+O%u!RcElC0GY3Qs_*FU?(^83sZruaKKDrP|CUD43-et0;Ji46Djh?j;Bv zU*;6yV$j797M;PaQaO$sDb%1_WhiYUM-lyCF=j!w0Tv-urJ74zHvf#E+uqvUTVCB$ z(l*vM94Rsx#<d+CHPfAe)eWAuXsEzeRU74f;d;ZhTyOV}KWz0l3!`p#U8=4fb_a^# z8?P`ITMZ6pUAS{3=xr|=>vx)$c*{)A+JLXN+|2*lAG7I9#-f@^*Z5WDQeSDMKM<|1 z`mgRywaq0?Lu5}RE3{z78G9g0KT~wCYVTebdUdWeL)NNuMrVw~2KsvPYSj>A=is^B zQh|ARNHUtTRTa}4l0K1{nT!;?OjT4ys4uv61qC9AnOD!6p=ZOW?4Z4<tbiFc1yJXy z)uuRQs>;=+sPxMJWV^_n{ExINwqYXd>2i~5671!&F6I^Z?6tQ6!$Nw^5D^Nkt#yxl zkAHRd75ggOmP%J=;%X1%;mE^^ONv_p;y>9H32#K*&R at frCB^(6ptx70%Gcm;aF&%6 zn+zguL?ehThzhaFAmT=)h#P5wUgY%CJ&d($J5Z2~1`W|$5Ti*n!mh-MJ5n+tcg~C$ z%~f+FhK9^AFx?a9L#PVj#<Yr+tb#B!e56L53W4kD2zs9)!dwDVArEZ}Yw<S<-T4-` zmN!+F`FrL!*AJ9ebyy6N<`r*~ox2+9o6L5XMheVWbYQ=)-QK)7HvhQ!k+8SD@qwQ$ z`{l~<D~c^kzunbv=pnVO6*S_;@t!2SOA&UulKm!T3HsgT4m<EDM^K%>WL*IUY$~i4 z1S`D2q=sTk)McU$G?1v#Aifj<F0F`&0*Cd;(u7!DdKS)+G%>KBkqyzh*6bW07^k46 zs7xkUYF&DPw=UW8%44$12#_3f?o`cfIM~0a*<b4_a5xGM<UCT=+}P0;p5YFxXsnvm z75c~ABWG6@X!ZA4&Bf-frgdu<kGcw$d!6={m8cXz$oSa>{M4Y=kqbirJ{?0fk?Jah zPVys)n2iwCnb~YL=Y;qIxAo-BI0 at PeQ$-s&`jeoJABBFv1o)Fy$;M2@;S<1NGkYOc z)YMQLNphMPyCzQS%ETl5EgdnJPA7c+JX~i`klB}sleFv;Vidi*fvnsJdI%281|pjq zjZ;NyNs5C$33{&$``wW$zq at K86f-wcxWW}OuXbWJ{Ih&JX0tlkSj=4JC@M5#@GTKh zbwga0Kqh1m(ofhG8Vr*@5DS2-M**JNv`1oYKx)ULIHe7pbyb0;hN?h=dhYV!m%lc| zFI6r`$JE64!#b?Z7>A~Qqyha%1WvoqXF)gwTCT=W=bOK=(OudasMD1WEj_t1+U#sq zWbY&B`T$nL&5ad?f_1gQmQV|6dWw2?I56T=Qwi50fJBleSdIY{jPWraW)UF_j%8SA zu+m7;U<&cT4(bpyE7Zu7I>>c*QVdOxkPPDA5zOys#A;{RBlk)C`xPOREmTwQ^0e2u zg8fTc{T|qjbFe_f;wai#R%w&CGg9AVDf3y}5r at s^vZ-p<w&;|ef|JI#ofT=R@;gE{ zOG$I!PH#f@0%U0wZ$#gj=rtpQUet*#qVO}ZJ&1-yS`-U(<5JwKrhC*jY9Rh91)Wm) zE0tzaRtHbIEVJ9j*#c=5drEo&@KnU~#5oiRE|lvN<0}9G=u53+fy$-QB`Pc8ot*JL z_Bh_4Q$X}a{|Ygt6rPz!Suj#pbCH^Zc%&ChcRzTygbn4J3!;(`XmBB_Pq`S-Kv&>n z`lVtaV^+xh$|&vG^587r?u at pLG&atO_4ul~2N(2p?%zW!kM~EoF}Hj>*6J*Y8EJe4 z_D+q<dNDqOSJEP~8bn%vk{Dtsmgh{RK9>-*OnXk=7frutD^%u$Ad1e(&UcBvVaK4P zITvH+fKOXoTsGu&b`^WgU?>-_25^n50?vKbZHUBXI(<%y@esYC8KXk7UcD8zy8^ap zeyq%gHORq$x5ih4I~AoR1tuUL6l~UG?9%`=0d(}3HB5&Q&04(BR~-S(PzE&HP=-r2 zqRF8_L@?ozD1F!KbnP8Avz&ng8mdNn!j*%|THA)ItR}<HB6Zs9s5|=FylLg=V><0^ ztLDt!I9eI*9QFs=i=G`B)mn5#wUuIiokrN$TM=iXcGR<F`CHPJY)O4xv{ozmr_>;l z`6)F>MAjy2)Y&k5lQxWZDDg4!A8v(=Q`_2EjQu?3BRUOE>*;E1X{gJjmxcI^WPc#p zRHielL_=`ffCmvH!d~`JL0%HVqV*>WKx$-^^dkJ7EG&O^1M%yWFcwJ>>{Nv_DQjGo zrI9AZ=}Va!i{ipEexFeAyC&u)?B>EOMha4Kg4vKNQkjfX_zQLf@$*TAac at vEDH>*~ z`g79Ozo2SMYI7fWN|yU}%!8S*%{@szNNqD#{1HFY<Ztklr6x53IcbB1!65BFX@kG~ zo!a6go&O)u5tv;QnY5K2gncG$ARiJsLc^NkJ2ZOuN8|^DSq(g$K!scN-e~T)9i|ut z3WAT3GU2e19Zr?EsuVd)y>3z+GhUH>r8;W?#aC0$D!%WOUKeuJ&%Y&Wt-hYl_Lipl zXilv~v8RRowUWu$)(TytvYj at Fm8IyJXDA;?8zN5;W-?hO&|?(Nr!dp}Ny;^0Rjmm% zVhVjiSLRo5MwChsrLP{^uNqJ}ZZjoKT#_B9naq-1n+VAygENzJtuZ4e!!bXM at x$P5 zM|f+jC{i5^xLxH=dr<)hoao)vSusTg6IaN8O1MUaAwBdhxo_xpkW+<mRGo;$ctM`7 z+*!amDLJyU6Lo1mU~@`#J*G>*S)^LDI+{a3!@wj at 5URqIYH1F2o171G2guWtbEYRx z%<xwsMwh%LC6`XCquB~-Rz#h2nv6OzNl6LHl~7+u{LxGt%-o~lRWENdM3l)a{ZeN~ z*ft-or_SLh8wtA`T?Ni)QL)peKiqT>Mo3QKGx-*+-ESKjEpxeTMJr6E0(X6|bD3ec zr at pqPblF)O#xV1iW48*%e<AHaKOBC!%LKo?Bw0;+XFu-<M<}EMa1yZSOP@^CVpwRl z6&pPBqt&xKcJHvGA;Pz-l`KB}kcELkZQWpl&m8fW6s{>O9i0UqwFHjpf#8i^h85DW z@);xjeVy$T0q|CM+%&t90?-BI^^^ejoh@~eQ9slwAw&!UNtz%H`9ZKFn1rG)#AGH? zge+ph<YMp(^^cU`9Z`HZXKpB-2uyroNG%Esl&>V>9pZs$StVhu2f<NIg(OPM4Gy>= ze|}`4$O5aTCWyWp)_|lIY7m?O^`6NB#q!*ye%=+1zyYV}&oZ4-sD*)f16DJ2Q6H9u zl=y>(>emStIbHmZ8RnpqVYU<+K}CO`tE|_X!cM!-ASN@Gge-!x#?O-M6KCT3>PLoW z8LftJTa~LI;<pyqOOotX>@DmK7>uAg^-ZwvoOB=PK98-C{4w{w%jS>Gn>#vdDAw0o z7Y!iJSW;}oNQ3ZqiSpg0Hc7*mEGk3Cy}eD+88pEH4Cj%IQWvifo|(Zwq&L$95nITp zmlDc?MhvIt&JSeMysbjmm_#TbZ&^frQar=SvI-Pfl93arivkK3$*Lm{RH_b|kzEpU z&<<gkzMBZfq!%d~h&3(|wTUWIj${`Gp)Oz;2xYZ7i_I#Ef@L(k5t~v`l%0TJfTkt| zTn){z97+_SO&|%-XazKksqoaAIcBg*P>ES(kOGv$-|%Geurm~4YG=M5do!e}#T1$7 zO67aU>?E6qN8Ik&ZGl+<Yk0uzt#w<!NDT+@Z>EUH;BQF+G=+{zD<t3pt<h#wSiRWd zD6>1(loXc)>s*}~@dJO4*SssItUxB3q6U+Y3E56Q2ASw)yJJP&U9BylAlC2L%oqz3 zc9M9n*#ZS&g^H+)N?N at p%~+;cJ{>G4g6<ScCf(PTnNKl!sdi47KQLBGR#JAYa#l^E z%B1N^HIg*8p2!(%$)8DsRAldJQtHO?H<i|8>o5r_)z(c!Vqy8TuJ*<KlaNp`f=%<g zd{d<q#D{#B4}^XCGFH^v)81AWtqPYr(`eFSwLmw85JhRIm#``g)5wKllAQ<H9ieHq zgdefgPbErwRz^MTsUu`Rp+YDpR2ry~$Sb=ca;4ftr67yQtuB6(tJi8%s*SZOHRj5t zMGd4pNtD&vF#$Y_F7kSm#5`IAM-dFo=aUG^ct-;@X+njSmc=A$%S{V}c%{&$xL6+^ zn!kS5@L{W$)U_|%_sFGHLxY2r`qFTdr*oxgXHt%!?KSZ);|=_6X&v?;7-Oq>H0B<P zt=M<*!dWw7V?$#DeZ5_+&5>$fg<dOs2(ko=1CXFv?BLT at DrpQ$tdbF{2dg+D!`Rk` zOPC9YU52y<2m^GJUaw$6=53l}#EeFa@0m<sjtD>~rddC-)?6_4lCc{Y{?Nb9TZJ)f z!epXJON8gB)in^vFp1Q`$yqQ4;EWi|B+!jv4EMr)s7g*qhBH7BJmo~92j9VoIG#^L zBg3JHJZL7Sv1r6h6nWW3bxC_8*C(Fxx7$}7Ggvj);BKmSdJD^aMd3)GXJJ!Gs7PNC zY4zwTI?F;;!JY+;HI)`D2d>4btMC2Bmev|qWqCp4g2q`(s{;LfzOHDwwrN36z~Zj) zRJXg0fr^q)MY*|bdVOSii&v*B2!}kHV@$@f>Cu|$EuMnn5)Zrssg<A99j$M+d&BnX z_VIwzj!{*Ag)?BXmlLmGesqIWC7ChjXfAdXDTo;tEgKk_=GS1}8kksZY;~eNz=XPv z8nZM7dgT9NPyjg6<;R6oR at Cb?7f at rdVA_~yZ7PQ9z0pR(&p<<YYja)E__NLJ9Z`3+ z%hIwT1rz*IolRd{<D`~qBx|wU)Yf$TggYFL+0BF<7lL^f!^- at K&?++Xge)`I2V`-~ zHx%1&@Y?-zOOBPiZ<3rfRlLBQ&5<-oDpPXc1DQ!a%w~JYGBqhGP^4(p9 at L^T`;@8! z=X+vnCoMg%MCGj{O}YxcF6l}QqkWZ<cDov4{E9_RdLkL0BNK-GchtF};1~2Q5hyze zaWH9@JPyoR4`Sh4VQbsjAu(fpU1g{M6D7fWWbUvm3B4;US>jT#9rn;)&aujex=J(+ zKm-RA^B9AroFJ(r(JDzSSxYPwO>Ue(ix$ztCS|D1$4;yl!Qjk&WWsEfwT&(prdPm- zm<rA}VB)o=CQP#{IQBI{rxdHIrQnRk2M!eZ%Wd}PXlG&Pv~F8)nxov=>8R-IYO(j# zmlXCLxbMOmVcjt8g6w+_VzzSIW9+n;h2sA*1`_KQFlR++Rypd?$XG~V06_$rYC#*N zioqv&nN!<@c`yhnY1*I|lUeryEvdtOQ$^9`>KNZ1<LZkORj#6#0b>tKFCm8DXD8EY z4_`%j8Gfm&JzU@$SF&G1Yn{Y6znH44g}Nceasv5S&`P#5^)Xfwh0~r;fP__364VOA zPp3+)+&@Y6PusCFDka#OF}A3QguHdNA#ZK2LB^j};7LU_R(6QC at p8yZ31Yqra*TL7 zoUlz=?x)VL7 at xz~HDC at TZ_5DF^E7~A`ZHIfcVRL|2AnTq5GxmmimZZhP`q0io33ML z#q8zf<#pwC;gA}aPV710fz%{NsS(lB=>V|koDdCYFw2db2!c_cAT2ScCRL%2j=R|U z1SCR2_&Xp36@&DcT0|$65GjaHjecVpcyi?zt;<yhmwuwoXAY&hr*I`QN59X==+enU z!VsNajAg4lSjT=qdoM(5Dnm(56nG#`M4PcY1!k<Ibd~KuN0NRtL5f&1kYv0>p44=h zjZvRWLLREv(gDt&7D$1T0Cwe>F?6bg2cJ#!G$u_I;S5GdFsxP;*<5U)a_$$Os?rG_ z7H`|T|3AdTWKA+a?}2g21uWMpW3Rn&H@{!eAN|-v@?K1GVY6mnQDt{qOJz7oL$Qj8 zXxQ3`wQe1lS1tuHJSZm17|<<)W-&p~WH}6>8!lsCSFFR_KbfY@$(+c6%!<m%mk$w? z39_K8M^aszR@LNm<`YaYNOdnV5SicsvOBrwAsDBk*T#R9XR?Cy!-VD|zQOx$cn8Lo zN-$#3L=sh98T41Uou#sF7ZOFINln<YN2m39VRI$STE>hXd7C6jQt)p{6EEs@(_m3U zo at _d}B|0b at C>fQOv=xFp7PJ?2ktpwBU9p}iz$Pnp0y0IhCx#0$R9#oruuleYcjXx5 z$t5)prw;2#VlK=BkR6)0a81k-!KyoXMvU-RNLFf7X1vgjBhb|v4QA>o4ho>B5GiKI z5M2mYk(3PbOkC>EW+lfu{Afx_h6!v8E?_c8A}EEj6`7bK-{+lXlVtCF^KFtWOn&S( zz8ijQlpP*3V#T_r$d>S9D`}BBw%stBuvZcDmzC0Dbx;(3EVWknuxMdZu4M3iF>Zu( zq~gT|Fi5{E#e at u)buacgkvBa-&t0Fj>xt^bb}o&c<BYcSMV209kWSSaqJ3UJmlE!4 zc+QF=W-aMIpxIJd)zDL`UAWvm60+7zujuU6$ByV~U$Fd`edm+girQ&r6vgpNSY1qu zKA-BZTS{P|l514W>Q8xnYNiJw{6;WzIta-S5IM<WeKYnoVOTG8Ld-I6&h+8trmC<d zxw5x$=0Goo2bM?53lYbXf_+$%>H<n>aWXU)xI|t7Bx4yGwqeo?NZKhfA*M}M6jK9J zdIusrklkr0OIc@*B|J2V3lSALuA=U7LAj*wi0(rq?2Z=09&n at O0`*2+F*$DMz(|OB zgvU9`^|s2IMt9Y4kvJ<o5TnJC>{S$59L^k_4TPo}{bDj(dH=|)pqOm9$Xn+u3RM(G zJLrFEFNJ7|b>#eW;JJ(XVGZ at 6fXR^H-<mG6dX->Xbi|O>4^hn551TM)Mn6nJk-P!O z<S0Fl5K>*&>dEekwUiL*&Q?)}r2zHLqOixF+RBu^nB7{^=*vJ4SXaiuF5sY#ZOXzz zeQMKaiKmd|yqKt)H2J at W4VsNTb&T*!X at TZmpoLE($4SM_6Tr;~Jbw$V<CM81(O2zA zZ7{fm2h!LihdDrh6_e81C(cJ$%E+rMq%PS*{z>f}OdWIgsiBg+!QN|0*EoU0*MY;i z{3o#@b>)_f$ApJ78w-H+y5S-0(2~TAmgFu2qv$?q$n~e#KMjY&F%)a?)mVT!QD)Yj z7pRA6)l>HvnqDoAh8$^z%vTB@`2b%Te6g8RZ7DHT5`Af{CFW9wKlYHnc$V8WbuQyK z=WxmPhU0iqn$O09(CcqPuUlc5#ePi{ZigMC&3NYs&6iKzuL*?42&wF|W$f1^78>c~ zl{IR3oa>reYulRIjips_7MixabNx}Ffqgq#7k2yjO$Y6>biWPz)WjOf?;cn;vwHJ! zgX?EUDu>sApEL?gy97*O)=hiYq(c=7hk^mj<OOrXKL=+^QU-9eXHD8pM47<RqVx#> z+(faCyiiK*a(EK`3mJb;30P_0iEqZrIO}Y0ZloT65^59qKor)%9BR=YkI*b8dkBL^ zd0GHhgwh~6wtt1B<xeT^xuy&|<f24sS3XtAO at M&x)!q5A!C%apWJ@&cOYl`48;IGl z9Z?!apwD8JDWQyfc$kcmsg0YULI0ZwRph1W_+1HYEO-!m`_sT@CmUw>#eCge(OL{0 z4tEbXH`aDWJFCKeZ#niov6%H*F(ZXWLuO#wG;^a%jO}SLTo}a2o1`(JnA!rPS%Xnu zFe at BS(wXwgG%+|H!c-%$vO2~No3{uH*7KBuxkSlQXrwUtYl1S(Vs^GHpiY|nLrzut zGIQFkgVwiTpDOrT%ErJ|$(j2>lHs-`UhkZywvqHz<@JlazWGfg*>5Cdo>x at Y>%@%y ztt8>0aG)xE5qWvo>x0DaOsWFY$f(Y&PtNg|W$SCz0~8{ffNLJ3a8X`{kz%9<RDJcj z&;f0{mNY<Xb3<KCbvT%z0VqN-tt}v7tFBszHN0hccUrQfTF5$7nZaP0Kr4`urFcbO zo<a=h`Vucqpe`u-M0Wb$gyY`=K{lWhD(Ea at IRTmY1v#wa1avc`+dv0me(yj?t03z$ zW9F8o+DIrM@8;EBQI27NHrja`3K%0SE=&q2w9+1_)H~B4$ROfEm|{r86Qc+;30~gX zt2!daiVOpi*!wrp>BAB+RlqRg&Varle}|UlI;*kzo}YstA}tg<&f(M-Rn@n6Xyxws zORgrdQrG6C&P+Y6(Je5Adb`>y3Z@<V&67ryK3r{CQNUA-<z(aI&GND_(U}2coaT(< zeP%RgJZ-I(8a1G4k(qPE6LZ1UdEZdeSWDpMj{;wQtj)xF>R=#R6KD=LXRfC%#a`2q zFx*s_FO4RMxfINYCC_*>p$4n$XeqTkPY=pa;7yJBbReUYKcleqa&8&-GSac0I#)he zS4^MO(i-iwic_<SShx-wlgbKEUb$4}?4oI8=#p!&ypQ&Jucq0NSntO>!4i^ZW>j6> z7w9al>ChW6RYr5a1AAze%4_ at H^7c<((Cn^J*Y^1;8lC<7p0E^w3qE2sS1&lB-J`7R zL;o67SS8UGUbAAT#cUFLl#=`FvH(#$=r>X{3(*q}FGF2`>CE}8QEUk=cYnm34KNt3 zQxuzTBld7!X}Qkqt8R3gT9(=Yv{vl?#fj%6eXzx23;WBp7Zy888Y%*_M|z7(yRiXj zV_#h*O_=}{%K*edh~fLO@?b#*v|bq^a?l83V<yC&yw4|tnmV-S=ZBn+Q4e-v at OhWw zw~4(G`Jv$9W+wJVtc_H-u{UBfZziudN_!*L)82@6SnvZgo(Lb>FkT23m1JFPT1wuD z*y{smP2C$2D at BQQXlFzyGO;n at qZQ?*5?^U~OQgK26SE4ZJBzC;1LjzFt*bs*vUH?! zpQzSa)7Y at 4%we?`T8*Z%P-ks(U!cHjI#h4cYwG=eo5>k&ubMa0QF at fA*ouh`h$q5- zKOD4ZruSZvr<~rBmW-BQ^Yj*s5aw@?Sjd-Sv&S3_qLpt`ZPc$poT)cVx=^7LdN^p` zuR$jtBB!%s_JBVe@<#(vkBjC&Vqq|-)nvs4Ol-D9!6$~{H1ynHPH~T@MB0K}?Ig)W z3o&&k*oZdK?rJIT2WXU>5O&%W67u3TVN`~joi)D78Gdu%fTqZto^YUdSvz)QFZ+FU zt?qiPR}9?Z?I^10i<DH@=DSKgeQQQy>qjG@&XGAY26QG}aZM1?BIwko at LNCDS;HId z>*;7q(u&&AmO-n8bDT;o8so`m%haf4NErv6D%Cnwr+Mn6`yk;%W6lQFa6gT;u at +X& z=3rmq(qh_|SPtx$8V%5A*xj3yo3NZVP9wWM^}hTdBc?4B8?AguECf#-qH0ep3Rl-H zrL7L<XQr$#r@c{|v43G0JvK|g=fc_(gHF6(LVlk>oeAN054M)qNEMixDs%t=O5QsL zEK>KCpwK+FmP${p?&vMIEhC3K%|s9+Z66Bt)d0%2PtpIqtf9|U94aZ;#Cd(sEzZUn z4J`*WG at Q`gy<vVMAG-f97QJrVYV%kIrjNfpvZl}DEk4xibj{vOdK&XoAv+_|4Xlz^ z#0o3J74Fg!3$zLL#3Cxt{s|E$To at h%Q~7D645k2vQubx2p|H$}3B%OAY0FNI<u@u? zkUBdnIjrRM*5}G9k*HQvO%#bIsZCTzmaj?81BInPM*=Q#iPbeU)-T*Ikqv7o8Yav^ z4jibOs78(B&<@zxyc6qMXsNYNysts5wQi(0ICO<=!KlY>+RtRx9&S1WF|~pco1 at xl zE021Et-&IbZZ_W{l~nadJzYy{mKpjFuB>hGm<o)CSgnqxxt$H0XQFL3^oIp4U}GTv zw3;QAB3N@RQ>0mKL6RcqBgpG6bCHI;c_K~!1o<YT5d{K?MjbiIkg=UcBNZH(Sn6PQ zx0*R8u at ZS;Nz<u@-friFS(8*t6e5#*s^m+J5KKzSRjSIHjHX}So6vM5YsX4U`NLv- z`tg|AUSuu67Tj3H0ICvZD>2lik>Jz`BSn1$j7NYE6S^F)TcC+AVkkN-Ep$EpX!mP1 zkGgd=?O<K04n<F`f>^^Im96yBvU_3==jd)pe at g9C3H?KR!DAQ@{_6Uy(UKYr7jX#z zY{Ccz0>mC1xTa_rZQ+yb6vV1>CZ*iK09W?lS4ZMfHt7(1D$6r?<$y+4b-VoT0pZxg zA?6p-kWr|%F*#&(t~z@!T8t5+<dBiY8+8^3(X+zrJ!@BT_MW#_%-(Yp+8ex?BSvKR zL(swF@Xu(>=!h&V)TcI$#*E5z6Jeq at Z}P7K8ybp<o;ziXEQd?C7g&*QP<%U$f}5v+ z8|(tg9*Y&JJAkH)c#s|F1b=9gO@}R}_xdQjqB?iz;L%KiB<GM~qKIpE&fMzmTG$;L zy98$xqV_0rSDc;|^&&Scr!3EyawqRImA+xj6nHVkKT8YJPMsp-N2qhyl6!LQIonF} zv%<N8kNd#KB+Dc0+d0SgT9Xu#h(cmC3SzB@G`v0F2rn~9rBJLXccvseC$h;jQO4W% zNXm67nB4>Z9{+=C3ig7_pGg{r|9|NKiib{}<F+F{x)<1yZcX&>X2my3zZdVtX+$5v zT<*-+(cdNBpQM2*#@Hp?%fuUUFyXOGezy|c#GjZZiudG*O^q_&lau>BM%i!AS(*FI zM(T at K5--)^S5EY*BgBMf#of{`#5}@gybtyHn4S82jrEZ#wS~gtqd8H%QOskl#Y at Xb zYN(YdTZ^EdA$!$IvuW8m6b)^jSFFWiAh|Eqol*jPB#mC|n;^=XFh5+co|LQ_1HFBw zgWOG}2%r`IACssz4Zu?CmUo5AM03{^Csk!5e$J$`2(xH#@8mNFHJEqh=J$%ZNJNKw zVueXM6qrRnh-lCjMVzOWriTRKA!)HikcwuQ6m{Ys_4?qQP)wniW+uqUZ^_Jx_ydSZ z)*yFAbW=Jr^MC`818WvEI)>Tz$s$r~lu at 2JO=_YoqEb7 at 8Ploalj2(=i8)_s6RyS^ zC&xLxr)8t&I`pmU*i5{q#n{|L?_3hIg=p1jpsp%dDBiU|0_`7)Q7}E_oeS!W=Sq<d z5tv{`I^Y$H?UaylERrjkYIMXK!{n4A$cq;a$dkS&en(U8SFug{<|gjXtvym-&NlfQ zohEv1o6swo%b{25FgJNA?VYh?(b(LX(+8)?SQfKHh@%JULYV%l*A-&*Vimd$!(7XT z5Chj?$Vr`wthh_m{}#KGqN71!&0>w1p-i;u&tD4OH-SC-iOtEN=O{xB=f~2@;Hi+L zmyw_=MIBOMiOtAggrvu+P3)42pa%Rn#1mAgvfHF|XQsviQ=Jw)Ia6R2;w1B<_l!Tr zkDXiwX<egRdLMk!g*T-i!1s&U_ur3J^39#y-zR(5!v4^{x3iM7h~}3`I%y2rODj8Y zz$*36u=-W(AO_B{hd>x*%1^pMAY(*~H<yajz^#*bc5#?-nNb|%7#2=3Q{cjc;)Q*q zHMHda&z?e&Fk0BBTK2*77KqieV-u7`J!%c{5;eMwVG{#M*GA?rwd7O)u?4?aMzMzz zfk?gN+tn{sWlw#2kTf<qlkLUizH=w%G?*;t`|aBALEqE+dPmt`V`Z-L(UGB8Z%12e z^3A;_H5Rg<0UCpOAhtp1qrM_=i#9ln2qab`1M#$NiwLpGGaHm2@^~Lsu951ebvZO! zLX3i8{pJ{zXqccxFd1~6p7=u^V26T96p>k;plIr#K>n8oSLJwr%@kfFtQns86*p6R z&v03u7vaeCXh_iG$h`E3Eh1JQ_TWXu4IDd%tK`rH+p%M`N{$TVLAWMB9)`n8riMEm zaMzW1_m4rm`)3Nclm2dN>FO?j#9$p5Tj*O|P}0`bS$>D5WXS=mD}qyeL-Ws^^Vc5Q zYWODJNG<hx##XLtF&t0V#LL(lP;)6RY5&+UUK}e61;<8b&cO1YuH;U#f|vcI>{=br zXf<4~sf3-41rVE)w82^BPP~h_2f7op1O%^QCrw%jB<~y`|IivGncd;|VdjW%<|$M- zn~F3{UZm_@CU_bHlUW7E#<I%7WU?IK<t__=(MHOuuO)90kRH~UWFOodB;JF)aitq3 zAoZSXoXhvmM{u=oF3-8WChwfv(d5KYM3cz6DX~kqQz@~ilsTiRQ*M(H4|V!Z%0$#D zmWMg0*hNHjw`A{Eoyqf6LPz%YtAZ{z8DNkdI1A*wp=IQ}sS}oytrfC?o{v0716wjA zH({PUrl!dbT~9r>u{cMNdv9VLCuPOOixCt45Zb1Y8Df}}f_3j0exi5e$`gd}4$Tj% zS9YD)zO3 at J(zZaj$0 at N3KRwxU@~0PCHau{^{(sm2grN-SGnC=WD1%- at mr@j7?TU3K zcop4e-qFSrZQcr($=>Zfv2k|AX|_nYug!6qqt#blW0PyBS-xs@{j%HUi>eO3Z~ryF zJJ=$hz;F^PLY=!2%l0wsbrREu0+?*3!k(wz2x7|8?uqN(k0x%27Ur)%vBp at 2;VHln z#cZwpV&;xE8cPcY#aIi)dn-KUj^ZM#NsIlY at H$G^dFV0p+i5p!3@}%xKE`eFyqcbq zM?l&de^@(Aleh#b^Rz5=$IR4t?PRSTKQ}8#J6Yqe&I-t8(ZY|;-ek0(T}UT`E{=fS zWZ-altt@(6z}Y((->+h;X{9~Rle||^=_iU+=~apMD$@HDiM_Crp7u&WK{U!vXO#@} z!tUI!SmbZp{Z%bh6^*skj*AM*%bZqkv-2V+USb!BE^4~OWG{6WyP|Gdyx1|Pvv$U` zu8Q(#ZH=>Oac!`(d$?)ZH)AECs=A8WxecffoKh6`h<VGULaq|;%Y+CC@f7cM<ZpTh zXUwfVFB0?guLz%3Q10>D!*!T0bjTlm^Q(O<(~j>w>f`e*?Hkwa>tA>4aw?bTMdj|o zJTDi?j?0OTD#;Gs#hF_6AkA{hQ~scklbm%Z;sO6fuIDM#6U5t2_KlhAqnKsnbC)ON z!)3Qxu+UJSOE|)bXOl at r2HaiwHg(R^mK`~SAZN>R^L`pG%gS@(vWpl#PyOc#KF?XB z!rlp*y$)Q~vTb<FE9Ue1DqPOQsBS}T86t)OKXn&_3g)1gs1uVtyh~v%8s6>9oe2gI ze|g7wDVRc&cK5K#y{#<~D#_%|Y=fFx7UtzJo_XXWZ4t4MK{xE<g^+&_)(xb+TeKdC zPI<Q|9BGOi0&f(yf`2XRa42wk!H!Vi*?kOt?gU;XzdxU+NHQ$LwF_|h*>E~!%}L1o zK5s>-PUGJz=*V792%#QIATN|c4XAKF0XQSr<G+yPUwuO>?F@~1vIsLPnA8LXoQbH0 zhzaJ#DD7rWZ*0x%wWW&wgs?wN18VeolDrN0QF7Q!;JZ)Zg9yFLBeNY8s1e+N*Aj4z z{I=P|b{c!i7O8QU2^+Cp9Tm*mh%djDXa&vt*{;F9v}`)!aM1fR=Cuhst-J$Xyt1jE zJiqFk+vP=QuA96}n0DBcd)D+eg_QR at X^bgvbwap2V;8;ukG%H)lkBR_MNg<))fKvO z&Q)DiU7;&<b?)l!=`f*ta-NY!(nuo|07)P~fB{(|8j+I;Mj#AaV_yt5VBp$dKg$na zoXs`K&-h*(z&5_dvH>IY+<)zJPSvT@VX*zZ?>!;Zsm?xot-bczYp?JxJ)r=1A at 0e& zpnhA`{z=LwmwG-8gVtLlxL`s`1R7s#VdLBkYZevn>JG(n&cp!dy-4S$Ax)T0Jv5-` zd00hn8(C=wmUOTk6$HUu;g|ZEB4HoP#Kh+}F3jx_nQ0hvnBKA>*7OIUeTitVTNzgJ zkmenZUQ|-mO3Qf8?mvEJaC?sz;_?an=FY;ZKrZ>G1~vZR^F45*?X_&9UXtx&yZ+B& z9P>X;^G9;cNI@$%$Wj_d at 31Z^0$6Wz2reLY7+j!EFTA)5P at sQ7I>ace+J#4;0vgx} z{jp7!0vOo~Sb$|>bUq~@(90bpI*rw~j1s1HH^}I2nSlzHY#VM{=X813ldgh$#Fe$$ zyjK0C_?X|SzicqG+(BN0+(CCIchI2ufV7x0823R>z-KVsW-yri#pI-5Pfq89m9yiN zR2V2uhT2Jm&GDCS$neTCQa7DX1>Daj;P-PPtCiJqCSm~}x}-XJXKyVh3v0 at 1zFo^n zEt)C&k&w>NWmH|xnc*wpa>8kgmZPCc$~6+1*#2r}?drmV4`<hIn}6P;!gIq*cWrLo z*$PEh@7X+j`$BO0Z!f?0V(I5UdHJy`l}|`~_u|VB5bO7HO=JUm9QJlLutNuniBiCn zbS_-fY+b)ANxh2Lg0z8~fV)uUz47s|U3&{T+VIcAqE<XB%;-F^j7N&r&)stBNcQwc z&m23c!3$#7f5d+pJR0NXS{A_h1kmpat)YlNV<{y1@^O3!fdG*J<X{9-v=_`$=lfCu zIh>pl8k}3t*d&$HA&GRdeLDVA7+J`<!aeDk^>}@)Fq(`-P81)w-(+4qaXx*gSzDZ6 z<?k-vluzdl9MT`YR3DAj7Ng};^~ysh1<if1YWM^C*^{?nGC9~o|0etnR+X}EmReS- zuCG^7brV7LA!c51<Km>n)Df`|8C;F*qlGENCJ&nA;v||pkIFx}GJ!H&3FT9iie4gu zh?S?KNRb4Q^&pq>=lE|;HWRhD<HgUaon1`X;?)%JS}m^1@rC)LMaSKvq131|6?9mG zX|Fp4Q2A`Jn8_52S;4b#%Ark6EGBQd7N{Mr#^)#ET4Ow44Mdk}@px@<`)Bg?u-P20 zk0mQ%i#b^4uT9L)SE>zy<-^oL|DS^XI;u^OIbSRU{iAl1(V*w*e4R4q8*UfQ`xFkk z>IAHBF|M+~+bc)7TF7T0PDm)=;DlVdk<4~F>UP%CDzw2)!KS9m^MiWE7H?)vg+|yI z&*d`rrDiu$<+YM`B$x}Ww_>v=>xqro%mc->12dV4km<NBH0EzC&Xp|Fn-zDozLE^q z$KyuRO{-vn&ssXOK62~;=%d=7$JxF`4=JbBV%BNf*cxFhp}@z{DFE98-!XwiE(Y_9 zA at f}QcMLV9`Vb-A$R-;qe}DiV5odY+ at nEf7aK1G--6&drXtW=?bS1kcJhJ%ykNx72 zm3RHz`!=o#=w^fA!9tqXkAx?I%O2+LZ@Jv~*&TL=@iQDOHY=;XkRo(3u$AZI0U%dJ zb!ZWVc|y4$)0(pKNWUO8TlEqAmJ_GwgS5N=Zo_x<adOkp*A&a<jN=H2qdWu2*`m9q zPf{rsJAkmwU^0oq_IHho_f!~m(P8^6FT5E0zkDV?d7^mc_HW+qjvc)1RX1I!oi6h3 zxyxHCv8^ZMyEyd&{Vw>OfRF!V%acm-Tp^d7NKH^xiC)Vk_yqO9YT#x^bZGcA5avWE z1{j*i?SK=OBuUnu*k4-Oa-=8-V39Zu15F(Skv18bJzkvcVnQ$Fm at _5oo-B*5%2 at XB zY$`r?s<wQxJQ`@8o~s`!dBx{wa`mOC-jj)qPndlv;dytz&}Mg5PAtxyoeq`Gy!7a` zw_YBPP97|btj%pdm72ckKzi$BrWzTM&H`W{6OCmRjpbCihsHEWf~wK5TP$4$NJ&y~ z3~f;qvZS5UIW36BH0os3<#LiqNA<(x#B^4hOpj0{F#^6sVz!sWnXQSS?m61pJdv1< zj!%>}p8FE3eYALVPNDD4x3uZwJ*nn}rPS8RwHgEj)598m8D~iWI>DYFL$z7N=XE=6 zBRUOfgk}cu7_$knMIjE)IW7u3B{m5nk_8xP5>B87I|;PX>Pe1fqD%on#*SxI&@jyv z*raUSorEwlGlBndbZV=TJUE99KQW%nm!k<YsskW{_x>0gfVUT7m+$!6^0|h`S~|Tk za(v~$*10W at RablYWM)2e(G`c^%3!`9Yc<W?!heo`6QV#tu7-Oh5EMrAIu3q at 7KDyT z8Wk--$D;{b24@lWC&Z1uq%<UNtZ{>0Zuug1{lx!j<)#Y$)zX)nE4r?qfT)IlF42Fs z|FHcnSRrt=>rbJt-#1Xe?&iwm-E)mvE%?c(V`rtGM1;;qnppd;g`F;9K9r)AM2uT~ zr}{mOL9e}L)SH#k9N`-aZ+z_0`tzRo*=KHV*X0OY5&iVO48rKWuwi=N8|=JHy6=M% zm<mCL5hnnt)TlvEI9b&liZ@`+RW&%@??8`g<;{P+ at P;=$Qh(mlKP&vK@?t<Xa9`qI z!l&`pG%6a+CiHkOw7kT9{F@;mA)eC@T#9H+Q}q~5h-O5n)@I0u$3N)Hq%uDCAFc9j z*7qgMj{S3qgaRCs-u11Y>|I}vRm;_Av|6b~wNZQ<qi<t_-z9wSCwiCgw~doWPu6S4 zj~uUQYsWT^*Xk#aooozx*Y)@C- at 5)o0O^8<Hc0R;JVY#!D)2~dB@TYQ@<6$AKV<d( zimx#>>AQ;UOX5jv1K(QLQ{0EJ=iOWfSByOV7B>WzaK(VTjz*6r<;Ol;F)f9RJ|tU; zS1>BAh$wu%bn%`?&ThT<u3PH4<%OlASC{Yn`n?#kbv?mJxv5z$*3v~IP$-a!QA{Q3 zBHt6fp<3J$iC%ABo(NH7rxhzU at OVEbzxF|*34p-eKMJ3BBy#m+YPwqVMv9fiN at gwd z^mF@v2O<Todi}fnOQ74E+$vDdQ1e+Sh2h}gntT&>td#X$s^aKsP1SP#5p)3X5uI_I zT&hzGY$d(NoMkX13IiU%yNE^HnEFXBDU}zqVrUJaNOVxr8(2zKj;Lvaqtk`TT at snW zjKhKM!=ArN2V<3HB^LHNz43`DZ}prjd@2`-$0nvG(C5YDzjVyEnlLBORl#I&#`9i_ zbyB$1?;7>GyvfO2W^&Zg7^}|3%#qQ%Tyd|@6ZClE)ogOw<*4P4t{Fz2MFq3lpECJI z>?nFO8Uq$D72ANG+k{pAtMJ2%$m)SVMvudf{P1wQY032#d*SK|U&VifzekEP{SRwA z$$f(V62F9d)nZp|&pyj(ASC?|K>U}cW&;3=ak6gb@^^f=FJ|~G;R0;Lf56_lm&ICa zoNdHxG{{}IDUZvI=n3mg#q=_nBPc2o;Wzf-kE0-+t>&2YDV{b;guWMW<)Xha=QFK0 zv>bhr;d5mm1JbIrvi#$|@!7<o&5M~}Ff05+s+o2kTZ!cJ*b4%;CM at t4;TfD`elCYx zZqKOQf_^Y`juE1=BuY5Gz*0JI5y at qz1!-w^e?p<O`pjKxRGi76eY%{Pn(^9DlIw7J zgtzDCM|??lZn{`;IK2*si`As_+%e%H{v*Pd@$Ls&Iyybs-I5Y2C^7}XCrJlG1lbKg zOts5?_TGnv9tE<eyraor7x7P24wuVE%3}F&rF^7<nIX=7g3ecxL7rO_&hzgOEu5JP zwy1R^_+vv|CJsnM4=5}wwajIEsfiedKKlXl_~_`k`L@+-*H%Rfx)=!`w%m80rE4rd zd>MKgYlM~+S3%N<CDwem>BTV86c=fT+oa({jt2QK4aeqn5>S%A3#-D;!7n11$uB~d z4qF?k?ea-&IOcGhHR((qwY>k`6F1qMrsV3lKODCw*cjv7uknxY?*na^k3`#uiZ*FJ zD$3rVqKtnZP33Er4}Q?X#=2ja;J%9aV6V2wu)=)wW&W$4RkQHJ-_d at VeI|MRjR<7r zaVFq1Ch}Q%rOPaNpXd_n{el1BO1Zpp(4X~;r##-&xcj-*`t47=^rauVHsvVUnzy{^ z+FftHGD#~Aep)e}Q5nzdrtzT2CB#OIhZEoG9#45asc}zM8F<AydF9P_U3=3lO<Tz^ zb?rkhed!ao*X8k+uD_3;!gzkN?EF6Khy|UOB+CxQ9PUA6Kq2uB5=vGE76Ln$zDSlG z&?G|WUL<(jH8;UGNGA%Pz85CND}KO^5`q6q;hWye_dF+;yXWr9!L7=*=RdNUx&NW( zLE}-L!25W^;P>M&Al{Mz$6lO)%8)h+ePQo+)~tez9nq+<Zf|o!CYwzCC8kJL-w$4a zdE6`JbI*I|{><hh&%ajL3SPeZo?PxZ_gp4#k5s?^ZsE5f4;A#QwvJ^I@n{$w+|VhS zJQrnL4>UZ2*T at XjAkh>Pg6#<%A>Y$+y8;#MbBgsBRh9ENahxRUz`T-k^k7b?95Pnn zJ%voHM7_peB?{Lt<zPN&%>)v~2eWe<vD#{G)EX&H%0?hR7M}|`LKaUp>>bM$!}XHv zmx(tHB;$<|1&EQ+1t0Hw1S9CF0xQM}Osix(I_gm9srW1?qXt0&lK25T0>bbwyRt|` zz-lGppk=iT3YrcDm9Z)kR7}l9_a-L4OzAe28wp%Be7H>#=ov;6|2r~K+n;sh#xhor z<z0?~tWszYS&);1+*i3D^X~v%u-%9n$#x^&_aV!+wKl5o5fIEAtQ3;<dOkV9SMu}K z0(3K2g~yWocepQ#R#gvo=#x(Aeo`sd7k<5~i936C49`BPJgXOJ@8jB^Y1FZ=VM09F zes;S5*%<B+zYZxHVS0o05|`e~&>kA~7(HB{ECoix?BVQf)2Z~ht4iyO_ww&$?`3g% zmmMWBJ at 3{IoCt3mJSA)I`~>0)|IW at k>@HN^FkRc>O<K*Ue^W-@{(<scH_HaR13cj0 z#&{6qVCOhIq5<!qR3KV3Nt;t(rt?#WD<2xdAL%t6ZqXXg;XNV1ym(<7*|Bn74!3Z) zuyFOJ{Z07I<Wt2a02VjE1=NV#^1dcWW^99|9(XBOTC31;$LX6NFr>o1Ty1sDX|oja z(QLr)aT%I|$&iRdG9KfssJ9qhBjSTJmnF=_3r*|ivMrt6YSfbq%kVj7tSudgop+Ma zj!1>%+9atW=LPeI{*A%!wU4?TX^YK1k&op3W?wvE_Upo9WuM3Auot}{n?LRd)pH?A zf#FVG*KvPz{r7PFP|i*88Fnu_F5tj|dO9iOyy2+T8?qKhe520_22;SFO{eSu`<Tn> zffn||pYbQK?+8P1o&RtvGc{Elhd*z9YJGKOskJayn;35vn}n(Dbz3bk6$*U8M%F&r ze&>K2CEB6I(L#?;05vd&>>T7KYV<n22HmUBEKY;Ja5<}4FdET25D^B_M<x>-AnohB zEioeF$!RS(fsm9+8Te#Ap4nx56MAo={TUUC^}cC`(WzvBItvaNo#tvXHrqrDc*#z4 zN3#Eg{%LE$mVFLP?&3CQfHT7wBiVi41T+#k8N5<70Q(BzO<H#*R!xQ?LBM*<(M*mc z{NBmbc}6j*KeV`cYArki!z8leirVs(s{7-(9a^0u4!b>QO;@$94KY?s#p0RxS5(%w z<;?2F8M|$&oUxg&=qv{9$80l;lV-0=b49DmAp`1CDH%zPCF5D)y=psMBg@(|Ox9Kr z?fZvTf@JO3)}f69%X2e>B+W{aMr(DkpQT-rX5x$@L5!#*0X>rB9%&G~9mD~d{5SWH zP79_Ifjy5tXw}T*55J)`W3H at 0mz4diUU9&MW*@7#)_?U;^u`zDwfsseLMyqtxPWdI z{rZC8RMILQ at 8@&ZDz<kq6orvYQfj%aUOTg*C`^)~puTkdV|K0Gu4A$lQwD~5lJ?n+ zu9&myO17V6`hU7^Z~IuFNY_12UK<$Lq|;Zpue3t6HiP#U>qJN^b7EzAskMmsPd_(N zKG4|-YHoIKD+Lo&OSq%Z;i&RGGkj3(-UelBql}F0O#>Tmd^f2sfcse)yXvyIk9GNY z=fVsNK!E-W at P7g3EODFMOIx9pWthL~%bP2k^=h%uK3|vkC9F#8d}kpHbhH{4=LHVe zP%VIbbXq-D#Gp55^mN7=G&~fY${%h+|7dg|#!1UqMv)>!j<Q%tG_*ACO|o%>3@}l8 z&y=0GX!Tj_fi82#;SXEJbe&^mCthlK&s<Dp_4o?uh|yV99WUUiRytF}KWU#Sr}<m= zc&bnx&8;0KtL>N5r%s;G7@{|#{(CJMrIX;5y}il6nN0 at jk<1M`&~`q=uJ*rz6k#th zdw#{>lkCPf-!Ok0;rZ<(N4>_#pyTY*1PZfv!(_JaNaHi)b7eM)@KN|_=TSp`3f(=v z-O6mNFD{UkvVLmg)bi58+Tt1^D&%vCSUBh!MV`w%KhG>;!oxZl^$G^j1Cw}!*YeX9 zK;};{oA}(>Gp98o5D)>ejM at _;kLJ_ZmAI1UMu4qMQBJ8Nh}9&>C5W;y3`Kfj;HV~T zyk}G^Z$gH(iQ8d#C;?IodU<eJnMyYh$)Kb}47#bYE+LAKRG*Y|rwkE?au|v>*C=17 zlf!_ZE7tOfWIgHd<)>o78NbQtw?$Xufzj&Jj4w0otZmkoyD^-DwS2;COXu?ZY;s)x zgvJ)#{+*Ga*P0Im3Q7Jdt!tn#70t|yxo!64K+FM<#PcS{h~DKbbOSpZy}4K<=QHuY z)jB*{DMYs4WpYQ{sc0;pNel0x<%JGLogM$HI2%*&3w@(y&1I=qkZaVa*J*HXl(5jI zD&<mf9MA}6IK)CD76d-SNYT+C&qd45JYpa&bW#N|VibgnA@{c)hYZYho(F<_-}9^_ z4?bCXu5S#WG%OB%oT?M3j#)L00VA-IrKXfc)dllMmjL>OyoLa3mHrGzZ!$i2yb{HY z)8#VX%JW%IrPQb;R|B#0rSy6;@m|#d__f2&IhZnQbiX-ba+sRstGArk at SD$ty`vKs zgy*PEygpu+#s~4bo5x~2H<rz$wOT-7a9TtN7 at G$0nrhG(uc^O%hsV9o@5pOxti@d( zml-~6wa}7*Zu5KKwaOK~3$JHXp7Nb|eVfXOj>;j}7LUWWnB|^njgo#U<P)fMkR-c} z^0(aKb~B6)WoQJahgc({23 at A9Z<Ssr(sjL??`_})xrZF_eXYZ|ok!|h0rhBDfrEnw zQbc{&)Fip at 8f!0WVdDomdrBgbU<S&6a6O~#a|AhJv&F#RrSbX1RG*XRs_3hq0&|nV zV*E$=nW at Hio;%+fO~mJBChML0UdkX@bitY*OW~-G;iHJ2LQn89pO-S6;A|m3HN3m5 zdm})tD4Gj3!k8>s-H^xq)XJ*iS@%&Rn^UsU_frr1ukHMFR_`p_EAwKRJJ+%&;%ZKi z53jW_U-hDGb~i&baUTz$98|yJ=v3IDfm;5FS%GAVuAtLC*umS?0jmA*M7Y`;%wl>| z0Hh0YMW?x&Tb_KbHj$goPmg62NH+=xoerKef<H?UUN2O}1)Y8b`ENQgo=O5j$izzM z0XYEZ4Dx%zun>@Rggvd-3nVM7DwsvzSz$$10v)C}ovOwa1=bCH)0;)EmA=X2%qa2m zyyQoU2!j6-1ByQzN*Ha4v2niwNFEK|p}j@(YJ_#JY=7O8vI)Fbf+QD~#giX7EkVTx zvMyW1Z&T38ZKpMtG?&eRT2S-MmuwbiCH9%Hj82X~5j(Fx3I8PZ_M77QRw9>+gh9<z zZfde#ohTRMvG7=A41W)~on|A792DQ=3a&g*W*{Huwh0k0EybKHMnSKKwCHrQZ^)7` z^As>tIMa63qLzmoh2(}@QVnH5(U$=`5qWC8gAN}EdQLS4&B35=!h796)DB}v`mM_h zQ?h57Yn|C+tPe}Cr%zjNjIDcPSBmM4>11m0bhWmW9x>`ak}YUhr;N9$+<X7?$h^;V z632P%;-Q20Zlscv%h6cf{*{#tjYUg6GWbcgS5H}^UlYE-)@YKusTH6Vnyi#_y=#Ns zrL8#?FSY1BDlH4vLr?x5>Ox^0x*jC3a<-Xl!wg{n#FcDV`LiF|jsC~GJTJR);L|<+ z7P2Wn58)yit)n*BNA%-H-y@l{XXgnBy`qJMFAFF1yH>B_f)!0ThKVDKLhfb#MU`c@ zEAg8u+iq9V`GYD8Pg(mu+N+g;v at 0|V at gmHVoK_db2GBKC3>Z%Po|j%!Sz5c&{&AJ9 zwL9(eDvL|HO9c2&!se=Ri;#L>qt0<t&H7?v5x=R8=Y1ZBO(W2%FMx7Q+=*}^=R!6L z)*Pv>NO(nRC?&q?pr6q=MT?vuYynnLi2EbOY8m2N_p%T6%XAXydsKnU0{?o(=I`>w zph2T0zE}wlPzMqxyk3oGHlp&txYF^ES at br}t_;)*IAb`?^=GaZk^lP-;K(L-iMvNw zX=N^7UTR&w=kh&w-Fe6Dx7|W$Q0uF$&85u+v^A<$vYALor?D$q`}Q<%vcy1r4nRFM zHy}w)jtB;V55p0M+*)8laj;dvshn*@Fq@&u5lkY!SuB=>74G4-L4?CkBP;n400$W5 z3m200EV5p|ZP%AsI1_I%k+$!B?H*r6{vYxS41E<*VKL*TBa=7ajr({NxpJ=zqiPSY zLjEiMMdsbmcN!3!Yu&NeS7=4F|1KgDuVSN?t=`}_;UbN2UKZ~E5ku09U>Du5+L_S+ zMVES>%3sJ|feHgJRK1c5n3XQM3n(Y at h%D?S)fHu&^%AdZ|F!3Y<o~9A{(;5xVlhx2 z_lB*$h&`E&O&=>e6LwuFTMcSMlb%F6K7F(_ma>e;MsxVmoc+7fM0L!c@|jCVOY5i7 zvANkuGw(~Q4pU9}=yc2y7z<|V0Yfb0OoV(U&w=smfr+qIYfdJD;sZv5=Rkh!z(mmO za0Y?dCRF){rt;&J(QtA!Q{RqxN1-aBA#cn$>J!IRN3XosaPBN}hq?3Io&2FzX!FSJ zx7~E<<nbftH_snfR`0NOudqvp4;|cCPXoVB+GcvT%?vk>2u5j}q5A~xf(C3e@=8%( zZ!2shJ(*I{O at lTT9Lp4)!NxKn`w8E(ELe)$k_evP?>kH!9G=vUe#e&?VUe|N-rLx+ z<be#WM8@z}K<LnQfstcON^nCrDsq55Y5P<lmdxQoWo<p;H%#7q1*a*L7;|e~*-^K* zSKhY&R643pt)9EK`ouut+d<$AdP~SZDm<XoxToS>0w??~9qRA2+<Kqoqr*h6t=q*B zS+_r%f1(vyYTa`4V2$3Ew+qj@^Kx3loA##EAx<Q1ds5>hZRCqMyT3OmDsJaj8JP7? z!o1_~Cf?N1-R(1?t=xuELc_N%?=<nQk!5Md3wpF?*9*F|Cj|LFNO_72Jy|Bh#Q8?l z=R3MGR(6o*8&aU2-V7MB<ECN1e;@a4GuPrCX_=;)m6F%Z@`t4=vxbyNilj!^B~^45 zgIA^n|9LdUi*hnPj5>HD5R4<ZHCB2CM{a%Z&xztBJq!2<S~HavDN_*|x#B4afTefP zO`Wbia^^?7b{)Bhe8rHmSyP7f+SbupiX0-l)~JJ+zop&(7SH9&o?ncwp15^kk0t4> z5Bl~0CG&2HdvnXav at kQ(s1@?bxCuSD7$z*?dQDH_=+J<7X~HcL<8!l3Q5!|`4WMX9 zd_x=rZgj*p>AdOU;nc3rg58t7&ntX{Ph+plYmlb{pjFwXPEWbi&E#}sSMKrN at qp+r zxHYfU^3j>3Z~#QUZ;$-TpDw`4KLKaS9j3-CJk0n<;Izmg{076>ZE~M&xp0q0Pgg`Y z3&{la3PN;{EM)>bt)LWyqt;DA9H(YGZxt5`jV*zLo3M518iBJ}x<)`-(rT1c&jGep zL!n`GhaK`1+QANc0Pn!%g*;2@*Jzp6=~Sf*3?s9SaOh5^N~vY#l*sBWJ0e~86uU2E z3CguA*+jUION4U-t5jPlvA!3crG)B^;?1YRru5X}(OE!<elUa&hcZaN6Rv*`ew_^N zB~+vS8!e9uoOLB#$e|5~0zUlKDRY*x+G2Vw3T0TNM-J{-P{Yf_lKEH(Qit3^5z4hi z4+dNh<R{dhqP{A`?+E=(BO;$n`Ed8ATKPdAp!_~AL!ShPVO`Oe6nIPOp~GBC5zl0$ zJl%F;m%_<cNnHDm${tlSPT|szR5qo+u(ho4<q-A|$~Qb7PtF5BjvM*os1)ez=^#yv zgbR94Aoggr(0nWmhN9a5e&zt at i$1nppF?2?({CtZ%Tw#4P8FLTYOYI_nS5b?$ma_M zLO#(K@`vf$uy2Eu8vc=K6Q0d}68IAkbo~3}j$A0@VeS6~>`TK=HX{@BR15b+OVkqe zdQ=)dBxz~{o8sWJ72BudB?UR?$3;~V^k at 79Avib{$LNDHJ?cmrrBAB~IUw0=M^{gt zT>Yc&*#oo_nwM2n+&*C9c!6;JbBOCC*xbiZ3vJ4zD2rV+^SpA#B%n#-ID^Q!016Z> z+S&i8wJHwyF#J$5>Hr>6!@jd3ZZ%~)A-*rEs2qw3tfF`O_l7|Hk`A1{uIGP*^EeOW z{Ks0RaxoDP2SGJyKH5>W2v)#D(hcw(fsf@)z!gK?NprFw;!MF2GX);dLeX);%A;`_ zw|0I?Il<{9rU(17&sn4#TQ5vSmsn*9VuXOI*h#EFm0&-z>J at bsF2lg@0v36B7cPk) zKGtRR$PoCwC?YB}lB4GyIJ at -=7oL9qKzjxRSjE4ER`a6zLt{3fI!EH1-HT~CvB at bV z8L3n##<Hq^)mOPF=@$g`dtm(k7;$-dmR&&o-)3QUT0_j?Zmgjbk@kZvozKH+7^PYG zV^NnA?k{XMbhX9*A$e>%*lIn0!!i)G0FtDo;=i8F#qyn5dZd(D-ZRV3sG~B2rd#X@ zz6jhDa;kru%{$Gl^vv6f9t~U+lNV8g2?-%70~0Z8xB4&mlT at 8KNLTmS&OKAs3=)Jc z7C-%S!`A#kHt#5RtYwIVh~sTE2M^{)oW}e~d7(mwCJwNZxbz479&AP#4oSUon$st7 z<P11))Sb<G=KpC0Yikg@x8Kl?075))AaeXKV$M-+wxzM#+EVI*yTjv#%GJUt(2>;9 z-d{`7$_!-eNMKXJd?U+(a#yG$2p@&H$wz0$TyJMb27Y?j?aihVPN+#CvUuoB!{{8j zkX^Xr!tq>b<Ivgp+y$M{Xwn}4&>7N0ZrHXzhVG>-%2&2bON$bMeOnJrb`b1ggFztR z0YWb=V6dV30D6eSp)aw#0fysvWU2_UpvFEP6a)ezu_2$5t;9Yd-<>LN_}*dN&#K(w zD5V-l%|LLJI*?f>I~~YEv#bLdYxCUGfs9{OdCG<Bzs2|{fRBTEkrmX;n4Ot|SV;Wq zXB~{u{BECa^Gj-WxnF)E!*cI@a#2a(jqvB7R7S{TOvnEiwto|qP!G3Eb2B|MR_oA9 zC!R=h7Co>sMF*Y2Ao;*k1dMK;dw2 at La{(1%?7SoSVmD8m_wc9=zipzUb9<Xnabn+5 zw|D0KdY7iV5jH;ax4oKB!^I(M-$DM~2oy&WhVvZNsfPltQB<ys@FRq`ltF?~Dw&AK z3_=w4J+or5HNid}HWZ#|cUa!BQ9KSsR=TY%S97&;-QZ4N=MwYhChxc}v2eCgJvJ5L z?MhYm4n5)PI5Anhx5(!HSGc3l+{?KA1!6Iti<M*LVgbc;sSXv0qe}HRCDa<RWH5~e zw>V1hwIF^tj3mmLqI=6Y3;>it;zpj6pCA4Vw%j;<NQY#s_ES52oLx|M<Xd+Y3cmyU zeoB3F`K8;=oVtAN%vh`F`~1SywamlMU%caBHnn_{&`@1qpehwUeu43^$SpDKrfeqG z;UDGMP|hB@z*0RVU0;UrOvcrbGUuosS1ToZv3fBSZgpdz>_p9LyMoNaC4&DHq);x~ zmTAEmL^4s}mRok5$%zgnlqZu%EOjgkgnb^FT*!ohD`1FB??#N;+TVec_oyPq!|4z< z+P53%xdiSpe+ at l122_JJAm`*NrQBqGG6--5mU-DKr98Mxdtr3oEJfA|n)V at mrCqeo zb|?NOJ2Qw9Iqi;ByAAX*l*Fd&f@fI~32Ao*YwP2-?!0u0g%;PUfp53B^7kbXo4RSe zsQ1i8RUQTw1=VDiFMPivkfg_9%ltIfiB{>hmLZpQyG+Km{gt7f^tk_16M7-eln*uB zM1B|Ah)U)n(oJwfOYQ!_l5#OqLSN9yRBz6FDx{LtNB;1>^&a&Zzf1f2Mb&XGpIH|L zx<pJMKgp0vbkUk3BKf#PNOZh_G`6;r6ovjQ^9WicG4x{)0ZRNVsSa=!;_Ice*9-mH z;lyrqpEWy^`^^9BJysaav%ExefxEk9Lj5(Px@`+~lOrvvKvz);YAIMJr7oJ~m1oql zi#HX*O9v2rNvRIxf%L1KXFHJIP%`y|U8w%N$~MQ{8|UcjA<xvk4oFvM5~u1KPg<ub z*}A- at t9v{plTtDsyGkh0Lg?i7vC1~?-a5ytvsm`pIX<aUO?z)1FMOb*s<765w9icC zvY~*}fzq1_qrHn}G2Wo89xbvG5vnE8#ReEeJ4 at u)Bkih9yjR*^soK%|p#ANt%}uoH z_`9H|E8H@-IcVJ*J+#lyO^6zJLPgEgzYFCI!A|Ww*(>>}u06hY2K-LnZl{fz$9YA3 zSm&;`JUll)JBc->bC$`$I=^nj9#|t at tN?&tSYCuVz0j3SO-2&%VB~b_wn&h5N$H2O z!yYNsEVXGDu^5uK%$ET|vNMA=7X~CS2d9FXk+D*7S1uh~9gxp_P3y4UXLO7hu~&dI z%|D7ee2Q!EIM=(o%d-fO7ZzuEqLN0Ila3m$3vsALYFv!$Cu9~Nix*Cbc0EPAkb;tC zW`{opvrY9>{}9yihERCwgNI=4<%JAZ1(zU@fdn6#jO4&pItVt|vwZKYQFewn4aGg! zBiQa_MzZh#n?3GdjoqGv89<)o?$^rcF3#Q1e|yn!c5N>IL$x1lw+8ckq{k at +o0`^H z$FB$<#yU^(r-!a{l@^$f!<>$S1EP+cvGP)DbW?+tn%0SydSS;WcUx`B1fbP!?lKBn zx6Z0#3E7I#B*?Kmqdd+Jnm+iTEPUUZBbYz6*N5e`Z at +wB%dXapyRQ2SyHw(C3;)b_ z`!pf-B?<6HvF_9SCtI%RDOl~eJ)_Zplj1=v4_&I6IzZG{k#0T#B`7ipXs^9SFKF~N zC__Q7t0BEhz2DV}kkYM57LM(8EToAIQv&L7DF=_WINSTU0`i}@E<sTbNE0P_Inp~I ze5{JVcM%QIF|Z*i9MMr1>M at a3cjyL&>j;y2FlrFV+=W9_$*|*u(DRb^cs at 94ylgZv z=x|XB=x`UH!@U{*XkRHzDmP}-9vmWBX5iBH6~o+RJ$I(~@EV=5)Q at TvvQ_6_-tv(N zRUW7Kme1pMI#|4s=U9C at ogmYdQMhm8JRo}|;nHb<Frr7HqiDd@SBL8&mqVhaTDJ81 z5j|@uOG34}mjUEtHaZrVH%%p7Y%p4QlB9(pgK>Z*K%SXeaJnXl{C0UtBtA=Bqd<0K znjkIGIJxeJ)uO`&q5`&E#{doNtl@e_lcv%yD0+s8sfTYPDS>eVT3i9C!F^ia^wgS3 zN0*FPz21;h;{qC at O_3H~J{+&c?V>H_6tiJnw}>1<h=C%u6S;|C8(V%dQ$rkAJO=9F z!%09kw&BMkpYbC1V9Ox+McX&(N~af|5MNxFXSWfLk<h9EW=BUW^|kJFz{8ERxZ@-6 zEs(b!S5FojVVBSDLMj$&m-0;ILdr~C1+|GQxTyF9_ulR8e<&`(8)@<OVa0by{uSca zKY(BBe6;17n(83<NStf;-JK<9qz)^XN4d>0=xBC-YXhM%_zmm@#uPdy$$>b%(-1I? zM1kNfbIM?yRUi)}N&dhA=;K=I{~#}3dmPm=!nPVXf8|+7It&m3MZlGlI{V2}k>*eR z{NOm+_p<i5Ecd>;H>*6DH?qJVQMopiJ at E|In9y^6u4Pw{bEFk+=UXjcc{IaT7(CGk zlx;=kt9&nK7xvB~w{YVbFaR8k$aNv!;%U}3alkl|oZJYPSuzld<@Vrl at _Xm^_RkN3 zx(VmKj(E^P0j!Z`Cx8{6VF4_cb+j1n31A7w`v3x91511C3ylA-YFP;FM&dj}za_b@ z@3=A05`b<B8bbe8?}^srz{h%IS^YHQGV>4ZTb8>jqxQkt!(Hy{8xiJQ$FD8(68<HM zt-fMF+^f2>3_Kc-Zn&z#MY>CFq2cSpAL?T!p1pBJuS(F&?t_QhUr at QHZiI!OQ~9ZQ z=2PE>kK!ozGcC*7>g;qPM(A^GX`G%f;hG)tLkWt&6k*<HO}tFyvjDvW^d$<3kt+yU zghv7f8F|+kq&c+g<nU+V>S at xD<mu{tkTyQLQ_JV08r-g{ymZ&2l;6%x#;je5fZkZb zY_TEBELRHp%PDLb-e`V}Uc^6R$#C8wrG>#r)L2}ru|_fHRC(<~)m&KTj5+qF2e9W^ z-%rRX_5Dn>G|4z2-BK&ZY^R~86 at P8R=kD0NQ!O{UwZz>1zf{7qYi~>yI4|ZR_kF~? zsUKs!r3v_G-fn7F5SBUyn!6|6+gDNTd>zuu at S|Px8{E at SH7%(>+<P!5>JPWsvZRwr zcQ}g5PNO><a)PAJY9gCx2rgAroiOJj37dRcv4cwXry**>s+f_E)$D2Njg*i!`tXjO z)wX|2<v1PMU5!_R79%U^dO&bt?=5jS37V7R`B8_(tlE2x5|U3eOE5-oYazQb7NdQK zN>5NB at 4I$z3%iJ==u&FP;OE$$Qy%VrPJP}2C$}9AVp-x at q&2sp0VkWiMl1hwZm-Vd z3nkp~C at xZ{ZDz at pJ8dt!C0y`25<y2c5Z&C`@Yjx>zUTNJk5=#ky%q!l3H$#5 at 80^K z&AXZF|BRkY<b1rRWr(BC7T{%+?7mbGi^j)cccZGBi#kUIioB6Mjol(ALL$ak9`rF> ze~`|G&-LX!N?|AjSO=#)cG5vI*(bH)z%0jhzApdN*x9AID;0}7U7XHoj-3mvB;>5d z?Be8vFHxObot%7N4ztEY)*&aaM8AP)#PBuQOglf!J6)@kLq3#bM*YrFkts4Moy|Sq zFH#3D$`T9l6hb5 at ZJwow=`%vsr321&d%qx;+B1rzLOP#>SF&tfozbjwt&AP<XTuJ0 zbLFKK_!va~)!5ef`sdNRj|&2zdOsgqIexV at cXDMRo1G~{ExF9dEx7wO#yqd&%kOqJ z<`%=OA6Vt!(K|Lb-*w#c)7$^|uT<W6E54Ozb;Hm0cUads?)5E8bt0RNMf_fsK55S3 z#N~2uu!UfoBl}uFs10sbT$KTB3tu|(SiqY}s-){ApWf|Bv|Z8&1QpFp0oAjlRk{;` zh;!r%0H0W)Tf@QHbm@E3g4$U?zEawYtQtY%aCnx&-lijLidX{vd^|ki#5AF5qyyXD z&i`@b*XrIgSFdGTYu<?0Vz^VMudHA}#s(#t5$}*!2Ht%WF^Du2EBbY2Gv#8Yk!?5~ zjDIbp5eVFP4li<JDq=j1N20-iz(<e)1O8Fn4kMn{o0&jz8&}G+ryw(3N`MXws+q9s z2`if=KfxQKYKZwodR}gFNv$!xX{r~36k%_P{9mZrUW!6&_4uuGtdV7F>-k$*vmpT> zn2>qvp%*-SCjWw$JyhI&TUUciOpw2R-}Qgx1L#9y<AC?e5xz8U)gl2|+vEe8>4`$A z5x*-vQz?|{apCh at FS_%lJ72VA+I-QSSMGe_7Cry{>yPlihv)V5JkHvYf_`#z5p~vj z>#d(Ud(Z9v?fgr=%)kB1ra#=i^Y>~0dGY!0v(E=waEH=!glufW^)CudA%XmR<srNS zhQJt(>u=?65$llavn}h?WTTb`GVF+1%K2C_U>GAX(7fmdm4--XAS&885sT;!>8#FW zl~OiIHz{TSSpzAGCOf5s9I4TO6-ZsQ!y`!3#;#_yn3D5qp2w0E_y#KnzUs|2BHkIl z*;{m)?G~NE?zP#xc7x7hH#>`7vwy}LY2>`R5w|TAv$`x=;aux<6#!(b#t~DnHntV3 z=HfP6JXeif$yI|Ui)qzvb5~EJ_PXphZkd9`6liyW_NyJ*C(r|{jWpI;s)Obl?$T)x z3db)J+R;M~Xb{-X()Ao%Kx#g50lEg%vJOlXS%xIbv=fc9fTBl7AVzn4XD$e!mo?a9 zbW4D4k|^^It;J=Hg>3E-or(=6hnfuzlZp+3DOC)bwv7IA`ijg3P#mo0u5{S2HC79n zMvO8Wu!5lfKeM;dG~$on=F?n^%OPvqI-(^>4Pp|MCQ1KNUNdH;l+q9icbYbG0;o1! z{gfa+xW0J#yP-$Dl}K-|BW!6X42v&OPSA~mm)k3$9c0!KSPIa)Lf1?Dt-|}bBru*V z$(Y~c9z`w?$L?iRx#bIo1QDr+w2W9{se%e<<jBWPu|Hu9ehPmlM<}|}Qx+6vj!)B9 zDJ7fo&XiP4?L1-ekw(TPix7LLe(h4M?8o$;NAu<RU@fkVl>^Xk4?;@?s&W2Byzs`Y z7v6DGJ-0NsuzBn9iv%7WkIGl-rp9GfjRt`O9ru8a3GPKL*F>q9&t;;apwDTyS;)jd z1YD{_Lq3zB1v(XgOf(`D{Q%6Omo_BHSJ=lGZ9Sir=)^-^bTSug8gybcD7i_dk+p+h zwL at xpsm+Lk=)NOX_VEHEJ6HwL72w2I6YQ%@{6t)b&wvLpURZvhm6 at NKjx*wI$?3Bz z;Fg*XSl;cY5mlgdt#hB@{)AuMbFYG?DnqBPbOHOfPVcYIc+khdg&aXj8-aRD-3|Jm zptG*bU*~6W)^+z0qW&aW{Qqr8Naii9QOS?A+a~;<=8b2#w@J^mI{i%Ov%(MANAQ^> z|0w at BK4Yb`!OG4S;6LeE7no)iNKkyx_A<}Q&Yy1`-n{A2+;jE94}ZFGd+6T!{m);$ zBXd*pQH&9~eniNjUuJ`wZdn@D@mw<D^O{X`yGvvDX~v-Ca2bLsgJ?G(&d?C}HK{a> zW>iAsm+Ul_$gh>0>u5znf-XV^uw6-+1N)reI|w;uq47N0QIL>h!=A$tOkc0zi#=*O zcfnaaJazUxN~^7BUp!sU%{7`Ex2~0sZk?Gt{+`pD*EVXCOA|A<-}Qncf^gq$`D2S2 zh%oUj%2oMe{BPn8U2P$%gRf}N25Y+&vm+49^N<=@x8R#z<pM%*3VD+yk*X2+qoaw! z^jLJVly|+s8ct0l;uERR2>+XoNN%o at oSw+LN8{s*mEvqZ>Y#UL at b0rW`fev)jMrn+ zpu=kRGb5qYL_9H(3R~ac%9k3k+*BbkidRSTvuOD>9v^k*C#I8?xm*OVCgX^!3RCRe z1z6sw0pvJO!BdFGBo0sp6ucjH>~!z|mFZYe%$UotZy6s*v0*6I%cYwU at jbESMf>2^ zU=9);;GwYi??*=$Q<Ggk@$VnV<Ff;QJDBqWR|Cg9dMhvD4zz*lztG0&^5VkmbQ9L+ zI1<9+!GIUNuFy-21L|38tHK+MX|%ua!?N&M%vcuO5e!Bh+I2O6p$)uQjgInGEDRNn z$MV6pFrs-AHlSXQmN(cs%)X$8qU2T~<x6({f<OgttjT7A;(yGAz+!=Nxq!#3!@U5< z4;dQJ!laFXD@XeH?eXJT+o4&bClIzT>BGf^vEqpX^^tO3n?RV<m2qla1+T4n at WRBz z(F{6xxOsOqFnQdSITh0-Pv`jDrR3LnVRKbPhk;2`G~^D?o}CddiAJZ%;xm)7=J}cP zGm*@s-E6j}Jj=L{L1~X9Gtk;1m%jc|!6t^0Pv77cxD9l<4ETK}Bm61g*kp;?a?Z`x zYpG-;Jfb&|ej-~eR%Y7(h888Pf=`5R0tQKy{oo80uYz_r4qVU~kvcS?cD%$gvb+rm zkExlZno0MhNEb|~E`ANFseB}sKc3;UN9Hoo=4MHARx^I3tg#gvQ^CrS$#80VBQ1Vt zB6Id%Y`&PIlo!LUagSg%++}eH$vfA~;qqFwe6SYPyhwb$=yKd=umLdmPi@ih#d7^% z*{?nJ%tuoN>*s9zJ4VAEhwd{bw3h(^L2QUSAV&otkGl$lv6`fF+-%V6$l7FQIYls# zCXdS49Zz%Ij&Pb+<qu+Perg}*d6rIdU<*R7WC>zc3$_#MxC`T at sZTP=`SA;R{&g?b zM&=_ztQFHb)2`;3u~$2inY_QYo_tO2{B(X?_oCQh?5xIH^j_59*q(<x@WR<&yUUWl zaN+#e)<-W{?)i<2;`w>Vg1}|3znXtSco2(O<r>IaPA1|;1L^a0X)G2F`O*GVMBp7d z4Oe3L>I7EdCppLksaFj0BbbGS6-S~Xxe1s90j?1 at B*L47zLneuSZ%i}d)s-<0YS@; zPlinwVT5`3=mD>PA*or?q*iLt&*Rhxr@!&ix7nU+3eLoZXnk!Apt{D~q{-7b{=)NO zaP*wVt9i~nysLJ8Vf<{dc&_BQ?Z~TXJrmczk9_(kxfMRy@~kX38`TPO?-C^44jX)U z9IFu_Yje{ALPp^JFJk}4fE_<q0)U}5J_`wkiU8UkT{)P42%=6A00=sot<v{^VWM0` zSwkRoK}pd$wZovCQA7AA89EmBGd40{PV947Er+sf%#@|ov?t#+VJQ!$rT_&h*F-(G zZA}lvOSxmRtmL4;B=~CvfIW{NPug?qO>3bvZZjtirNR*#ER0te9};v{kHepG_{S0v z>xj$YFnb)0h%H+zxxH1l-<JtR?V3&gm6^sJM=Kh`i0QUbp99fcZ_atzP}^u^97e4c z{ojoi-C2uINBwd%x-FeMcc8vPXIGwc2wDE`xN#I_o^M%8Mf3=mDoxSdne3^RQLB-p zwBUDBAZIKU3(++p?m*McXf_Pr20_fhV+8$%`y(rCqWhz?!t^C2^)!RVB3}+Gq{V-z zACVpgC@D86@vJVH$l^C>Ce+NRiYYSZ{EoPL-Wjn659AM5t)bC*cg#KtpRgCdv`wEr zTpJ14=bcgO=pRZo73uFUm+cYPnkQy=xl`7tYt0$9WhWOZ4t$GeD_IP(<_KEzU))%n zUp}%pvjn}x@=L|j@IeIO-TAYYFBA%;L+KdYH&nJv-M|5JD#^No;;#sE5h{aLPqjS+ z+J<a2h+)oXApZef{|K!BB8|aGr^9Wi<Rr|{vqm(n(};%RpfSL{!6U$J-`w?Kypdi? zkiE(%B5Ll)$Cl%9yMdW&<0nU(==4mY=}yA46sKdRMy>C})7y7q8w+3Zj=SG-{Fkoq zfBGZgo8LrT!S=10gUR>9?^G>SLba<k%P09;KY45Rw#7?}*NXgi4!!x{^y$?mv_E(` z#y`fd;0(ya?*8Acz<7b at s+Gdz_+%!Ph{@fZ at _e3Y4l}qpr*0@P25SQ51g?03zR2<5 zER0kmrEDOxjZBF#uohdM%I8MWj~tmE8H_PMcnn?-m*pM?X{`)=UI%nrq<*1<O{b|T z<3QOAoQlL`=fIXpES(b9eJDeSdT)1&N6V$zf+JzG1*|pis3{VUTc?`>TGW{x2HU86 zOgmE4+9Q6uE#T%Kn at X1^jHXb)s~=g>8nog>!<^0}3|4bh=L!aVOHP+##OiX4Vkzh> z!de{0T9{FzU!odCkIQaFjiQ-n``ZVGB9#SP#o<hwm;`r}lNy(HikdQN_~y@sBK}Ay z=K0jgwdI2+SC<Y8ub(WJs#E3h>b1jXx7LnaI3d6L`cvYk@$Mv7<ISx=I#tM}YUx at s zf#4wO#?j@A*#s>2j_^<p`+0)~xphVX>yN_+|EF9#1Nx3|SptK at fuX}%w11H7NElNT zA2tG<Sb_<GRY{4uWM-^mNZ{uV9+GZ@EJdCJ*;JkfS(En+8&8%pbp6JWu{vlZa1XMG zXWG;3AJb^;jDvhhGV0>A=iKo*9!?ZG2&6;9ThBD`TkOhsS_SEb*!rW}PxE|ZyTvEZ zR9EjkGB$qlc^l`Jc>cd^{{?E<p83<Qi<@WD3#EW9)8ZT0kGER==5V}m^TS)mUwzAD z#W-rDo%-jGJ^IM&G8@l2uz2fAl4K(aoBxFHB`yYQ?I59qr&IAlqCm*c=#VH<^<sGh zECK8#-KUY342?(XPU1UB3Mv{kbT5;OS*3Eh$z~kDE3nX6ZwJMzjgm&#>2YAyMTDru z$iJDlWfmtIm#1v*nS}-a{5{7?<Hzq?J#b|)%xjMvGH!oEG=(Rx#}9d?&dtXXfpZSO zO+R+}r!SoSncEtbBR9Y7zH7FKcdnk2<@{6poGj<)tKKW;LA7|3eJz_HHj(vJcMcKt zUx}t7!AK_fv90CBwd2ckYr;?GBZ2IAERft-I<m5`d~_XrTn97rI6EkI|1#?Jkc<WW zZkNT3%uyv~24|60%p>qgW`stBcLLm`E}%#ahp0<;Bz9&fMO+hmd7>?_7PvK-Y{?M> z4?0Oh+rw}qM;+^~p`|Mp^%6}c^g=#+ at Xlp>)Xod9CpWJkz5zT*#W$`Uh;4t^8nGYV zI<TC{@wZQ$n2QTSY9)b)(ya$i{p@j5Y$+qgXEuLX5Dw2Zmk3R94ZR-!SbQCHjN)HX z12?Q?;^DDuJe(ERLu2t^FaywH>=@C?x5P)8UPAvyt4pB%jSiJxO6{XUhk;}w<c<F> zE(7cvam}BJ1p>)%Q2t7`D#qk~EWV3;R&D=Nw)x|a9esiL`j=T=205Y6jC4K&g@?YB z at I5{9f}<Z3-}Q3K%b6^>Ft<;NAMEpeNrl*d|Jb4P{2KlvzT-6(_CIM{0XQUY0uBk@ zjaCDQnLyE#ji=8t?$@eK{_d$+KtTcS%RkxvPQ16T7dG+ci1@f1f6>rb(yA&g_4p%! z(n5Oc<i$cPS`;5wCGWBM6CXY+eo^5qp6%nU)a{xD)vCo*vr$adCp}iD%V{6=h__^? zOK3u#FWMb$yWI)54C5`FURPkqfIb|`n3Hr1mjZN6{!~}q7AR;J7h_J+^ACcjOh#Bt zNnHedzm(!}f9u6BZiyeH<fE5)4j%OMK8FYnWJHy3L7$hO6<<$Jyo{bD`Lf6{-8@wm zwoIjBHWTnU9dsMdC*5XjI>7 at SAjAtu{i5h5F7c$tX|klwSbh?<?L_tKB6$VjcNm;) zl*ImWqD4)Kw*;GWt79)6J22l2uGxcKSw$l*zr`A~{=gbA|ME=Xw>@Rc?Z5U*A30?? z`Sbtvvv=q`^Xe8<?o1+Nu03mV;%Y-U{Mb7VzZKK7Kv%y^d?)0~&xN^qOXv02Z4`UR zghRoA3pkchiy5CA5{Bra1on1k(x5D>p7^V+A=NDJ!NyR})Y7=SGcEotv}67Q=SCfy zhR^K_*x1A8_A1)(%G^G{_?G4t2(gAZN4N13LuWv<SQxx<nR(EJl$}lgN`V~2Y3T=n zg#g>K(9J=nSK-7)f1Ba6y{Z!N;j?~kNA{Juzmd(o2$VGIcs`q^Zcs!4c}JIv0i?#F z9fKgc;9(L(g^W%YS}k;(mR-)&R6)RrD(P}Im?~$V`WjB#yW85Vqt}Mhc&%IEX=K at c zL|&r_>N{O2QRTC=Rzzuq=mYSWv`n~hqGTWR((yTvE;2+0)5SVn%ApVS5i^{+&-a}^ z!)a^v9ZS$O(%$_-o4&1n`lh<*gEGRUTQ&|v=0zPsCta@hK&MLG52y2izLQ`$ofrF# zi1uE)nbDc$S}ju+i9;l-q;*S7LX{Q7rzvBN8I!8r)|SiR>*wq`c7{{)`mXb6I3<V% zDkvB*4&=3ZKcgWBRJhSVAdm~>vgu?Z79m<#_B;6iCc6mI5%{7;EYQIsK{XB*Oe)&! ze^8CuIOI7p?gp=P561X#mb_7AH4JCSyH)0dR_4dI!H)nmBHifU(y}&c<zhOC3Mwzt zLlQ;|BMV!Ua#l64@TJqJBgxV}WsV8D!qG(ujtNv2$)Z2(Iq0&Tb^wE`QZpt_=P~&+ zO1yfn!@1N1cAjDZ>nx$^`IXE|Gt2YS;a89NMz)_ed(6Kat$BaXGx02CC-Av9AHDn> z#W1qm^a~$&&r!?fyFd7gH(3g2iety}f`9e(2M<2RXXX9CYwxz){q45hBvRe7cpB$6 z`IU0q-|y;Knx%|iu<r&OOiFd>l4^(>@-v~xbYZV!40Jb1?aKDxZlXom)#b!jaM17K zcjA|rfzRscg$nM0GrX(iX`*#c5vVnk<x8lxdT3SVbsE9`Jp?Rv4v3rZlZ7B)4IXVy zkrtyt69@xf>Ge=rxZz?qW?>LYK^c-;>KX&@fS&w;N~cpe8<_uM*AY>jy4N9FN71@* z=lNkYo at X^4G)o-kEOIntk`ms$bTN~l<7Gfehn&BJz`~GnO)>xYTZ*%ON5W}-F3*oo zf81MIEl!}z&n=byntyx!(prvRdh&}FopyV~7PKrJ*#4W9n`VPy$6Y?JnC>goyiJH6 z`YBAF;b90(>=nY(dbDMs%tUF;SVjwRxZ2HOF-{O$z;PSIRVWzNE3q}%&B)F}LzpK$ zD5aya`{*!s(fy7jcnEunFPxgXwp9{FAihHw688EJJ}mqSlU<4aiueK0UnVU(mg#VS z`4NZ%Q`9oDfS!fjoFK{V?qk@Ng*G4NI*+OaOL_F)gB$8Q-OgP3j>;U~kvXhqfOH#F zWTYa#mhtEfEjz`hJ6vLCGjRz%TlL<Cy9Ef;97CYqQB<aTMMd@8zYBj<{*9ga^F76D zvLkbz-S((-c+-}jVxhyCj{Nj;hC;Nd0evug<Oc`!TrYFFAKj5Z5~$>^yGe~HwKIQS zpt}0+$f93SxdmX~tzVxN9>V$HWf8B<)~MgtY)q6>3E#MX+-5=00k$gis=&pAaE#em zCNGRStp?`!7&-&76G73nva0}*YL+3o(3Ql?+;0?nhDTRQ-=#7MxN}uPaA at rLYe!#v zrdYfD at Uhjq<1mQ@;g20zm%zLIwe!oF@YK=CHn7qs4AzMYFW)-xy4$DqS~<ge1mC^z z%=w3>O|i9mw~pLC?|(+ at 04x!`PYDk}r%3byVae~CtaqrTyA`X1RZ%-ZePJ2O5T|J> zSQ`Bl)77Jm5F({552aY;ve^U0zph$aVQ)kOOr+!ep}pV)-7m*F2Vk+DXpK&yQz`C~ zK_9|-w92SbZXxAD2c#uY7sm}53$4|}(IJiQmeR#UYG5o8uGQ|g)W~A1XW$b<?zR_J zrnJ=L1jp*b?|rOip&?t#;-bL2@=NK?T2Ii at XAb1Msm?DQI65Vs5D&j?`;XwOSiQf} zUTnQ~{N4@7B>wsTC(X2<_3*uWxCHm&)@VE$q%>FrY^B%|t460lP!XD(vc2iX39uxJ z){fz1L?S4KF1kWL6jW>#RG){N5F3BUlN#CYF2{5vpSv8!QSWsAD at qWD_P8j=ZoZ{# zZYgdfZR!smr*O?P-|FmRDaN3OkDcgz$qsauh&qwTqoj6);vESc^Z-aEiq$Oo$q4<Y z_IbtvRBA%`Pqj}m=qm`a9&rJpvxF`wo?;=J?p;-8!_%r(47|?B?~C5zkVp1OvAU&% z+Kn`LnYTaD=R@jWaQ;{OoJY#KzYF{bAT!UZmTh*X6Bi0d#_>|m_dPB6SpTQ at Njte^ z2h$lKQ at 4MCpC$wM|DCju`+G1ge+JigKP`fReBKHzZ^F9dxsSCxloJ&V2LLJ4(LHS` zJ&^aJJ!3p3==2DqqPd06!1kw+(}_l0BlogXk^8Z<k6{L`D7(@q!RIbT)$i;b%oF1P zM{hrm_;0E`yb7-#2d^^RAGHEruQ%hRR1owSR`ZMH0Wi;+i5k}$^ab!rC(2E5chzM| zeocctEw#Pf$HT4aPR|YGr>-lS-^Ea|Ctx>F`3ow4Tt62DyUG#A&UZ;SUI8CdbOxj% zVOTRAd263_C1g*azdBqqdyy=5*bRDMAk9*cdEjB!<(M5TKiV#*Y(LHXOFQ0JMQ%O_ zdJ{Ma?A<3jyX%qmt0l<2qZM^L`V)!N=5-IE(y9!9Y9QULNMzvN>8fBKxX%BozU%gB zM`5%R^XaDf<t=+GqF&)P%@dS=>1B;=_e^nV*faa2x2xWLAidk~R2lFC7WwHeTV1NZ zB^$29y{qM-@}IG6G~z~%c2CamtR43w2Yhu*i*meyS#$8jkr79b<dm3#@9EA07af31 z$StPPI5bJ9&*_0rBcv>6 at Q_QdJ&^iYwc147bU+kX&a{xNK3VkNGoZoL->?gwV*E+0 z8PHftZ5`pP$z9j6oa#EaMm?oCZn}vYApn_`i(?t=3YXQw@~X-6aVU%(6paWv0OV=3 zCQ4M(!~vkk$Cg?RREkh><rGXHTxeO2zMKj}g(bN0Q?({o#_|>61>Oqdy0M?ZSOQBs zxu$186U$FwpdC1LmCPqOIbss=(@9qDnoKAjNi^8#YhxnZLBsPxDK&QgOyjmArJsMd zZwz=O_Jqs)9NrWtC4b#pTq&fMVh<UoyI~2Jt&!w-Xy!=TG8ae$4nBP6lV8*Tn_c(U zBLGVaRMMX9@2uQ37jikS>Mm}d?!zdgb$AM|{{VR3;=Z`cIvj2hSeTaA_pgVR)&mQH zwp4ps2*~cy&S<?0GGw$JSAw+|q6#SF$nSY2R8j8zEP~vZiluh&`&k0vidrv9ynjD< ze}MawR;+<i>c)Y_f#uf1{Ook^Za|fa<dHwx&waKVA{LWiFdCZ3AJ_9#n36+(%ROz0 zlS8(|&Li%)8aJ{pdSZV2S&)ybt&V*y$W2`aiLxJF%l5;6Yx&S1c)njVF)Nrf)5fs1 zfJ!nT_<}`1rtR*FfbDQ(*dh!bYDezh&|Xk^@b<&{#r7q5UwqfJ at 5dVQ=V?A5KEeEX z8{8@GFIzsp@5Hf<m8I4UYR)^JJ;ry!ki1+Zmv<25_i!(2X6=Z(YvRjvB5WQ(i({=J z3Nzc#gkPAsHnABfjwCt1<R3H|@_xYu#<OF!{R58<=dFu0zJAZ+<8WcaV`!=d!=b&y z=yYQ2(Z>ihD1E#Q%@fZ?#92PQKfj~66*_Y2WP)#)Y{%|?uzsgGwsq=cdi%RZ=f(RT zEUj%{Rhc#29=f5<SkKri-u~VL&n1`Qf#QoFd&kCefO{zXvDyHV)^rx0DDu(Lnm)3_ znzpVUmX^~XEMwCYQFqW@R$YAa$f1J>ht&zp$N;~Xfq+ZZF6(y53aX8E;QQX$t_9ms zh9?$yDnHdTwyQ0~J-B;?ui7S>Q@$_jM;11ZMJ}10wdSPn(-!B+E0;p?F3_YsGW{do zty}J@>R*5g3t=X>dEx4W{z3Y_3t9<2{Ul=Z?}x89hRX9z?mR!=@+IPDPj7CltuD?? zH4C};L}G%_J=k7~VlSPqx?nAi at j5ZZ3%XVeh2+3TS;xkRoj88%=$6l`H$<mlvVr|P zju*)k(&O*oH*)kK^U at C*Bb<O_YayyY#UToG at L76+e!nk(-OI+8_mnNsKYZ(A-^hTo z-F51NN5-TnVIInWP2R%p!QBXfwW=4s@TLwJ2dGKR!UJC7=Xs<av;1$W)!j*I_;-d9 zMq6TR+#jru`QvjZt5JfYc{R#FEH3-MdQvuECS=FU7EeU@@Lr|I63Du25x;G;a)N=O z*p9zs+WgS=Kff|Nk&e0&HjA?o`%L(*J9E{LiNASIGKBjY{Linz-6e;*&wI%xtXE6p z_$ik`^^n)&KquXH%_FuPmK>K7{9W-u15}vY&yY<L1JpaF27HMAN}g)z!}yqDNw-0f za3Mz}RXgKbbjOkTt#&^PuV8jF&%Yk`9bx<H5Xsj|mk~<*=J|{${J!FgR#1j+Idvq9 z*u&OT<>cX2N&sh&LZ<Ni`sv2{8xBAH8*K-*bZ>nHY-8fX3#gxPFBjV0i7}~*Md!XQ zGevH0+vWujr1C>Qs&E9&Qo%k>_QAuVZS!FeN6^K9QsAXj%{_4BE2 at k89+~pLJ4P!u z2i1mi!heB$)PPL$rdFa}hlI at 4rzacu4bY{=3;D53G8k~XY*v&Qiq>|$KvpNfHjNwb z1*Lc*Pl@(`@i!q&*#ue0S>PkX>4|U^RZ$b%miz>MgL2|sp3^HF;{n4=g|Gwp1}KbV z(^IXn8UmE7WVIw$1Z4ueEKJF52G&BKNbzQ`Su{J%nl}Q;l}`xUf5B(?<OV7v0<|!I z;1?J`RgYX{4rcfR{1sE65%E_7KlgB~6^G1|pY{4I_cid(i6oEBmK6Yrs2svi=%dWC zLdXN;!RMq7ICvi=#IU^tloMb35BL?v%d)?tx{28pYH7i4QP(Vdt-V=TutqazXDItY zwGHqN;A?Szhxt<X9y{Ku>ZslJ*QYw2v8k%bl0Y77Zx?MWXr}Wq`T)vASP_)u<{%Bs zu>5)&XYS;mP^hX7 at JvJ`WuTVwp^ga&OH8=_k6`MoNV$?ppotCdbxl6qDDBkl1W6o+ z?Zw);iM6t%P>-x<c2}2cd1={bq#6gUZBOTFg%Pp_{3q3oNX-Ccg}>K*@45atyz*-- z7vIaRGQ<uhk8ONB7uAev&=pigAf(YF|5~0f`|Ds?ETbqk+cQZs!V!Ze_t))s1nN(y zKHxuSPT4e~K0R~d<B0bRn$*`>Mich$^{+ri4&z*){d=}$;W%aYBE3#3(F;*EU=VIN zK2AZ$CY?sA1&{RnS>_)5lzbBQ^eOMs@;x&ahiG(Vsa5J*Wb$1WDKDM6t(|S5q*BUC zS7>PaWBrshpk$=0<r>S5(zDLrx`lO+9_ngVuZ}GCP;#*^qBv+F-qIIRB%R9S_qB|! zqeFD6msBUYM!8&%QuTNKB2%-IR^5RDLAebNn)qAWcV6N8Hy}f6BGo};QD28u$fV*? z6u`Ri8=M2}RVX5PLaT{{5nhLfct8z=dS%#&xy$FfSF1<UO7Wg%UztSDIz2rgWHyN4 z&vwPku74Xe&xw>vA4gv_oO=LaaY^*H&pmG^i at tA-I{Eb&XqS@@ra2|5$dz0@^Z#B~ z$Y#*YFREfT*Z;@$dr`agL6$8JgtTZF_b%kx1X(Lo0^5tb0cT59N<4KyfayjEX#`(_ zIz<UIQ$(p1m5w&lkri|Se*=a>mmfiAFfpXeU?FVNtM)Vhqw1-esdzkAyZg>t^9&v{ zKLL15Z*i?Ax_j)hb@<dbkRMd$mMGVf>~?%#PN!m#Hh-rbs3n8fgy;N3V%!JK6tkEG zAv!l(p~ko at NrG=+qlkb}V{Rr^y$Ki9?0%jFTPSY9h5*p8a)LE+qh<}@;i&EF|2{{# zMtyB at +2ZL#^Ye3|hfU?y;mq{0s#koSM%*|O8bhro*)_n1`S~u-xbXS#^r2f0@QLGR zFN|9(i)Zhg)TY+&Iy6?XWM<q~Z!%nY)!czgM-DgY=Z>zYp8l&o*Es1E^lkbxW~ZgO z(=C(F;{+BRE<1{rk5D^vunf4w1a$)ikNYo2)m3!w!DbApKY%|`4i@ZPd=xM+#1WQG z&g6uwd~aM!*><e1o?s(T*2 at 3c_H)%&8eY5oqb at oXC2wLNswW<r{O`S#Fx|-inS72O zY8fOd+9x|hgTTq&PXSC5uX4WA%<(VvW5nc{lJA{7b@1q)@7*{~`z9&orNdO28}hsm z|GjAdkCQMM#aI;U3-!lP>jyD9rv6lWs>;WQFq_3;BDl;`Q8$>~KleAOdw=y2Av~+E z*_TX at Y}=n|S;(@N_}$)tVaN;h^`ab!K3c%?fa>pUQg-0F%5U3G!DkfcyuR7<Mg>fd zod|lU)W5{MquiO6AsVrv+Pp0xF{Bu}2<^xK`z}6k(&6rp44XGGn!3w3aZix}Q%LOj zzl4-i_Mkl(x1wwt-}0V9#x{nYWV|-Fw6xh6y{Od-hu?bC6q;Q*I-#|gE*ebQjr%Gj zmZbLx>$t$pb3fHGR7!py!<v&&G(v at RB5s9qB0AL^r+%u?e^bzZl5aw?*JAQ#c=cyM zsNxO6Uhew{)Alr$yxpb53FQK&-w_|VKLjWNCiDE4hM*)r*|qD{N0wz*l;nQxMm&28 zAkA+E&lb5STh@iS>1G|VSK=JI(-Hp2#Ns$?kI4pX4~2&)$Luyj(AEut+{4aB--B4) zo(Gi*PBo9G20w>`1TO*bI}|7ZGf)PIQUNNd+Wqjp- at 1{}s)sYbu#3I3`x1O!Z3#&- zOZ@*?@W0GmYw3vZuqh;ob+e)dCbcFBEf~j%AftcyRexkkTai(i<-LDvcVrUx#USYM zU0E{Ib4%Kt1>aQePqOSi0ot3~Yg>j!t!uw38x2bwr>yO9rQ36lkfoG8+x0nC>#UZk z9X{4(k9I%oQ3qglWz+U&)bSM=>70C!d*GKUprY*8pJ6<EWy>^Km$V$SMQE>vl^u#| zMQAvf%Cu2rmWi_L#1)pwACm4uDm_QOTJ>jmm^Q2zm~wBDbe-gopkO<5vF)hqy6%3v z^6U2p-Jf>n)!W-QD{?Al;lF+r&I22(s9B&M_EJra#|4xi8&CDF%qOiBKwv{kz9&TS zU~bU&46Tq51^^GtvSXy=cLIj)fSQ*CwS$d6Pnb2C#iAdWHQVncK%3Qj4+FCCdFupl z^X<07?b!rclRb3jGJ)1?f0yuVlo|-&+iYKJyP&a<Szta8EqQJR`0S(O1)xsNjL&2< z$TM`I=N%{ks0uIo;cKAoe(h`*kfMc)RU(DRl1T&-BCB#x5i$e at LhL3zs}Ph~zdCRC zcOgMg6Zpm3SJQk|#RC4ELfMgrZ(A^WX8T*gPv7)Y{1r7fAYZh8PYJJry-Mqs<)#Sv z203H$+QE)$C0L1^02t^SUpaC|?XqILo%~Zfua@u_`Ktc2=1dm$&Qc~=Nipv-=Z8rZ zD|7yUyib;?V>MNtDV(M{nmjvg5LJU#LE9sg&J5!?6$^C>Oy6En1uAx-;vZF4;N6IM zr|K50(DX(|QxoWa=s!JKM{`V|OmrwSg0k6W3N2MjMW7c6NU~%(8f04NL8qRKb0lvJ zQ46$xb|!TIPR`ED#(!^+iLuKv-r8kUXykqKh`gS&@Z*B|nJJ3isn?aL&q7n^VS-Tw z*bbELPXVg^V!JHxJNi9+yAV6u>-AIC`esJ&1h+_fexi_vw?mO&(vE2ui`29a;U%P{ z4_VRO>FhxU+GRDr)$L%~g*3QYI&cl`effSyR}GmHfm8~ZleJWBqFfx$k9Fu{F?@<d z6pea8izc=O2(m^1oSd-3q9QqZoPA!~+|$R&QL&4-_hg3e!kz!6j!o>so40qxDHIu( zbXFGD&|LrNkU at 1kE8{f$geL^Ft#)^f%fCVa&)Q8>{Y)EMOXRFL;~vMk9>57#<^G^G znj0Id=Bn|i&kLkFBRkopd_U9!X}O6~B<M%y53LKGKBz@>r-eHXdZgdrpl6tY6gHLa zw8)6n@~Y>MEh63Bm4|WG;#n9v$^dX*A?%8(*x5mkDsAt`XVE=`^m_SeW=bo4MVd7N zM5RJX<i_fg%?yEZ%9VtfEVHZ#I5m?a+oFCZ8NKh+9N9~^uNxhP2hPp&zrXgH)!ZSn zskD!8UDRpUUUMKL*%iS+?ymE%rMl5+#Acs!nfIOl_=N^pRjI`%9(y#k7(D;+^JUPe zk$5e>OXl??_w60Hjr7$%ZUbMgh72TyXfmpMnXa_GUmU<W`Ei(Fy^QPQJ)Eq|JB+Zm zC6JIR_PYdB0JX1Wco2rhewKodN*6$>Ub&852RY#L9^ifnnA-W3rTN)<H9u9DO2oqf zR?8|$@e1`jn7}EGE{wb;0xQ;uV@n$!#dk5REw%&JgFFH{Duo|IwW|n6Jo#X;Mv6eM z?Kld22?%GAe_^N52yqsgu${W60|d0<!zN5T9gtY$Y!#_MI?T(+Eb5$<L&f_Vr|(_x zPS>M3pHY0BsIjKzE9OWWwD8Ye<6a?@K74Zh!S;pW6NZu0^m=mfa?qBY+N|ZOmiSz7 ze7Wf1g_Eybf@_aZ3`g|3{Id>?=#~~|R(k8Db%Mh0L)?FDjTZ9V+-^skR(Iml>Vaud z8-c?|JJn&8uAko!;`Kd at qi|iTog6%zC`S#*CoZ{Z_ssBKH~K!f{!q80ZU3y#^*Z1o zic-yb9sVAY`8n?0EqfuamiKlZ#e5&<G;!+B4gDkaq-zT)VzUDzeb?u4O;jzPdXO6z zaMB(7VDX3Pin4FU^0AKocuJ@vUs%!~A0>HSZmGH1mgj!$p^3A0=KzP0*vSw^+B_XB z&3hcBo3rYnega8WXZ!A5b^N2+>vRL0R=J+wgUG|5VuZe~!B-4)37%V+n?l!4!kz>Q za*eF)nj8AK?17^e2vr;m4Fz2SDj52iuqO#u7dt`rNQewwB2Cz>B_S)dd_d at nx<bhL zlwR8H41)p5526~ZnYS&@d(snu%4RdtICgWcemPmWBfqg6E!NzrjoDOs<x;cqf@#g@ z<l>S|QKENX!l{7MXpa>Z%S*?LHoM_@*3nujm<rf*rf_AwUf-$*T<#Ye9X8{?bX+iu zew|GJD(J^8Sxr6Nk{dnLw-)Q7hP7UuQqxbD=MMdJi`)nOy((=#OfYuow0&{n5Bjv3 zghxg4u)*C%H_WBh!W`PF)C!VBZ15X&!(1hv93Y8U;q}}iuhnS~zynBusB6&G8kJhC zOjso!NS%i*s->P9N{QQEc%&Nw at x<DR?8c^h&kcZkDnEOY3D54Fx^T^$?L&XMRdBkP z7%|y-N-)BgRfDg4o$e}~eM6qCi5|&mVn0wUR>c)^a&@GJ+$?*S8dlZ5kKOWD(9F9N zt=cGJhACwU=gEkiDAEx?Wa-}{O{yK6MQ&gh#gGXNkCxafdb at YY{^(U_SM0X(YAxYa z`J47f|D4ME1n&ZLC9SuL5A(B~6(?QM?4n<cPtlSyWSSlMus>~q91A1_ODQn>;lpcG z=f?i{kWn8Q;DZTgMhNj_>Y;t9WsOAwKDSGXgtu*-aQl?N^%XV}cD)Q+BDKkh!H~z% zjBW?YX`=p|k}^&$AKIC|$mdNq;%XoZA at n3=B}!?LPYR>kpHfZ);X?WOCpw*Fr=C51 z>(js5QT#-MPNpHtU2a)A6trXc(L~(CG(E4GQqPV2?Vtf+<GB2ZHYJ)#^#_$K??FWL zYWzM*KGCJt2a)rQKJ87JQzpa|Xl;tf5^@1~EuV-3_YxO%3ljTP{fs7KD7S^<w{59w zfG1R=`YBW?Q-d42&$1vnl3=BC-2qc4*qV|a#2+CEywKnT1Z=f6_*n68_W<x+s2*jm zrVcOk9<S7XJDFwRLnyV-H>n^%T4tFc34JQ;%hguboHk1#AI%2*o>8>RLoo`LyTg at o zpPqg;ONyX%hlx%yi~odRSCY)$+_FT&io~|pJ5D9)9=2OWO;f#)sul104A{+giN+2q z-W6Hw72A2$*6a|~rl70*3NJ<FBE#=b;*1_mCgQO$*n-LpU_kO%IXO=?hWa-+3#E1s zWS$`nM6O_C0Wib^W0ZzIHSBZkO^8G{6d_RCoAzFm)9;Y(fh{TIp9IX}@w%pVw4iLq z;_;eTJ37wuY@I@_v>=>-(QxE}OPkrs!BfvZg`Wi>){4vPvkaYbjmhmaSEG)&)yh~l z9CX;Fj<_9l(oVO<3Q<Zqyjqlz*JP-WXvijYpF$Ff-1xNw3^Qn9%1aSkZgouWODOXR zc*<kw{s}g*vXbKAtIg(>^&GMy at UyV4sFQyw%x6bOvvc{##`MPP51S&3hcC}Pyl~4( zGPQEceE49k{mW<mRnam$w}QQRCulkVGs?QSP-i`U6^aUJxv#Gf$G*kFY0!_E+(_6p zu#^aL3E5_%h+RkL<PiFXh4<7n|875>$%PYTd!Rea=k!`?=a;ZfjP$`2H_tES^q#rC zV4s*-xjd_81KV at UI4`fkJ}RPKt=KXt>xsB_D at dSiFou4@)kK_c_f?!iq;D`egBDN- zQ_$}}XZ{4nx%>S2tFYYmL4ZTLOADOD{||uw(_9nX)7v|WtkdK)StqdBa0rl at rdkQe zom8YVT<)YUFsgkO=@csMPEku`lI?@AY?nPI%Je<L=v(DxTB8S+>Gs;86S}Y!lcHgC z+HpA*kvE_`)V at A-JZyJ@eSIOZ|3llE07hAy|9 at sT+0EwOT$}9X*kp5GB;g1o90}nj z+#sNGEICLpBs2*gV1>3|En4f{gVyq+Y7bDO(AHkIwN|ZIv7W70y{i^4ywClAp4r_b zT!L-?==<Ht`_B8!^E~s+Gv_;V>SF7AkDeWq`Yf2Ymmzzjn%j`n!10$d7aA^OBKfGs zN at hXF)*fdTlw+ojuZ_=fHAiRpvGbrO*MpvPoT<<+po+JSm=zt;!z%Pp&nXk}^$a<L zbs#NrhBUEmGeh{_<^7R!gr^{B^%2vAUqHqyhRqm;^LZEYO<+H@w93lL36<<1Av;Bf z<)lYGdq8Fi3!{e36v`oB90VM!Qqa_3ZArtmz(fpA5uP}nG}um1?HpqZM@|x+f_g>` zn<qSV<yZ=im@=ea5iAFP2>F1?$M*qj3&<vBOo}tC$Z7!2dOW`M%T)0xN<6$FQ9Ep# z_LGZV9p0>c at Bo`rmtVhlymJhO{CqVlDXWjHX>-gssnoVn|8;FkZ6$N2Cl~R- at T}>n zOJ?zeCs3^_o$uVgZM~4p8jW|GCEq1BObo7d;9iN8>zb^zxL7&5Ah+0;k!WWhj%@PO zqQ+wIX1sw at HiOHeC<Xkd%y}r<9&b(19Q|CImSncz=VwgPBo@2L-4RFZzdzi;>xZni z^z7WkuX$^$%2O}Om{gdQ=}wRTwrfmY;du8qQp>7pbHA48%gL}?cOT>FErnypZvE;7 zV`3Nnn4@!Ium0 at 5OB=WExNJ`BH$Qvg>)+7c-JzomYPVkawYsr7_h at f#3F$-T7b;X~ z1N}46f_$aomJXh1B1+{9>Vb}#s<dLsCzI9;Af38okITsZ7n7w!S|b17_%~Z2o;v>v zWx>}M<G){;f?@t;P6B(B?<a%bip!ZiBTVU%EO0O^Fl3DM()gRNsU<g55e6u~`DQK3 z$&7GVQ|b#C-mGD5sxK(!1xK{2EQ-_4EoU;H<>tGm`i2MxTSJ8UlqvG*qbtc7A4!Kg z2Z_w$<%XoAZ&zAOZ=YQgLi47ww}Ls!xRy<_YAt7`*NmN*&-XnPR?Cd8s=?EX#m4Sw zID5lcqnkI^=huy$Sk(T+*1<!I9}L>l|5yHs=u at 1Sze8d~CuZ>JP~X8C(&2^Zkki&x zTSIL1h_VtngTxUvIO32U)TB+^814#B#ej;`QAV$<`7Rw6K^i+J_HMy#4v(krf??l~ z4EJd#mhrPYkM&_E7Wv|1{Z%l3u0Zxmbx}iXMHyQL<Ys3Mlf0_Z!>oTnKCU?|qP8-H zfF at +y6FFm$4l!jXqzlF>>W}{l at tE@D2pcJE!`_#dnVFKG{NP at Q$5GCtW81JNR*UC2 zP1nycg<FR=Rl{ZKI%5xK%6EpGyc;f6+2&)zpdw<#jfA|$pdHAPSYO@|5sq*#GVq6Y z32(D;Y@sU}=2K2A;S0lE$cc0O<so-rQAv#3SXt}jLEFrM6tY5(#mmWYoTM%FbTt-j z<$!l($xYSl_!}PVHRBt+V#b7xEHaJAu&P;qNmd=bHPY~5Umf`TeUF|D58GI_9d|pU zp^;$W{~dPZxWw>3<5>P{;F_;MgEC;D at NYFe$rB17Jt!I!#|SXI9G*;oCk9u_A76k= z!b{@A{C^Mrb2u$7I;?vcV<`{wZON5y(o-YRzYHrKPwgk-nj_+BPr|KDeD?AA4EgJz zj;}L1p4kvv;0tpp;}FcRT4j#jScz4(M$O6$55k_1-4j{K@KM&2uzlq2SSR3r_{0YX zpkga>TB8eHm&m%lT(7qzw<J3=H6<z05jAXG-w4*<szQ%DEt<~|l8;%|XEw9K&mFz2 zua?*J&}*SpeF?*t<nih;E6Z5bH{p11()ZOYeG08C3omiQQw*k$f@M{pS=U!<z8Y{1 zTh=Fw`U*xB$)dh7*5HCZITk%{!m=@o$7Ii(T%0#?>F6c3-nlc2^knPL(ngIKwQ at ua z$Na|l4o_^IR#=(WJZbf$_L=Ngv}nSYay at sBUaPGwrzLpe?nq0QqGa^>K}eq!vUcx; z8Xa at 2My2t4)Y82{!9wdL!%O!j9;=?v+jyOm>X%uMDW5~&RMi|@yLYN;me%<xsk(RD z)OjaYx1RXp*dNn`$O*h>`37UswW>iiH at F+dGYmLtWL0H(0VfB$T^t=B7j5HMIZ9^f zh>~1uWKJw5pUqy=RLt`25u5DCM6oFxyA~JOZBzvbY_yNhD_}-4LD%|9b%I6K$jR6F zEdLCJ#?Z;)h_i{>{En%qwR5PromquQiBy<ZrY$YLI*+S0f04U9g&%jVH{M&DVUMw8 zO(=-Zug{E*ZI4N`|12`b)*h1(<r(8i$QhNMkvTs9v#mGVV<Xc>WX}1m*0)FBtA8JT zkJd{W{Fqb9uSL&piynJ=N#W8_(Y2?SXv<QqchO!bnO7<u#*Dx_`a|k3A7j*<<7m$O zt}dufNs5+sT0<4f at K|fWgj22 at 11?(<Gsh<1=FwWi$fSibxKv9%3^)C*(8Si@N!e*G zge{V?gH~L%!Lnq|+^E$0!zF1MDb))nj#xY~zo5RksIWRkXM8!aJ-t3}b4GPq_d7Ku z)fG8Ksdi_DWqe}g1xu&)E~v;Hds<!nys8vu!m>KowB)Q2>Cwq$S*f2rIk{=xH1GJ+ z#-wGEQ%VPJ3%K>)_%^*#vlB&Sh1Ab-rbuGE>>_IBid>8w)x{tR$7Wl63<u=}*K-U` z+sEf|xIJs^#a$WS!D4i&L?R=dk*p}5J&*;3X`fGl<sZWcoRFFWYVyU~q~lPtGls)f z8p1;aE8(RRJd8zlt2>e@*=T8Ukc at S>Sj>YNLPx$mc;EycWH=>?e#7Sy;d^fz_1EVy zvz&jJMe-z#sZW$m0D{x^@s<P at SQW|c5%Jnbd8jq3r-@SQTR1N^&g;sU?sJ#B(o;(k zou1VA2>X{(+jH!3Hs^{t=~c;PskupRr^AxqN}R3_MKm<dNXu|1BrbP2qTS_rW0u%I z96hh>+^!f$N1UgyIDXxu>!q1kkI$tjtEp;NL+ph5>Z;<xv=lb%lD4SqdRmagu!)oI zD$`QzULpA$5fLvlpuB7hK&(8p480^6nR0MsWK%;0-yqj{K|V_u7MDC^B^gu4$0QJn z9{grAB|9RxO03dSG}^Ra8QqPL!{^PR;I>p}cs^2eCYqI{r12KLETgczFe9(JE<LFr z&l8n4CC8iR$}i4$xpUlAS%u|zvh(SAQmV64T(d`2H54Rd`ZD!x>3KQ16%#8)O(}6k z#U(k`CFhmqW|idS6ptz_8IfO*Q(8D-@@GAs@{;1z)!+L{_vpzBT%H_v>8Om7>ij%d zM at 5C|LVcAbinh*Z+4+TE4~rw5 at o~|1wz$WmcuY@^K}a6*)rvX(S>{_3B7HJwBa&bc z;yrN}uA!+{I?^{&NDC&EMV5^B5jxXi at l7hTm5k4`SS~4VU^-E56-};;ESFilGx at u) zsZw|vr!(}A^+x8t<*e`ES=d%zUc7~5EHxwR#4h#!dCE#kJ)W}SGEbzZw4{t6z?-3e zLXZEY8#;=UwR0?Yu={6&;w<z at qf`oKq2u7q-ib1YUyDUEq=x5og$4ka)feAKN|C`e z0A at LLaIW8)Wd`dkOJwLS!jV|$Iy0l#DdX3taWtvL(w8(MBfZ4=*+7f7oMG5$(OH%| za_2PWRF at ScrxaI?%b%Jv{=Biav0Ep(rjK7$n4Vues@ONDBxd&2)2Cj%U}D;6-}w1* zcVuCDwR<8m)FMMVe!o(kAu^0;iZ4Kh<QkbNKH-W*_RcAk1&gOD>Yj-u?(`y0g1dM` zs&`y!`pDD*Z(^D|nxhbsrq^ap%d<K%b8<QFO2#TrCcgebp)1u9<xI=X$SqD|^Svq2 zF=>gh&gdwsEit{oGisD0rYX|F%ud0nYX&1^SOIPP)Upg)t)@-Do{-f=g_%cMt^>=h z9B#SF!nX`eH;Cu6vrDU-KONqXUiPIoLtUR^%sC9W9%X`m#JrAg-WML%HlIA9(f{yh zqvhi<b*<j4-$W|PK%9qj*(YJ2L7S9aYqTBflqC*?syp2kIa4!>)8o at K+=&x%D?J&N z-l<t7>7I(5=Ud&SSrf9$GveKDk8^5vxjXoGx~B>cVC7rhsroyXDdsGD=H?pWD|~tB zX(@^E(NTQ7g%-<DxRt%-q~*Zel&qT}6!fobz9<2@!txVT;jrNw=diHr81-Eh-VP-E z``K}|_Vv)#Ao_|>;15SDOPFAsqE%mG$;4;Wm>uI%RjS!{qrsa<dr<3KS^7&nRVXX4 zc%{hv#UO;Vfaw1++UH}Woh^a%)fpAzX4jTXpETBURZ7w5X*Jc;Mi(VnGL!N~OdeS@ zX>_G4xu9z5=<3OpK3t||*MwHR=RZw5nS@CmFH}qNcO{Wtb<MQV#mQHB#!i}EQagKG zMFvUxDkoQuo?2Cq?5Z3+sc7Wn5qaXREy}G|>UsEu7<wg at vZhn=jxWn&wA7L8(8%5? zOwg%fszyeu5Zhu&Vplw=;6q=N%dGhDCC$Ic%FoHkEX@4<(wWoeE}b!LzU7;>Q>Tos zn>u;amsgyz at QjtKmXS1s<MlfAebP=+Sq-)%r`a{4Awgz2Xm18LG%y4Ee5NAJXxUZB zXO_E+(DB~<$f=aiB^Z_-W4Sz%l}z5|2Ap2S@=B-IX%YHv?Ppi*dnl<B8e(%Z-5K$* zZ2ZFUv1JV<d2(iPu7}TXL_(@(`i&|=cCslJy$lK}0|lf%dXz6O)js&0IYZjAKdyy# zJUM<ze3~zRp37I3l2e`;o9#$W^El6owB=WhOq&=PnUYmtai<if6pXbNjL2{#X5=Q7 zRK};J=cGl*#>8XnNm->qeYo^$?X>)bdZt-crWg2JsfOc7Rj$jWF0FAUQj2HfJWES3 zN-M1pxEISb--ELLU)Uc!8-w$krG1_(3)2f(c4mIav>`e8?uSauY>_saS-nf$$vMT5 zW$Dh8l;jzyc_pb?rJ3>GOkd%Q3CShCw3%t_`<vlS8A-p%<w>>Wl(}O>9M1?xWTkIp zwcS;kZ})mce=1J5vR3SF_CoTg at 5qdn$1S^~B__njMOg6*vj-AredWP0Wy|Ft%lUi* zheBJ$6s#gfGBqE~hnrrxN5!<7g=oYbir7$gOdJ^wMMJ7ksKM9Z#6<9DAjw!C+xUoe zW$BSF+C;7*>S%o8$o*Ph7wK(C|GHI|MONtr7Ef`;O5K`XlrnPU`jOGz1r2!<(n?Zp z%^u?|oF*A4_O?vN)7LCz6gaV at NXB;3U8%_laiNhC2NI8@-D1Pi>0&Yd%W6bx1yi&Z zs|~qBXUIz)vT$5n44aSAeDE}kI_fQ~e)LISGgA^~qM9{f!{=;6dzFnIY3I0<ELuY} z(RF-M?ug8k(!8`8uJp8|G*?1WWD{lUam!`e5|!$tYP&bL+#M6jV^2<@FUD^5Ov_5i zj-QZF5E&C0IbQNT6Ms at hnae_mc@1ef*<#1p#W}^U)CA7LR9QMp)@oE|k{_HKu5($e zfE{bPT(UODR1yq2p@U&xSm1DsA=9}Iw|av%D-{IWSKABiJd#;y4E?8`bW2P|T3SVZ zTJxmhk=ZemXXRF8##`oF%TioXNjW2uE#Imb866evF3YPM>nfXAv~GD~W`3%z<}2|r zl{J&H=}C%jTB%;6&6mtdt|*SWiLph>{8tv{YSFoI+QMv*48_Zz#woIsnBg(xi+yIN z_1EGv(o>^b;@IHAetv|bY;>*5XSqDPdC|gr at 66e=^O~dui0xR}!ETi<MQ<{$T*Js_ zynN?xdar5GQBsc<C(wMC6|5{jr9Kr+NqK<@IRp@Gn(tDAV-uQfz(NJPkd<+C#HAA< zs<Pt<!fv}L6lSzbXSuU{xicnCnCtarWyEGy7Zi-}M9=ajC1-nFshLs9E>}{CE7`Ik zr_>QS+Bafk&B)wQk&X&?qSu|6=+4=nm64w1ai{;%>2f*CWN*rJ?4ZclL5Z3_Xa^-Z zB}dvpO)mK^?W|0XacGzU5{Wp*qnRyRq%?$-oD>=MXmlDvi!wqV)X87;C~FyG3Cs9B zkINF1ezYYd=&&WY(v$VK%SSq*qBF{mGKC8<g$fU2`(o2>#!Tj64^l^tIJS;t!%YlV zCbOgjOJy0_a^(wElG=BiN)kK7I=)ZZ397cimJlD>SxGwk&Lb*HI-N|Q9bI{ve)y5K zXVGBQsV!sJF?z7#oUnN!wWjf7flswfx2giQsUg<u$w(ipH)ju&lX5AUM&;=qUU^D| z8`QvH^%FeZd*tUwHUkmNWJ%47zmuPQ^KB{)=cr28MknjYS~ss?o-403#aodX8=v5; z&Kj(At?9Xizep)^7E$L`x~X$>5?y at JGqNyP<wm&j%E&Dj`RddwsBg`eV8_<CBaf+X zhqmn!VTWp7VV({5gXW11{;w+B@>8yF(f?T8qEhg at y$va;sY<10rDpLrL!1fm5z-eA z?GG2MCEZfz+L%eCXkoH6%H{jXIIHY&Uok9-RLj<}5>ZS;6-r2=535RPV=$^2u1Q&S z9Ii<%g;JFc)}*{>s#2|~OEU_C6=`CAbz_Cpq?cuu__7lR>r(P2UN56W?Py3WE<A2s zI*J+fqw7*S7_{)Di!rQLrF4=%;gOlqN4;h$)04g@MXBJVwJAFRo>FDne&V{cDBe`1 zQkSBGGRnTxr9(dSn7Xv)sJfJ$`(?Pvc%xy}YS at P|0rJ1BOm)FYDpSpXm{N!EC&}vc zVRdQH8a=}*)L at Y^-9s5f!w_XXiDh_oiwR+fg>*u&L25kt3C?%3$+YPN1uNb|$EkSr z$YUyA>RohPiVq0ZyOWNscT2+cF4fXd3ng3wi<(20B})DORq^V`Q>=K=v{}ESi;Ufr zs0Amkd&M&kuX}kjlHFyno1<%Ck<s`Dkx^>c|4rpvanib%_9AQJQfNmcm>iNX^3H5< zmlQh_W#u#H>y0cKDXk<|YNE&KVGo&xI&y@u8n&~iRjDWoI5(Vu16dhiJ9>kl$jH!{ zEOGYGRD2|@<H*{`a3krczU_(bTh<nit1hb?ox84}uBxnZRPJZDAJYq7dCZ;P&ONVV zWyg7QqvxF0vAW~jxdlfz0j@my2Kj3jokYml=|ei1t(J1SNnvAyXy&xV^XE>V+AzCu z_VIL-fQ}{|Pe;+(79SPIY`;+xlOuz@;5-MzwxXvP%~!4eouUd(pxFORfl>bhl|FVH z9a~j4^M>t4$9a4kysIIxY2x^L)-;ut6y>qix2a8~EmS{6R at dgHBvOgyOo+1B!dquY zFy&~aCu236aj-`&u8I*b+1Nu4!;G`}IKXkr7krzk8X0&DQxRhMq2uX7c6(%FZxq9~ z^zS~uA*EkG+IGM19X`6E55%W^ZcEE at v>oRTwJJlyF`q3yNo!2HGTF at 4tPSaW9&5<b z at _jR=)r~b;FZRvLpC_7^k=f~^MzYAik%bg<ni&wGv|A!VS%770HZHfJ{h@u(23u`W z^tXlze3W?X_!BGcl$&uU)!?5_Hn4IED*W``Q)>&dL;%~}7}BH6dtcj-K6b3nH at 0DH zgRj=d^mh#d-XUEEYvEB7$}LvK2IP!N1WlRI?yx0WRV}C0&@z`((x{zT8XzrQR$(-r z<TYZl!HT?0E at 8M55)zEr$ef(Y4;}7z;w<=_W~F}PBxZXsJc4sXyYo~ueeLHpP9u$- zc7^miQO#0I<m`#X3+K(5IlZZ|eq2SF=vGEKCe##K*wzxoh1D7|PgIJ%%l-l46zsGt zn8q;f0WT5y8tG)#`Gx!VCn@HZiKjLOa0-R}N2l6av8qDK{0&Bz<5i2=-jL}`T)BMd zX|rceozm1edSp=nyN=8J{dnhiS)<`s+7`7+M?043i0E7|X2psEOWin{j##X+=Q26t zu-P4>IXN^s!VxV>9W4Dca6)}H95DMf$Y;Mvc`?*<G<rh)H^(raaOwl6$NJbKM~E$r zN7?G!lR1RXzIoC?<)bs=(@m$~AoS}e8;Xulx%iKp7>7<^ANvY*x_YG{e?(<LL1N;f z1yh^G*U`BhS$X=1(+esJDiZS(^QCW=l00}a*6C!0W70%Uh;Qcb!(~g^%r8SnN3u30 zIu++^&yCVnIp)|FX&38{Bio{Nq~uiW at ZKnS!}r at o%HfbKGObS*C!7*-qRS};UnGgB z;QmbE+F;ng@G;$4eah9sF}c3vxE|@VdroEy$M~#I=bqR@>8MjM1KN?1<g=7L$!FQU zda~+K7dB*1Y3gV{ec6I}96dUtsb@-0<0NvN9h!>^-RYd$+pt<`NA%)F9M9|Le9QG+ z7MndMnUP7#lhu~1q9}k-RIw$o+QuZ3jnXB<+Q)XCShgYA8y#)-?ITL48+Akh{GvY2 z>?U$bQyrF?qe|pMr{w)UTkwVR>a#_s<_&K-wQLLJ+qao#2<O{+^=3n6*Sgl0HU2YB zUv}EU`7@^<m20yHWbVP=p`&8w&av1XgE<yKCn-8dIil<_jtWkDiE_k}ky0fwXJIO* z<K<mU3?GF^-tqPJ@YP^3o`yA_mqwJsKKj2;#LR1inr+En*g{S^gcF(&vH1Qo19|FX z^C-s@=2JD0=TAIl6rO*Hr0w^P<s9ZM!nSdmYHvt6ZNa>`GiMC9328;oYEn4qK~u2i z&exnZY~$Q>8?#Ht%Xhn+0v+5e<A{9!uga`Up3CfYaNtJ2b(Gp)JhgK9(s2fS{$ow+ zpB*z&B;zWwmh2z2RpcwZ7aHOlCKeUs<~S2Lafn7qXgyiIOe~M~$#UO_abv2Y7>kVK z+z-kbK9E61_J0X)5`yE4WD5itNj0AWgct8l`a&eko+X15B*8sTC!dspG|Xi)lOvfK zYJ3KlNVBHNOe4u!5g$Xjp&6IK`J~_uXYs)^WM%I&T4O{rd(1N_?^wmI?U5BrOLtGo zI=yN1%IPJeO4HJd>YF?bqg{<zF&<Z9(d6Yd5ojT^c3pXV+}P;_BlVusBMZi6q!c;} zr+P<tysn!0b(2>Y6wWOgInfuB))DWDOw3I4%|Gpoio{X1qmw>+v|`QZv{6}gO)O}j z4?`XuB9DCJ(oFS1LwZ5}v?=53D$4Rl7mW61XSh<59IOEtJTX|7Oe#-YEd7#9a?Y9y z4O!uc6AX`fu+GP9zbSQ;41Jp=lQin3AjHvdU~!<F<K1vHC5cP72c^nX-EoOZGc-(x z{?ruq1wE3PlTrwsEIdT!h#gaz+GFpeF{5NUA}^RNwW+~;OlCdY=Nty`c6w=DZn`(w z9v5$)t=;jHe0lDXDd`Qlo|@wH?~Y$Ht?>MB)VEA8N*~|;*}PG!zP at O|mSrPGt-f&K z{4L9?baU0rYB!zouSD5mZFyN2jI2zInv{|dn_I7!7R=~qy6}>sS?ih_TBrIxdojGR zzHEQXkpYyml0dKJHOn;S+x*(!zzp*0mCH_>+dO4*{kTyz<)!&~?)2~)6<Q?0x^4AD zS|bx^aE{U1R?IA-U&lnmYD)}lk|CF7*JWe{E67a8V6d&?h-K(gvs93NX^G+hF<LE! zDu!~6x3kDket4y>{}j_<^RWLkp@xoANrUywj^n05&nJSGb0H&mCy^=IEISRgnasEx z>%xL%{K%6bhKjx{<~7D7=S*2g*2wbHVOA8{?r>yHt^P`Cac*jgFZ0&i#U)}BVH?RA z+2nH;<fK}i6TF!vnej75rd6h-_!1|0Tygou#m=u&5>sN6^E`>!lU6r<<D1zHEmIn6 zPs`2CO3%p4&b~J;4kMW>MpLtV(~R-U#(C at s=}w<F#aEFrzA-j#WL{3RbGALk9&uqp z&8YIIwD>sRq?+0 at Ci$+sWKzr2{A8NGIOj6Gf{#rTm}kggznZx^y}>(es#eW2r_P-= zcY=H+FuJ-bKQGgrlEkWoI2~s!ErW7C!>b(;Gp1Xj9i`e9ZKXTIFdSomqg6EDWk*Jb zT1~-KQ_{H%T9G9_FIGDoI2N<4!(se}to@#LLVDP*gHEjYkCTvAG8!}ggoMPNBc$_7 zXZZLut<1q=^5NNQ88puidXn&N>Gb+4Es<s!L1-K7O4)oUIQt%4ZIEgYtuT^xG`@+J zQS5Ey%Co0(LNN<7w3@MW*1{Rf6Y?i~_G!+z1vUOIu)3;wOy>5);^eIS)TqL=L_Nlu zoE;H`I;j-rB*aBVM^;&$T)JSvqBG_fjW0?4S at nW??}k;?izeiG$IiXh8WEF}7G03+ z at Ol}&wkA9IUcptIl#rI5?Q)N>MU2zAS>!j%+5+43TeO+&%5A<Vw=27RQ67z;IpU}* zEp+@NIq1s8H}d-O<+rzXE&ausP50~XJP>`~XY1~h`kzy?EiW)`HCLUX-)nF$U&@K{ zb7swKnml1VJ>v3QZ?ZEEO*2k0dmvdXB`r09nxqU*v}BP*#c=wR)lN$wxLiYOeHNO> zh|f1ofOy8)gP#pVv*eI!#tfG+BP%tG`R3q+^af2VM2?L7Txw!>GmlRV9;|2yV;I~D zeUM8-)-cCtUeR#&WI at 98;}b;rtieVvRH(-xeGEDI!lDv#Q%;$jkPv3 at xMnd-SQ`<t zbZY7mWFiMM0j?qqS4xHXDq$Q#=tLe%N4{w=iz)b|SdM)()4Z-A$^mfUh51-KO&}`U zcnb^4>_wpm<1J%G`$DVt$Md;EE%W8IvM$Fs%$XFs=-Buu&)ia9liNLWWY!FCv6M*u zeO<oAVVX15nN{MBTjrQ&*7ASN;G~~#WAMuwpOPO}QKVmsh)Rg^jV#EBDavuirldKY zHeci1_UV()I&)FksQF8K78f*5nv`!#^^Ne1Io<J|S=PVg!nJd<1ImPdc;Dpd&aC(m zrFO@R>C%=;VV;RO47E&ksEsP7A?iz=%U3&#tgIV9ff~a8z-)C}M6+I>qC%Kgv*R at 6 z%RBKi1)ry%S%dn?URaYpB0nu>+}x`2iRlHSW9@^QTI!f<bhIs~DXTYYM3T;^C?64> zl=10>+k<*5IDy{06K5=|%N~_fwXk9C#<@kltWlNs{N&`?^EzV}Ur<}IuA`(TGkWbX zRf_LGmriuqqs~{Xzdv__vr^XIpGc2Xt-)_Qs9a^4_RvQls7YGmX5+yBp{XV0msDnC zpiDIAh`HEq<zKN+l=ffRVD5w&r;ks}Nl!>BoiQeU%!IKCxf4>-U1L%*>ual%>dTYk z>(?xuU;M=lRCMaL+=Wh;s5PR~m8bE=*zt90whuK$QhFzR2*R=yY3lF|QZ`lCn9<Jh ze7o_V6;|(bZ=8>@bBgDuGM&!3eokjvW0tGdnH8NK>(0>Uo^qK at N?DogOwKLK7*lzM z*L7O!+M<SOu54FqREN!8)uc;v#$`o+aFT*6dU-n{9lf-vUeQZ;yR-ZRdNJDu;)-Mi zx#{U0{}|i<l|LJ^xbz}dX?1<k;ugDQYlOYDJ}XCC{(Bd<X#DD`*wliGaU~J+m${qr z;)<taj;XUXbk}}yv!bjfv*utv`a4avivCtFb=I9ge?xXP(Hi{X({B2|*(|nX2gz`2 zdQ5HD`8$UKrVc>&!Ac-`SS65CpW&+Wrj)0~r4+>{xZ>?+<`!qf+gC)zM07bi*t{+# zIU%($E+M@%E4L;$F)Cs=2DVx1gw4S!fm3sm@?wvw6XIf$lcJ(qqFC)%;TvN~^o=X^ zR4pxEVjth;t{9W)h>7Zmi%T8RJho)(j8KiBZyXFr&5%{$v_(nPK$R}n<oZj_-LWcR z{JSc{`Z9OaeQPGk>Un<iMh3nVutn@P_Z{do_>cFjF9-NsCgKk~?~T}PD1{!%kABqh zb+y2R<b%$KX>l!9(=9*bV!s7TEZ61i1$4KX&Nl+nE#vrg2cEK=$?tP&x^=#qVY!d{ zm#A3FO|+^S3CnLQzavzsrBFp%Zcvjg{VLV^GnHm(qgi~Ga$0g#67Of|r at 7=eQ!SET z(h2 at 8VVkPbcPQWB6{E(ecT}+ at f_qgo@-x*uu4eU_s#6~Zexo1c{&6loG#Fv-H>(6a zTg6)@t7OY3d{M?}ewKXheH8eVPH3w9LQh#z)i}8fU7`IM+BCm`PxSlpci?GB&tfOe zCu*FvQl(maT-n1eyUOMH;AKr33Zv5$^(j1k0>5XGZkI~am#9+xO|FMnuPwh(YLw*+ zewV`^7kcftq at h=*${T)}{Q3l4KS6%H3okE`Cx7PRyPAmoYI?+M!UM`D?~tC;>fnk} ziQ$Xym2CPQm3Yjxm$mi7E^E0;uugyvbY`vMcLUd%^ywxjmwr}xk-tQL%4p5a=pdad zNmW@~JU`6+clbWgM)&*!RiSTHV~}HvH9z>PUQi9_utEPs)##U1iw<x<T6OA$YMTB) z&DZy{`MX?M*v9~0Wi+W?U5?($SuZ|QHF7!i@6p*WR5s6MswWtSdYLxqbIQy8V$$>J zUlCrW#-ry(Lr3iGIG3@#u)W=>^2N@=*D-eW5Ol?E6gDHj#%2P~V<(Z=$Lsvs_<eV{ zortY)jU~^=={=MKV=sX}ncrcyV(cc0{1V#;Ut%BOOY9?L7y1nH?o7(lQFdYePF8u6 z&n9olOG||sOIj6tJ6dPTY(Dv<n1{L~@Fo4bz+vvY^hWf#QKguC%7)Ji_`cwBRjBQJ zlPOm#-%j0*?cR-UzQnV$>CBzYCt5324p*UN4SFkA1(vPKqwm6ABe2&6s?@p>JMJPs zzQiZKk8%Gz-dn?O19qEEUN_)-J`P^cPC;&^oB8|K{POV<X_(9S8SynIxg<R)KgV3+ z*W|h$A0U_bxKq8v4-UH`{xGb3;UC3ETK<Nv@RMArIw$Z3*KdM<2ObIjEzbf^aJ_&Z zeKhPxvB&B91+Evl4$)H<pL+Bqe)UwYTkvsmi4Puri9bHxB|dui)u_^q&;E&OGQZ-# zKj*UURq4Yo<KM-{55MsD;`0Zu1Wp2y>nGTah4T1v;5KZtp0Zk~pTUovsYY3s at YU$E zl$SlKnmV9V{~4e3XKb&NZwLQO`OoF=QfziMqeX>;n^Yz~CZ696uJq51_df50;#sRz zR>TifmgORqZQ0Is1w8HG_fG8j0Dk*UuAg&V&HcZrdTWX*w#FELS4NphvwBoYL_U{% zrTj6!g(}6`&e~f{R;kiM%)l40p>}lA)AaD1fq#K6gBgJr3A;_$6L^nsCLYA9)&|}J zHw9h=HwWGUw+8+V?hCvIJ{7oum2a^U)`o%}6?l|zt5DFp0&g<b<_x?Kvf3WZfLa8p zya#&B)6BrjgzEwyfQ^9<!3BYj!L@;pz)j}<W^-qYp|I85*<<M3XeitaKaAYM18pPt zkARc6 at qEs}8=#Ark)-=3DCtI$?!UmyzzZO2DZy;gVgnmeiBt^+8x2k~xB~i-Y76N` zsw)kJJ%MKk-)QdNEYwK-DH(1JJPT$7{svAEntGzaMFv|9ZZmkjP}2vYW@AsL55YRp zvKbB7R5SDFHnqguX(zr7ZOGG2CjMp<e=D5Z)E=a^E2}|g;0eOn<fEPQGhYQuiEmfs zaA;RmfmgsfbEhHj65&QPZ|4N-x4~v=Dm$8g6I>FY%&QfFyFtGRw;1|s&AaPN9O1-{ zJ$wjmF?WO?yV_1IV^=#(cn>n#)q|{Gx2uQwlEtnbWg9)adQ8G->@Q$~&_QDZ;4a}% z at 8+#2@=MT->`~;@`=I1V6!N|Vjcmfv$oLUhZcwxrP2PP7HWEMDSWz^x$Q{3l(_-R? z#-d5<V`icy-(#WkAt-cWq3{9N2v at OCkULAv9lwdwV(#q0u9#Xt(pczx#29iCDaJwP zW3Uc=#u+P$gU&mI7eFlzIug@w;<Q68jy#o^>r8kPwiJgx-vqakcX3E6Z#~S{vGL^Z zzrZ9cIUaldEtt-`@n{LpgO(10nStMcb)*=tgnqm!x$)S{BZN0&nek9Q0A2;>@#N7% z;GV$GzyT~Fo)Rq-_6ur9;6X6fgyRgl4SEb-Z}5h|uZeTN2_M8-obdTFm=rh+x+n!s z*~=5m2>cUtW0_9){3|GV=S1fpgLNi;BYJikZ9A3ZloKi51=pf&Ct8v_n at o5!vOAIG zHSkgs=UZsTN!gUNb{aZ+0yh#Kz*?Nh^(eR>d7Vh~AQ&fius!H&`VXKd at DDJHd~}+U z>og_Ti68rrJ7O(PeZ8S at 176Xo514oFlN8aw<KV%7<dyiGB;)Inkoqmc;&YPlIj?}3 zXn?Ps%^k75B;#|EjI|_TEpkVEP7<Z>5V(cZlgOjz!5ht;o6#`m1=8kE!rMFtW=Oh* z=On{(5<EjkZ-Y)UIr0kVgu`U`kvksRgUN964wyyEWJ<xiU=jLECP)4aHo$o@{2T_C zAW<^>{2G)}kWB6f?lhFe4=2N$&=gOYOnFaJYylSF+etWl0ZfoM at Od4$%iKAD22zL* z66fD472e(f-Q-;=yuAiWX-YMkNj2p$)s&!AwDCGANS%^qBuc}6-X$!4JI%yQqr}Nm zDQjuikAyd2Gij894?xjs8uoJ-+(TJQgA*vg^C2(;u3V<Xxk%-2gp)|og_eE=7GS+D zcpCsqu_YH8y9KNY+z!@ZZ!Xpe{1Y5+aDu^xz|Gug4EzL~j=V0`l-t2(_Gxgz?a#p_ zCe9gwUlLw{Z*;-`ey}BQ8`zExUC8nTxGr!fxGC^EaC6{K;1(0!YB=0Zd2~_6p9Oas zI=361e;>GqI}h at 8vJ0=`0UyCLy70*v;A7^_^CqpA$UB#MCGdAzGZxCVOUIF~F7gqs zbRyoyg_cN#+(ajMY&1rR5WEjfy7XW1IWGNx$gclp at IiwQ8GP8_Bh;bkX!tOgO&+D2 zvXG8$C0uT>fm};RZ*PH9$W7Yj2Im=EKyIcRkCBeXgpMB`($UyQp!n5v>LUs7!LOzp zdrL>6=V*;(1Rev$duNc>FM>||K!(Zd3}%boCY+6z$bg at Rz*1}=1Fj at oWn$LBVFp(B z2-t|7WRSCOfX!HK26_4dxWqhNftSvJw}-(N{7eS9Db&`&e+GQM1a5-c402OY>@x%V z6x@#N8F;zF;ANDN40X8)Utz*m8P2aZ++Jhu><m0foINJagLsV$@)~}S`uAW0R*^vt zz6oYfk7VHSkQDC$Cwij6R)f1lB63!q9>C{hkhAjEU(uf1_<1)Pd!8`69fE1R#s0bE zET;gYAGe$y1(us|6*9Wf)stW&cI7s$4mY|=BfNm}?nYNnfGYwIfPVa&8(qBswi`O@ z(3l%-ya!%o!aI@OjihqtLG06wq$#w!ErEN%1bB8M>ARrRl5Ql0LvkF-$n`#WK<F5Y zbtBiSlmIK#JV^8kSVV4m;Qwt<^4eoOj0gT-C)}(OL1`g);9TO1xADO9hhV#*CN-Cb z{1pnD;MPO_O3ba~f(I!MgW^R!@Sh4kta8CeXmfbr{~b_r#{<vM)YsGE^uX<5`W04k zArt){0F%&pCi*`FW>a!A;r!QNskvW{Kgq;m?+5GPDibMg0~_%zndHLrU^DHEOk_C- zE-_D6P_8qPNU#NOlZiynfNSAA6Nw%JHyIu_8wy*HE)%(glkMbkCKmfLDD`qCcKbSb zx#92%6TS-T%2Ze52{Y9-=FU#+Gt-pQOlobBQLHx;on(Mdq32BX66G<I{Dpt?b1#@E zDU!cG2D7lwOf30*aH7FhgS$-p-Pl+rIgS+Q5bcrU$g3YDoP`eG2c^czLQC&~(uT}3 z_L+rdJ|HYD$t?8n5hyL;Ec77ZP4J&Z&K?G(O_F6=AX&)r7GaShoA$u3!F1whQ)m4H zbfdRyYP#FOP2_bp6g~l^R?3FLd!Y0(vZ-6&1qV!=eOOdBEt%ICUy3E&Y+5pJg3`vw zrVaBpcs*LprcDA(+9ZExeS?d9%rU9wnACGj>N%wTd+u+7eh%rr14`Q|hjjl5-U$CW zq$^J)-5ir{4)rxLN&OHQCvi;bIixQ4rN5H{5Asxa$bpA{Fqs^UZVOecK_ at f|u~@m| z!VU`!Z-wyo7-7*&q3NR)8V(C-rM$-- at sNf1#<xJ}0~W&Pqu`BLbs@g}6>y)SydOOj zVkiFsBZL~<9tPut4%YHAI8kE4C)D8ZB^G4}Cq?k^Z_o{eB2s@4++)Jh(<~wt?nC)7 z7$J9zr4>QvUBU;sL+wf(Q3{<mKsWbGq4Nym1>3k&j-`DJrXx!^aU`6H#>$cI6L70R z$%S&#B|h{&U_S`aUX9US4W<4g!jh*o(2@HYwC-w1>qXFGo at U|)YK+EeOl?vF<qx^D z2ia at T*c;%@$X<iSg#LasQ-fUGL9W-qSaT;ss3F}uplGQM&+{56;|6uK5lTSnNzje0 z*WtZN!AzA0X3<-#Q_{+<qh!4YmP4lw|MMXzt^GP$L51KfG+RemYXX;;m@CN5I;>Gr zypa^@6eGFdK1x#^K4B*Kh)L^F?6Qv5ORlPe{};gs;YY{PwyC4t!dv)?e}mEosnfem z_<DG((+7FC9*I5%=}m(41;I4@VLj4yg6WJf*2BR{klG2vql1~uIo2cNOfZkP>s3DE zEA{M|-~-1oa#63QG0I$zjIH1-btc%Xz7C#2FRNaiM-5W1wvlGN`Yu<!`k_hdM&i~Z z?_6*Idi6-X5Zup-#(MQzB&}!6vl9Hhp>rpr0QKrG)DHFPUe^8AtNQ|<g7+Kx537p^ zKVo=(RQU-%rdq(~Ow8wztX{nYzxC>{xg$NOdUOc4=n(19;R4VtscVmLs3!^!)JGEM z26CaEG4WC0LA6SaSN~LfoZa$IG(XYcB#^ojIkUh<EWE+^nFf3#wGr)42lyDz8%^nG zG at hXm3xA9|nUu9g(@$x{mOdsdJ;z3DNl@DSjnI^^^sgJKt>jL-!JXJ at Bf0+;D1NDt z-2XsLA^aYBBV}hACE{H*9UDsrZ$@L&k=;$6?ZXCUpaBOcYs6+CZ$2n><V-b(r_I=| z;1qJ98A>vSGS7s+3!P?i(*fRS?%YiKq#2qOsu}t|P_)quWiRNKIMA;J4`R)8k^PUL zl)kxW;NPG~Jr}7z21V+*Nc}D-wbEQ_>QBHu5)&CehL1gzwR!a7?_<_N<Xr>S4(W z3yjBDfX8@_a3*Ojz*l?>ZY7lky!$WkMkp-6KghfLNoxT<fjh>FE-;>R0Y2e*wFK|< zAsB1WN&F?`;76c~p3xGM;u2DPl(3sN-V%J&OJF7*Y>DyhOQ>@tzKn4#ffJ#yg}hq= zKN8+<?n^It30%DbN-uZ`yuA(X$3oaiQSKOTzXU$zP8{4Wq13+$ddT-B=z;ifE>G__ zcMj5?S%KBP3A$)at$^FN!HmFNpd0_c!tl8Q4&No*POD%Ad>#fj(UM$YT68Pm^9jP2 z!tDy$Ja2$9PPhUNzYgvXY*Q=X>Kk|w7rn5R(0>JVLuV!Q{{hayYpq1{@2Qn=D0mRN zU4?gg9h7==6<+sQ(1mYb1%>xP4?0-|g%`mrBweMl$%R#>C9?`^`H1imY-1Je>L<XR z at Vtst<XsuPTxB%53hn(}twMYM1f{&JLT`_PVmYg@sCU$A;=iX>6X$)ri16cwliz`2 zQGWP&26Q8rAASykGG6CLOP_#JulY&$4Nz)QKk0&`dl-}&$8XZ|o80ju`zxy5)O790 z@*!c7rQKM5JCxre+)VjuH}>C-T<;T>k%4x+icPgc`CU- at cWdGEX;4NK*TUPsKxvz; zGu~z$Pya|b9SZA+^CT!W={h6DI%2*__(u3#2hZ=Sb#VI*C at su&a0?x{eOYZHr(Ok} zw0t%}^G~4E at 0-Y@7r;y;+C(0`4)Sd|SOm>Y$n_ at JV8SxqvI&Xa23KGen<&@sgDr5r z2^n7l#r`+J$&27qXl4_VJ_JV4&)P)Zy#Y#lU=y-K8Q$Jgo8j#(Q2N@N;qA|$2W~gR zm7w%8HpA5?ptONE!_|kN_?gYdyKaUn39mrX%|_ds;p8>K((c*}4^M(J9<`a&p8=)a zvf1c=3wr)QZ6V#;K}m57X~~@;w6Vo#U<+w|L|A(ITS)6QP^8{MiTe=TiJfmjx|hLS zXn3p9_Ew|qtx&$3JECpo`S2}UN#);Q7TVZKTJM5IP~J*fa!0him9zwx7+itIwi<13 zCEfQ4?=-1M8QF at ALQ}N86-gfjBe1Bg@bfw-+TIFRZ>a5X at +(kE#CG_36_hf*9ey4H zrS98Kih?_NcRSYgj at k~LH$kze?O2rDk(T7eruMuP8Q)fyLQPQQx|Ccv42u3Qg|}DL zrEv8u7y+M`!p~FcGUC4rO6_?WX}zZ|qb?w<uOoc9p>{b>4-uB~csX=H=)49-Aj{>@ zc}-nG%)_9hdxeR4g^78EiFt*Id4-92C44 at ouB6pu16NRYUr7)5P4GK#ekDBoOI^uZ ze+3;_ at 0B_Y`d9Mq- at qRs^_B4UhPnzm?}0LQdKKDx7nIbmB6Y$h-K$8KIK2C!y4uKc zHE~`eEPa-%4To0~^N_mQaCkK_Us2aU`CU*(8m}?&uOa>$gl{G<t|9)5U<7YnL;UB| zw~6^Km`PgS=IQI8%p!dU-+owqkGy^alslXpM4ayt^LcO%ZMN^xo&jm|zpi$Y?)#vN z(X*Yze;Ra?Q#-NMS3s$McVesWfie=llltXta1)ewQV%{4ZZ+ZU$hedG<q&w8iE|}+ zy^~tzF11s~QrGW<!$Y(@=Fo=uAv`cXg8d%?3yHsnQt(f(oH}R^X}t<IFfy<Q%X|); zV&Y6gKYP?X6PA&gJy@)~<tGRCz}w5<Iul1mWA>Uh^IppMA;M|qPP)Nt@?tM3{u?ZZ z&R!$kUfO!mglEw^*bARYpv*e$C6!k}zX{8H(_YHH;97$+{<9aIyaH}EX>B!lsd at S> zxZTSLgV4Fcgs(DBuO@%@GQw~e+-vSXO#5~(nju|nMT)&<jAgH$Kwj^q3<@r%HMm#S zF@Ul&&|XH=<^C>sxDlIq8 at w5sH)1Vss+-V2C76U}ZldhJ1B&d-jl;uDM)sSK{cXb1 z3cd+RUjh9lW{Zg<<3%?i>Ayjd^d=;I5tQ+wo5+2ke5r}^Ez-RSN#6iP(wit{FM%TI zO-L&I>@{&7rhdH1$aoVn@)Wu9!82)j-h^CfV86uI8x8(I_|exI6iIJ^*DO$GofzS! zoZf<T63(DqbPG28IG9Ooato5a4bDMtwD`!8TkxXqfii=03vvmyb>_|{YKL2p>t#^n zx`n*pElSE`;1BTOx4?toF4EeEpMM#2GTKAm58n0}-R>jBmk5_bZ67xL0@!HoG~<Ex zVNr*{C0Oh}e9?KJAFlSnmE0Gu_QB5w;7-P|_t9oZ17(hKA0_=}FoANqkJ5AlxJ!6o z><E4sHF}Jmm=g^!T7X>p;o%jq0s8yl;SEq`O!kxRHqcL6`=Or*K8$bN56!*cE_nMn z8cqY}@cVQ05UXx8cWyIxZZmiOKzM=r6Fmi+x)a;`l(hzPC_#5K_wE1-Y3tujpCK8X zXzolhI7Q9p&NSlQO$-Tt!`$Ct at M43P82ql9&C?&!XS*A_ZUJwmU3fQr&wOy7S`Y50 zhj_R8kQDBw|I!Y|(jvSYu9kpq(z%<yVGXz&j_%gitFyox=q=t&->@D$sJhiX(1}v_ z at K!O%&eY)bw0rO2X_C5^TJMm$4;kN855ad8`nnNn55rG3xDRjkunO?j!|+fH#`4y~ zP>Tnzr&skbbPDO~?jw~)xSs-EPn<`%pGrS-fS8ZM17Y5c2IE-6_b8ksgV$p@kHT#- zdLJOaB%hfVA!aNX$6lY05i=FMo?i1~NM{4@=g#BgMFLo;7K3%z^W*e`ZQulh6X_>D zt|l3rqD~_`jUN5u<coyAVeaoRc(K7t41Skfdz^e}0&ipm{^RJO4IH4y{WvoAg8QNR zICfD6K5f!{0ofl{uNwLv%Dd?5EHGByMT#>(H#z<|vebgRRXKRQ+5p}_AOCSAY6K6` zi+@}{ApGmU8I*SQ<E#i26ia?wKSEwSMG4)*2*Qoz(Mxa`uU;ZWw|d3s|5fhW;O1s( zz*o6%RfovgboCm#a)MLHo!8Jt0yxivzYB%ekU9#yQRRa-Guj|!w+eifTzd^V#b7Ks z{u-%dfNm`LHKeEj4-)eY<7wX@tqJNqkYiA!%!15KgRGGNivmxB%us+0Cfpc!hHx{h z5H0wS*Ffq%(9ilP3*O^jV0++ga2>0uEO_a6!L0^a`9PRHAb6E|>p at v1fXzBJOLGHb zz>EO?g|h#w=ExaF46X3<97v4`W<%2oS1*I*fmgx0z*}GgD@CkGkqj<izd9>iy$Jfv z(-srI-P~CVhgNu#cj=#kn+^S~f#(Td8h8n$e+ph6cpba~ZCK&<8Sv`BA at CaWbZ_8I z!ZNRHr5unh^$46$kMI`tNFjK-Vj~Y~lN at k`xzjHc^hSd}5DIv5LB`z3GsY9Zn+2&i z-_j1+825qS;;Do7%$wkqtVN5(q9k{nfd@c*ELe_=vNq!oh>r#7{egH|a7EzjAf6Uv z-V at vzxR>KyEND9xPyQm95!eIb$-x8UU at V^e1+2%(O1L=6c@#*$45VKM(o+DLe*_t6 z0h?JbD62T+jz91LNDl>kP?dv^GIJb<uF^TwEhBI<xQ(x+;z;q3PN4RD0Hm!5(pCf+ z6V?gTdWUo(JiH6K;5m^v&w_6FWPZuq&kP(OO#1;`Ywm19dx^9RB at Qh^aJ#{MLQTga zZz4Rr0B#FBsgu#kVVw=W$l5<A`ji=gCqc%jLE6}$C-65gGw?W=1)UtU at iDjpIyvNv z++n00Wb6fGL`buCU*eOS&wz{$ff<3nfUJE28FK?!jR0<gf0-9}3#50W%dqEpdK7v6 z550&|@DWH)4J?ArBJ?jP`d@_p-vk$!r=tHw=wF^Pe+n|E4>D2&ZV$Y#7s1tGa6;fi zaH2uhUlC?42e=D5OGrHzJ7#Pdyjd0K<=AEscFag1cr)t~R&dA1E{Zp at UUwyT%GuF4 zLRGR()P*+In4DUJ_MRoo2n@I{@Q_}E9_|9;On95zM{43A^|S1WzKy5bkoN;H8>`rc zyazz$m%(zhw~f4h2W$u&1RKrM1^B3K*q+4n2Ob6K7lX_&g6q)#Hp=_6;AYDBHf%%g zY&E!D-o-X;U;^c98?$DIz+IHSZOnyzqIVe!+=Yev2s2^>vf2$~+y<nr3(AU_U08UE z-i3x=1{t>jnNOh>i6;JT#)aMoomkFplc&4U)zgG&6@$!tfo^=`Ze!cK(c43W#mntR z(vLu9R6%+`AZ=i9vw6xIGs5D>c9YAWfb{i1;cd6fj)9B^g8L;5hdecYX1B4--Q>u- z+ at U`XGFJsoG;tVhBYcB-%8V7^gW!*_`8V`+c&GQk%XoSncV427WY$~XfYf*BeOi&? z0cx^z(1lzFkns>GsT`o^kOxXhKR`=58f0t}+`<a+1IQxbeR#hEaQGsafo>1zewmrp z8x3wl>I3=*%;X)=*Mh7)AqW3TDzAWU`2Q>P9|QLh^RLi31Tu$3Ey&n4n1ofZULzoF ztaLa($h&_8-PjUi*@0hyQtl37hf;Tn-(w8ggr(j&NJ*&zrQSJ+1-=T3r#*=GlbG%3 z<RJO=0=Uk+wF&z?NUl8v(nkX?HR*l}J2@z`z~B|;-7De$Abv?GTy36SV>tgdc6AVm zegQrvsUz1ukP%LB0y;lP-U%);*lLhI3+)x=8fc^ZlXkVDZ5RCS4xO*h*Y~oY;{(=M z>)F<uBFZDWB3_L=J#ts%o3?J-{q}bI15qha9Z^?Az3Q0jxXST#^hMDx#q5f?HTL$n z>*7}?yqb80GtYTv(z!`@COwlpKlwY!zfFELB|T+B%EPIXQxB(grM=`@?7B96f5t at _ zceroQoRO83^<4G`Ip=$S;k_q!TJAG>bMvnC<@pBkRsMYi*K%6Z?!vzpT~Zuh{QZ)N zrRSF3QI=kIU3o<LT@~(%k1D$>uNX0Y#1$jnsajTbPjz|q4b|_|w2v$wl{xB$(Z!>G zG3Jum(%Rc<ZyTFG_T_QA>*m+Dj{n|-H51ju4;pG37B^hl at Id3@#%m_4$>oz*Pkyk; z)^x{|_$h0q+%>g$>aJ<o(=M2Hc>2oeH%)(Y#;h6l&a9hx;mpIcT4y~xyKeTj*?*Y* zNpnqeNAtDK_s_A-Svcp-xzTf*=UzMa$$2&Nmd(3h-mZCf&HMX&HNSX%$NZlxNL+CK zf*Tgxx!}2lYGKX7)`j0$`1B(8qVbD+mn=K2 at U+8AzrXCx<@--xcZT<j!)Knm!nWd) zl`B_1z3R+WkFRcB<8E2ldVAZM?Gfz*Yp1NesUx=I!p_3Zj?O#RdDm at Pckfx<XMNIj z-FnOV8@gk=H*`PKb6U>>8)j^{>q}F<bWiV@ee?U9H*PxnqI10GCY}4>dFO6&Z at O=D z@#fyme?R~1FQ<HY#|7$wYqunAso64XOV^fHx31f|YwJs2ne~+izw*&nQ@&dH)rPOG z`Rcu2i~8EMul;OW?6$qztG3^IVfxpjzJAF?H5YyGjWIiHJGyrK?Bddkr(E29@z#s4 zxOnfy4_<uulFUoSU$X3y{!5<xX6ZN2{pO>W&bsv1->Ur9WtXL2cH89}u9$np{a3EO z^7X5>UiHD%{a3$!O~*A)etZ76|Nfnc-?{F)b>DsZd#8Qxft|B<KK}h{e{klth1Wjw z!+UpK_<ujyUB3IaA1(XQz;%V!?Y{2N^<%HU<_60Rr{D1KkE?#X?I-0wx$GxT|8&|< zcmGWN?0b7o-}Cpqy*EbP*nQ*6H%-3j`#0Ne-gfgR104hJ-E#V^iMRfJ-(~w3?*HKD z1HY*H#cjWw`^!6Sn|9m1zbgOLcYdA!>m9#-=Qk^V^Ym{g{`T76#sBV-+Y at i!`uoJ+ zU-A2o{_xd5Jom at RfBf|w(RZAA$K!u${nG<?R^55oozMMw+Mi#)>nC?t-M#CcpZsOn zUv~fH-h0dMz4bu;fn^7Nd*Gw{#@=`KeK*|q*<V}#`t<#)@4xf at PY$*oy!U~q2PQx8 zi@#<5ZQFyk2iHA#+e3K|UG#9$!*@P%!J~PPKKR(Vf1mu1F^?xb;eF!%CwD#d)u;FW zbMHUze|Fllhn~CU`Ml at vcwtd+b-I33eVIOm{If7KRz@#Msq^hK%^gKcbCaxLH~;p6 z!7%3&GNKa<TX_2yp|BMSzYB#Ui1Sn^9LZ?Szd~WwUhC9Q*v@x9>?m!hNAZ2g+ECa* zt$21Q9Bo;qzZ(k2 at a@uDp)jr02-#mj{>7=wB9cR4*5*e1C=^a&L&D$D1LDLlSYH+) zJ$MOgm14^Yg)J)HCYyT5ecJuD#!xtdI9~~cBh{I<t3zQMn_tW7e2H&YTkWZ#aFoin zhvPWZG&|!cCQfw3fc?WzI7a2g{xlShC43+hj?>;)S=lc%<JGA6xTc;B=k<23?dbOw zx0HBms;X<sz4QHRx_Z1b*KhE5b@_Wcy~XF8b53Pv_|M9gp7rMs{?)K{y}z?d?v<2# z7xuJ5tH(QSV_Wwoe^+O#KlpBM````pc1K%RS5L!HcT2qIboO_67q<1a^`70<>Ydip z-S3_2U*G0!@^|<5^!h79zw^4=7PhV32y2GXK|brk5s6S|L>YSFw6@;9&Yo^>b!Amm zb-lO0zumvFzo(;<B#O_jsvKM5t(eX$Ro;qT at 93(b_bNguh3<Ge`@DW{f3Lr_ZN0zu zEN at TyG0O6Hw;o3iN2&CvKaZ!maEir6i#og4w)GZyJG;G$Hu$?c`#MU3e6;s=wsp65 zo#*XqTi@wz@9ACd?<x=e(bCi1+Sc9I)*AY|&)?n0litqu%D9>R-VT4Cx0jb7*3#D9 z($*)EbO`HRZT<a3>t0*#UEg^wzurE?H3lM&*Yuo2*7Wtat?w)M%8hP+|HfW_m$zjj znbubsC)Ce@`kJ2JZZE(7Zg1PU8=$zp4P7HbYp?$tA=}>Dv)<d^(dO;h*x%RL+UD)= zF@JV;o4<;~@gz#$27e2<vA4HpV|S~(Eq8;g_IcYod;9v!p$%L9R*_`*yWv>8vA(aZ z3ntNCH+guTq0{5_Z`gneg&<rQj$6I!+xk0tT8&obbhdQ(+q%3n{0MlSk$LLbZN2^8 z$z45AT#q^Qc-Noj?K!90ySD4R4IO=6l#kLjw)A6hXr!?VVjI_Xgw502*<U1PBzaOE zlmxBzw)p#oWa{vrjlhy$-VMDy8+v+$7V4cTucFTOww8X$ac>7AcXf7?gT*td%e^xw zzB5J|3Hv%XahFnp at sJ7`QB$?y+{$Ch at -a6ScVOkF^eyUX??1=ii)J9yiKSprUUI+? zZ1XOf+3cOSp{+X at xj7i2+&fg(sw=&QSm<42wP)il@Got_((d<8Yh2*<_t$wl`ujK3 zjTq6_(%ZSAzpt_nr_$5AcEr4C%_n{`R8AQT>tUGdJO<7>nF8)m{ZtUe40x9CTcfI2 zie0113D0MGdX4HLwwI+I>v_k|UtOTQDes(P{;TA#qn}nnuZO$mALm&Esji1gC$z%x zN=%9id8akRm!vw4?KavNFl7RmxSc%nACYb^L$Jrj2-7?t+1d=pJq#Uveq53L98&1# z9j{u*dwpEJ>TIypaMA-GqA3=cz<nE`CPH$rhj at M@Ir8s$JZm$Q*1}^@YeO16R-bjp zPz+L#`{7(UUJ9ofKKtNZIP{WxmE@u1Vm*KL^ItpMZ8Us#5GyD}F_KjgbF9hr3KsnY zQ>o&&0(ra)fmR(?qKczrI+7<ZDfIES<Ws-VODp_IZuc_$>V?O4a7Yiww=B`CX#G^| z;qz&I{!_8?Q%U1!8H-sSx5(&nt<ioFbVRdW?ruQVZli?`quU{Uw4;Mg;)`ruls~Vr zvGv^Zq7|_s(NmWx31WT0CsK~&4S7%QwjTAo&!i_h4_a&|v?^(Q%|!oRtl4k!qc=n^ z$XSb_CMfR(^F(3>wcf>ju^X{}k!r0;QMfrb^tYrH%-tam5`KFP^v>a3$v=rDa`YJr z;TYYd)Q?3<sp%qCiwR3f>oc5(c|Rw_yHM<f53#DCZArKdZZ?3zqm;*>T?cbPe2Cwa zXW?G7+Y62Lgrs~++3hhULQ0KjR_=?x2|pDoL(htdb0ocRygrlLqDPTcB$9MC@^<T> zrowTDYgMFZCq^%C^&8y;Qxt9ajpl-Ra-wvPrWH&>^ep}^s7di?L2XD*oi~ghk<<_8 z8$wnX<}j#>AstH&ie}}x_~jv6nFF;JV_TA*mt}%dz9n6;>To_!HFtV>->W7QuZQQt zqgZ)Z<Lk{EVlk4ZYYj&mpe$(xEk9^!8~LlBRHZD4bu$=E+#qKgc~89d(WO+pd6A(c z>4nRQl=-m4!jsr)FVsbD$;YF0E9F8Y6AD3V6A!k5ccc~-%M9}sv|cH9!Bm6R+0I+S zd$^2CnGEK#)M!#ih^)ovu-aI{j8N at 6gGIALc_`MnDHJzYmjpdeP!`d6(0^~BR8%69 z{P%hF^2uW?hR>kynr5^v5-cLrjx16##iNA1y+{)DDWW5>n_w9beGO at Q5k9GzzvrPh zDX&t$OMVLF<|8Ob9-T%UDIde at HL2UYOfH5wJ2KrcH<Dk$)<v-WB76?j?P3elu$~3{ ziXW2tRoX!Pa8;*9@LwOYh*#ObJ;_tyL0T%3*K2t`kN2DZqj%`dn}2-gRuc>ziS+w9 z%86K3R?QS)6eY}YmNDXPrBbdWX{pxpd3Pl1j7Kx7S4#yx4z}wVA(+65-UwLzCd}?) zmEL+a6-GbPeA~^u;&j$XJ*j1;){d!dP<=W|JJgrd9v!V_vj1Bwb2OaYt!6P>;-o0e z)=3CSRx at w1QKv9EFo&j(%%CsOY1)OEEu`q4uVtmfCPph3G5Wih^`sf<H0{<N%{MsA zZ7$VW>L2Wzc7gf|GcQ}1^YH3iou_?_L>A~mU8IY3iCV5s=j^F6UCylRnaqs-T36^w zR*H_$Rk~W&=#hGq9<9ePBb~x5vxhNFH#1f8Jxn^87omQ{EcLC-j8100{$+I`qn>_t z9C=FDs=upe^;p(Bk5e&>15czGn5TBL_WYNOj at -$t at y}UB at U(hH*XerIt;egg^aL#@ z$24fAdUT_ntef-{J(b-}rs?VGLp?*!)U)(#-K^*6x$10AL!7S{=!MLtELP9!CHgeI zl(SElGbgxNpP|oW?qsE2rC!vlwV#=f7Un(Lbh}=wJ9MYos at LhWbQfbV-`4AOx9-s! z)a&ZE`b*6Ju2idauewfsRo$Wc7zyWS09 at Whtatyt`l<REqvCefYi(288QHl^{Xku- z1{j<98neFV=yUaXdJ|_}pRd2HFVI``R{a(IRc3Gg!peo4XwbLluQA`TU0+B+{gi$6 zE>Z#g4ZTBO%v|*)`kSnsZ&L^KrTSa?GFBv9uCLHn>Z{ZT`f7cR{<i*({;vL>-l at N@ zf51rmLDnh!Ro$<D$o$9uF=Ox}eVx9ZS%Dv`=h!=@UH?@7Oz+Wq^^L4#ctHJ)*`u}k zCVjIWU|roU`c`HR_UoVPU+7=z+w`yWuhk3sH~P2wclvhyd;JIfN9I}{W*tL^zC-^> z->Lts at 6vbcd-PxQy^7fa^&5R3qx|<X{`~-R?*C`z=U-JfsBbV{^M<;F-C?d&Z?cvo zhdHi?)LX3Ve_KDSAJLEM$MoOzKlJ1J3H_vgN<Xdtsh`o$>gV+H%(vgA-qA0pclC?< zCH0>Emws8l!mRqm%BOayN%~d&Z+%F=reD`@M9f{%+-#XS%jU1_>#M4cZfWi5_qWjX z>yIL|rmd^z90_!__4TcbZX0~)kf3>LYwPdqYHhQ%SGM at O`mOD&+9SDEbrO=R%eGbs z*F>)!dbcC|uA?&?vUIMqbp{D_`&%~lw?%gk#qSBnvh|v0wm$O{+1Ju>j^EK2e(LBS z3~daD>>Il~tEw8S%x`s5ZRmGw=yzP`w=wiPIrQ5U{GC=E`mG85j<jtIGI@^Sz;RA% zXIpPuUuU1~oVC4l;ce%IeuA+!1x48eH$j~`BxuxzYSwUdaINDyi>u$Z-rv&O)9q;S zgOzAA+P|TPk%gWO9c?j;xUKgGso4B$dfU!!v-^Yb?f#y%J>6|*MKgBSz1AdY_qUnf zn3k5#-j<E)+q>G%b+m at lwzZlR`04HO_uJZn)EMEC)NO6%p}j3gG-mA(h1j*jn26~Z zdJxgE#@}o248CFO4ASci(u-L)6fgFyVFYYlhDBSKNiVi**rSLpShaPV2Ql44N{H<q z7AdZ$V<Y1BZd~8R*q*H?m}*Z*Cp{r%7-m7J*xq3;I(oy3i0K>R&E#-Rb;u${PqXz0 z^&kdRQynslF(V`T5jAe(5lL?}Nk?uJ_1iaw7}yxn-#J4`#GE%2Hpz{g+!TA(+TOOd z?k>jGI$Nw$yVqLVgh+R1O-=P^6RE0kO1uoH^|VT<#N1j#kH<w!-Pqd`xkySu93lmc zgJQTLHf)yihr_7Zgp6 at YC~-Ld=8-X26Eea`C`#m!Ky*+f3C0geU?LkGNyy$9l-J%E zjBIN(bfO!FY_f4`&?cLPXhb&+^6i)s&K27fLzN#fPsh~o{b&h=O2E`$0h=1k^r@jV zrv|fZYLJ!K>BBNTX8N#Hrw<Y`8m?{{75WWoyt-+OV@5dDm>EMaM$91d9W%p^>@!0~ zHZv%zJP7eO)9`1X8DuPG))0-@*~7SsZ65ZAtvOgAnvE0@%}|S-JN)_F;PbiWdGy at D z)Ex5$Ihq&Bg?S;3%rl(D%^RNIG4uMm{CypUm}6o1)!2o at lUf)|YN1KWu_zqLsHvtp z<UU7FwJkF7Vipa_=U9AH<dAP2Q)62k6l}35amiq;B_Sm)394*Ki1Q_e^T;J)KXFTj z^Y2(1HgDU~Ae&2#VaF^T;=;B(=!lmaqA|;do;XeqryMPzP=1{rl%r=zGchtP!4Rj6 z`^Ro->+R{=Xi8ykY<thf-l0D_8DS1RiRtS+Hyn=Z!$Wru{?#TU>fyg)yJY}3OipNb zcDG9j?~TEa4~F~C8N46Y-+`A1KQSB({Q(EzKZJvDILN`^UxtJ5UxtJ5A5kRQ-_hCI z>L6gEbvQ`d-0F)pDF^TN#k7-FC>(194E<rM{4RrQ40ae?XYee8{mhN&K!8^K@PA*W zE&f&7eDl>p%Wv7K(_&G9ci0K`o}n;X^LbkbysO)N18V-Vw&fKAnj7AMKg=J9C_H^2 zV(J;oa(%g;9m~7}^XD(i9cWnY at eYiY(Aee6y#vux{jCGV@@Mo^??9CVswBoA<}dTM zdw1;c6J`Fg)!gx#ND>+&p)spHtJx>oGoZ?rFAt?n;+FCOyKnM<jZ4YZG+=95ux!BI zRCX&8Hm#mKkk*z-JZm}6yd-Iv+A^T0u30^$Vjv=XPnG#@u|*WD_D<d5^NTPhZRHXE z2D~0*2&Xt;E%5oL@NQ)JEs>E^2ehBCt$aYQ_Id{#O|wKN1bma14 at Apf3;8RWzbXdo zlGuQyU~-7BTO5RMi3SFAn$HVi(jrDg!Q>q~yznt#E6w$hKsb!#=6z_GXUj=%I;l)w zJ+O7nkX*MYmF$}`pqmEN#9P^57!_0uM3vv7BBw51ro5}6xQ~6&)#M$kZ?Rj-JbCcu zC?AL{``j4O<-8fQ+S>$2tA!2zU$D&Q9nYV>fuy!fPi`)RV#)`i%LbxLD+XfAz252Q zB&=?qf2_~DW683kZ+hglxG;ODVj#A3K&Mt2zT$`Rb=0GTa_?x;t{6xxR|D2yo?;r| z^dzX#_8Z%tGSN5omIR#&SI%<pcsK~hT8&EmV=D%d$}3&tD+ZE}^Att45F at 4h7K=(N z at K$=K8#}WU%-peKx^KGAzh*%DCf}N@+54CTQpx2s@{s?{(*a%9wxiPL^^V^Gm9(L! z-b(Y_I}nN0>N4-ZYB8LK1<UqXy%Ao|K5Jowd->$a7)DeR7GNU#rmY@`Y$CT;dyy|# zSgcK}TYUo&P5xHQ$J*rg5MI3;Ya*JTbSad+Y5uVuA9SWkzC|?|T962mA_dH!KTt;5 zKqR&mDev+gMUgTAbt^~_^!Rd at 4^brBTq2H_G$IQ_;`shwC0DbPFc5?(q7v~UKk_Hq zvZ830`mczb6fnjc(S+VmUci^|LZZ(yl?%RsZ>8TX-9=)I at zTsrzwXX_^UXBrp-p6& zQ*MfjOWf-S5ay!#HOi~(5m_|kCUV#F>?VfAL{FNKDR~PO1apJC4`j6m;&IZsBIFa{ z)Tj_1cBKjJ^K3!_n)9L*w#S)w(2Gsq3ldh^t<josgZHVsWZVj^ADzLh<H*cJ2k2$g zJ>}5~YQOP|--f!abc&L|IFeW<Taq%a-h3vm9Z9S15PD%@4AC<`Vn?YwN+5-8#V2b` z(+?q`C2bFSpJcfygE$6?(WEBnPt{%jPiRapH+WOji_W*AJAN35kgNwPBkT(2uFFv6 zbRDi0hZM$bGAwh3ZE#?BFc5W8yOOeI1D6+6#J*BlYp`mH7`RJVc#?7ky~hiM-d7f* z50u5wLrHl9J>rFe9xIEXC(2^zsicB|p7BCK>&jy2xw07Akholg6OI~Yx?wimjw2u4 zUSL_T{(Bss2?jQXNek}+ at kE(68mrpJJMq3Z?Es33)@Ug)7j;L=_~pGjN at j8O-(uCp WcDDszEb5M}&u5Cg1oM#=&iDfFy53I! literal 0 HcmV?d00001 -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 14/19] License: Add the Open Font License 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (12 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 13/19] video: Add the Cantoraone decorative font Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-21 19:11 ` Tom Rini 2016-01-23 0:23 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 15/19] video: Allow selection of the driver and font size Simon Glass ` (5 subsequent siblings) 19 siblings, 2 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This is used by two of the font files. Add this license to permit tracking of this. The copyright text cannot be added to the .ttf files, so put it here. Signed-off-by: Simon Glass <sjg@chromium.org> --- Licenses/OFL.txt | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Licenses/README | 1 + 2 files changed, 98 insertions(+) create mode 100644 Licenses/OFL.txt diff --git a/Licenses/OFL.txt b/Licenses/OFL.txt new file mode 100644 index 0000000..07c881f --- /dev/null +++ b/Licenses/OFL.txt @@ -0,0 +1,97 @@ +Copyright (c) 2010, Andrey Makarov (makarov at bmstu.ru, mka-at-mailru at mail.ru), +with Reserved Font Name Anka/Coder Narrow. + +Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari at gmail.com), +Rodrigo Fuenzalida (www.rfuenzalida.com) with Reserved Font Name Cantora. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Licenses/README b/Licenses/README index 731d45c..b850c70 100644 --- a/Licenses/README +++ b/Licenses/README @@ -67,4 +67,5 @@ BSD 3-clause "New" or "Revised" License BSD-3-Clause Y bsd-3-clause.txt http:/ IBM PIBS (PowerPC Initialization and IBM-pibs ibm-pibs.txt Boot Software) license ISC License ISC Y isc.txt https://spdx.org/licenses/ISC +SIL OPEN FONT LICENSE (OFL-1.1) OFL-1.1 Y https://opensource.org/licenses/OFL-1.1 X11 License X11 x11.txt https://spdx.org/licenses/X11.html -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 14/19] License: Add the Open Font License 2016-01-15 1:10 ` [U-Boot] [PATCH 14/19] License: Add the Open Font License Simon Glass @ 2016-01-21 19:11 ` Tom Rini 2016-01-23 0:23 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 1 sibling, 0 replies; 33+ messages in thread From: Tom Rini @ 2016-01-21 19:11 UTC (permalink / raw) To: u-boot On Thu, Jan 14, 2016 at 06:10:47PM -0700, Simon Glass wrote: > This is used by two of the font files. Add this license to permit tracking > of this. The copyright text cannot be added to the .ttf files, so put it > here. > > Signed-off-by: Simon Glass <sjg@chromium.org> [snip] > diff --git a/Licenses/README b/Licenses/README > index 731d45c..b850c70 100644 > --- a/Licenses/README > +++ b/Licenses/README > @@ -67,4 +67,5 @@ BSD 3-clause "New" or "Revised" License BSD-3-Clause Y bsd-3-clause.txt http:/ > IBM PIBS (PowerPC Initialization and IBM-pibs ibm-pibs.txt > Boot Software) license > ISC License ISC Y isc.txt https://spdx.org/licenses/ISC > +SIL OPEN FONT LICENSE (OFL-1.1) OFL-1.1 Y https://opensource.org/licenses/OFL-1.1 > X11 License X11 x11.txt https://spdx.org/licenses/X11.html I'd like to keep using either gnu.org or spdx.org for the URL so can you please use https://spdx.org/licenses/OFL-1.1.html instead? Thanks! -- Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160121/827807a7/attachment.sig> ^ permalink raw reply [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH v2 14/19] License: Add the Open Font License 2016-01-15 1:10 ` [U-Boot] [PATCH 14/19] License: Add the Open Font License Simon Glass 2016-01-21 19:11 ` Tom Rini @ 2016-01-23 0:23 ` Anatolij Gustschin 1 sibling, 0 replies; 33+ messages in thread From: Anatolij Gustschin @ 2016-01-23 0:23 UTC (permalink / raw) To: u-boot From: Simon Glass <sjg@chromium.org> This is used by two of the font files. Add this license to permit tracking of this. The copyright text cannot be added to the .ttf files, so put it here. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Anatolij Gustschin <agust@denx.de> --- Changes in v2: replaced the license URL as suggested by Tom and added OFL.txt file name Licenses/OFL.txt | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Licenses/README | 1 + 2 files changed, 98 insertions(+) create mode 100644 Licenses/OFL.txt diff --git a/Licenses/OFL.txt b/Licenses/OFL.txt new file mode 100644 index 0000000..07c881f --- /dev/null +++ b/Licenses/OFL.txt @@ -0,0 +1,97 @@ +Copyright (c) 2010, Andrey Makarov (makarov at bmstu.ru, mka-at-mailru at mail.ru), +with Reserved Font Name Anka/Coder Narrow. + +Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari at gmail.com), +Rodrigo Fuenzalida (www.rfuenzalida.com) with Reserved Font Name Cantora. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Licenses/README b/Licenses/README index 731d45c..5ad921d 100644 --- a/Licenses/README +++ b/Licenses/README @@ -67,4 +67,5 @@ BSD 3-clause "New" or "Revised" License BSD-3-Clause Y bsd-3-clause.txt http:/ IBM PIBS (PowerPC Initialization and IBM-pibs ibm-pibs.txt Boot Software) license ISC License ISC Y isc.txt https://spdx.org/licenses/ISC +SIL OPEN FONT LICENSE (OFL-1.1) OFL-1.1 Y OFL.txt https://spdx.org/licenses/OFL-1.1.html X11 License X11 x11.txt https://spdx.org/licenses/X11.html -- 1.7.9.5 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 15/19] video: Allow selection of the driver and font size 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (13 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 14/19] License: Add the Open Font License Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-23 0:24 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 16/19] video: sandbox: Allow selection of font size and console name Simon Glass ` (4 subsequent siblings) 19 siblings, 1 reply; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot Provide a way for the video console driver to be selected. This is controlled by the video driver's private data. This can be set up when the driver is probed so that it is ready for the video_post_probe() method. The font size is provided as well. The console driver may or may not support this depending on its capability. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/video-uclass.c | 20 ++++++++++++++++---- include/video.h | 5 +++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 2189fce..b6dd0f5 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -180,6 +180,7 @@ static int video_post_probe(struct udevice *dev) struct video_uc_platdata *plat = dev_get_uclass_platdata(dev); struct video_priv *priv = dev_get_uclass_priv(dev); char name[30], drv[15], *str; + const char *drv_name = drv; struct udevice *cons; int ret; @@ -197,11 +198,19 @@ static int video_post_probe(struct udevice *dev) video_clear(dev); /* - * Create a text console devices. For now we always do this, although + * Create a text console device. For now we always do this, although * it might be useful to support only bitmap drawing on the device - * for boards that don't need to display text. + * for boards that don't need to display text. We create a TrueType + * console if enabled, a rotated console if the video driver requests + * it, otherwise a normal console. + * + * The console can be override by setting vidconsole_drv_name before + * probing this video driver, or in the probe() method. + * + * TrueType does not support rotation at present so fall back to the + * rotated console in that case. */ - if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) { + if (!priv->rot && IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) { snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name); strcpy(drv, "vidconsole_tt"); } else { @@ -213,11 +222,14 @@ static int video_post_probe(struct udevice *dev) str = strdup(name); if (!str) return -ENOMEM; - ret = device_bind_driver(dev, drv, str, &cons); + if (priv->vidconsole_drv_name) + drv_name = priv->vidconsole_drv_name; + ret = device_bind_driver(dev, drv_name, str, &cons); if (ret) { debug("%s: Cannot bind console driver\n", __func__); return ret; } + ret = device_probe(cons); if (ret) { debug("%s: Cannot probe console driver\n", __func__); diff --git a/include/video.h b/include/video.h index 41e3cbf..5ed0999 100644 --- a/include/video.h +++ b/include/video.h @@ -56,6 +56,9 @@ enum video_log2_bpp { * @ysize: Number of pixels rows (e.g.. 768) * @tor: Display rotation (0=none, 1=90 degrees clockwise, etc.) * @bpix: Encoded bits per pixel + * @vidconsole_drv_name: Driver to use for the text console, NULL to + * select automatically + * @font_size: Font size in pixels (0 to use a default value) * @fb: Frame buffer * @fb_size: Frame buffer size * @fb_size: Frame buffer size @@ -72,6 +75,8 @@ struct video_priv { ushort ysize; ushort rot; enum video_log2_bpp bpix; + const char *vidconsole_drv_name; + int font_size; /* * Things that are private to the uclass: don't use these in the -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH v2 15/19] video: Allow selection of the driver and font size 2016-01-15 1:10 ` [U-Boot] [PATCH 15/19] video: Allow selection of the driver and font size Simon Glass @ 2016-01-23 0:24 ` Anatolij Gustschin 0 siblings, 0 replies; 33+ messages in thread From: Anatolij Gustschin @ 2016-01-23 0:24 UTC (permalink / raw) To: u-boot From: Simon Glass <sjg@chromium.org> Provide a way for the video console driver to be selected. This is controlled by the video driver's private data. This can be set up when the driver is probed so that it is ready for the video_post_probe() method. The font size is provided as well. The console driver may or may not support this depending on its capability. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Anatolij Gustschin <agust@denx.de> --- Changes in v2: rebased drivers/video/video-uclass.c | 20 ++++++++++++++++---- include/video.h | 5 +++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c index 13e8a88..2b5e0f9 100644 --- a/drivers/video/video-uclass.c +++ b/drivers/video/video-uclass.c @@ -173,6 +173,7 @@ static int video_post_probe(struct udevice *dev) struct video_uc_platdata *plat = dev_get_uclass_platdata(dev); struct video_priv *priv = dev_get_uclass_priv(dev); char name[30], drv[15], *str; + const char *drv_name = drv; struct udevice *cons; int ret; @@ -190,11 +191,19 @@ static int video_post_probe(struct udevice *dev) video_clear(dev); /* - * Create a text console devices. For now we always do this, although + * Create a text console device. For now we always do this, although * it might be useful to support only bitmap drawing on the device - * for boards that don't need to display text. + * for boards that don't need to display text. We create a TrueType + * console if enabled, a rotated console if the video driver requests + * it, otherwise a normal console. + * + * The console can be override by setting vidconsole_drv_name before + * probing this video driver, or in the probe() method. + * + * TrueType does not support rotation at present so fall back to the + * rotated console in that case. */ - if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) { + if (!priv->rot && IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) { snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name); strcpy(drv, "vidconsole_tt"); } else { @@ -206,11 +215,14 @@ static int video_post_probe(struct udevice *dev) str = strdup(name); if (!str) return -ENOMEM; - ret = device_bind_driver(dev, drv, str, &cons); + if (priv->vidconsole_drv_name) + drv_name = priv->vidconsole_drv_name; + ret = device_bind_driver(dev, drv_name, str, &cons); if (ret) { debug("%s: Cannot bind console driver\n", __func__); return ret; } + ret = device_probe(cons); if (ret) { debug("%s: Cannot probe console driver\n", __func__); diff --git a/include/video.h b/include/video.h index b20f06f..20fdf54 100644 --- a/include/video.h +++ b/include/video.h @@ -51,6 +51,9 @@ enum video_log2_bpp { * @ysize: Number of pixels rows (e.g.. 768) * @tor: Display rotation (0=none, 1=90 degrees clockwise, etc.) * @bpix: Encoded bits per pixel + * @vidconsole_drv_name: Driver to use for the text console, NULL to + * select automatically + * @font_size: Font size in pixels (0 to use a default value) * @fb: Frame buffer * @fb_size: Frame buffer size * @line_length: Length of each frame buffer line, in bytes @@ -66,6 +69,8 @@ struct video_priv { ushort ysize; ushort rot; enum video_log2_bpp bpix; + const char *vidconsole_drv_name; + int font_size; /* * Things that are private to the uclass: don't use these in the -- 1.7.9.5 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 16/19] video: sandbox: Allow selection of font size and console name 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (14 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 15/19] video: Allow selection of the driver and font size Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 17/19] video: sandbox: Enable truetype fonts for sandbox Simon Glass ` (3 subsequent siblings) 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot For testing it is useful to be able to select the font size and the console driver for sandbox. Add this information to platform data and copy it to the video device when needed. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/video/sandbox_sdl.c | 2 ++ include/dm/test.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/video/sandbox_sdl.c b/drivers/video/sandbox_sdl.c index 21448a1..dc5a220 100644 --- a/drivers/video/sandbox_sdl.c +++ b/drivers/video/sandbox_sdl.c @@ -35,6 +35,8 @@ static int sandbox_sdl_probe(struct udevice *dev) uc_priv->ysize = plat->yres; uc_priv->bpix = plat->bpix; uc_priv->rot = plat->rot; + uc_priv->vidconsole_drv_name = plat->vidconsole_drv_name; + uc_priv->font_size = plat->font_size; return 0; } diff --git a/include/dm/test.h b/include/dm/test.h index ca924d9..cba5049 100644 --- a/include/dm/test.h +++ b/include/dm/test.h @@ -161,6 +161,8 @@ struct sandbox_sdl_plat { int yres; int bpix; int rot; + const char *vidconsole_drv_name; + int font_size; }; /* Declare ping methods for the drivers */ -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 17/19] video: sandbox: Enable truetype fonts for sandbox 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (15 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 16/19] video: sandbox: Allow selection of font size and console name Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-23 0:32 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 18/19] video: test: Add console tests for truetype Simon Glass ` (2 subsequent siblings) 19 siblings, 1 reply; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot Enable this feature so that truetype fonts can be used on the sandbox console. Update the tests to select the normal/rotated console when needed. Signed-off-by: Simon Glass <sjg@chromium.org> --- configs/sandbox_defconfig | 2 ++ test/dm/video.c | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index aa92726..119cee6 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -78,6 +78,8 @@ CONFIG_USB_KEYBOARD=y CONFIG_SYS_USB_EVENT_POLL=y CONFIG_DM_VIDEO=y CONFIG_CONSOLE_ROTATION=y +CONFIG_CONSOLE_TRUETYPE=y +CONFIG_CONSOLE_TRUETYPE_CANTORAONE=y CONFIG_VIDEO_SANDBOX_SDL=y CONFIG_SYS_VSNPRINTF=y CONFIG_CMD_DHRYSTONE=y diff --git a/test/dm/video.c b/test/dm/video.c index cd00c96..86e6f20 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -86,6 +86,20 @@ static void __maybe_unused see_output(void) while (1); } +/* Select the video console driver to use for a video device */ +static int select_vidconsole(struct unit_test_state *uts, const char *drv_name) +{ + struct sandbox_sdl_plat *plat; + struct udevice *dev; + + ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev)); + ut_assert(!device_active(dev)); + plat = dev_get_platdata(dev); + plat->vidconsole_drv_name = "vidconsole0"; + + return 0; +} + /* Test text output works on the video console */ static int dm_test_video_text(struct unit_test_state *uts) { @@ -95,6 +109,7 @@ static int dm_test_video_text(struct unit_test_state *uts) #define WHITE 0xffff #define SCROLL_LINES 100 + ut_assertok(select_vidconsole(uts, "vidconsole0")); ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); ut_asserteq(46, compress_frame_buffer(dev)); @@ -127,6 +142,7 @@ static int dm_test_video_chars(struct unit_test_state *uts) const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very \amodest \bman\n\t\tand Has much to\b\bto be modest about."; const char *s; + ut_assertok(select_vidconsole(uts, "vidconsole0")); ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); for (s = test_string; *s; s++) @@ -185,7 +201,10 @@ static int check_vidconsole_output(struct unit_test_state *uts, int rot, /* Test text output through the console uclass */ static int dm_test_video_context(struct unit_test_state *uts) { - return check_vidconsole_output(uts, 0, 788, 453); + ut_assertok(select_vidconsole(uts, "vidconsole0")); + ut_assertok(check_vidconsole_output(uts, 0, 788, 453)); + + return 0; } DM_TEST(dm_test_video_context, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH v2 17/19] video: sandbox: Enable truetype fonts for sandbox 2016-01-15 1:10 ` [U-Boot] [PATCH 17/19] video: sandbox: Enable truetype fonts for sandbox Simon Glass @ 2016-01-23 0:32 ` Anatolij Gustschin 0 siblings, 0 replies; 33+ messages in thread From: Anatolij Gustschin @ 2016-01-23 0:32 UTC (permalink / raw) To: u-boot From: Simon Glass <sjg@chromium.org> Enable this feature so that truetype fonts can be used on the sandbox console. Update the tests to select the normal/rotated console when needed. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Anatolij Gustschin <agust@denx.de> --- Changes in v2: rebased configs/sandbox_defconfig | 2 ++ test/dm/video.c | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 2ebcba0..b5b81ca 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -78,6 +78,8 @@ CONFIG_USB_KEYBOARD=y CONFIG_SYS_USB_EVENT_POLL=y CONFIG_DM_VIDEO=y CONFIG_CONSOLE_ROTATION=y +CONFIG_CONSOLE_TRUETYPE=y +CONFIG_CONSOLE_TRUETYPE_CANTORAONE=y CONFIG_VIDEO_SANDBOX_SDL=y CONFIG_CMD_DHRYSTONE=y CONFIG_TPM=y diff --git a/test/dm/video.c b/test/dm/video.c index 4e3f9d5..a7e44e0 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -86,6 +86,20 @@ static void __maybe_unused see_output(void) while (1); } +/* Select the video console driver to use for a video device */ +static int select_vidconsole(struct unit_test_state *uts, const char *drv_name) +{ + struct sandbox_sdl_plat *plat; + struct udevice *dev; + + ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev)); + ut_assert(!device_active(dev)); + plat = dev_get_platdata(dev); + plat->vidconsole_drv_name = "vidconsole0"; + + return 0; +} + /* Test text output works on the video console */ static int dm_test_video_text(struct unit_test_state *uts) { @@ -95,6 +109,7 @@ static int dm_test_video_text(struct unit_test_state *uts) #define WHITE 0xffff #define SCROLL_LINES 100 + ut_assertok(select_vidconsole(uts, "vidconsole0")); ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); ut_asserteq(46, compress_frame_buffer(dev)); @@ -127,6 +142,7 @@ static int dm_test_video_chars(struct unit_test_state *uts) const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very \amodest \bman\n\t\tand Has much to\b\bto be modest about."; const char *s; + ut_assertok(select_vidconsole(uts, "vidconsole0")); ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); for (s = test_string; *s; s++) @@ -185,7 +201,10 @@ static int check_vidconsole_output(struct unit_test_state *uts, int rot, /* Test text output through the console uclass */ static int dm_test_video_context(struct unit_test_state *uts) { - return check_vidconsole_output(uts, 0, 788, 453); + ut_assertok(select_vidconsole(uts, "vidconsole0")); + ut_assertok(check_vidconsole_output(uts, 0, 788, 453)); + + return 0; } DM_TEST(dm_test_video_context, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); -- 1.7.9.5 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 18/19] video: test: Add console tests for truetype 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (16 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 17/19] video: sandbox: Enable truetype fonts for sandbox Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 19/19] video: Correct 'tor' typo in comment Simon Glass 2016-01-30 13:11 ` [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Anatolij Gustschin 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This adds tests for the different character types, line wrap, scrolling and backspace. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/dm/video.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/dm/video.c b/test/dm/video.c index 86e6f20..44fd628 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -288,3 +288,66 @@ static int dm_test_video_bmp_comp(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_video_bmp_comp, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test TrueType console */ +static int dm_test_video_truetype(struct unit_test_state *uts) +{ + struct udevice *dev, *con; + const char *test_string = "Criticism may not be agreeable, but it is necessary. It fulfils the same function as pain in the human body. It calls attention to an unhealthy state of things. Some see private enterprise as a predatory target to be shot, others as a cow to be milked, but few are those who see it as a sturdy horse pulling the wagon. The \aprice OF\b\bof greatness\n\tis responsibility.\n\nBye"; + const char *s; + + ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); + ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); + for (s = test_string; *s; s++) + vidconsole_put_char(con, *s); + ut_asserteq(12619, compress_frame_buffer(dev)); + + return 0; +} +DM_TEST(dm_test_video_truetype, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test scrolling TrueType console */ +static int dm_test_video_truetype_scroll(struct unit_test_state *uts) +{ + struct sandbox_sdl_plat *plat; + struct udevice *dev, *con; + const char *test_string = "Criticism may not be agreeable, but it is necessary. It fulfils the same function as pain in the human body. It calls attention to an unhealthy state of things. Some see private enterprise as a predatory target to be shot, others as a cow to be milked, but few are those who see it as a sturdy horse pulling the wagon. The \aprice OF\b\bof greatness\n\tis responsibility.\n\nBye"; + const char *s; + + ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev)); + ut_assert(!device_active(dev)); + plat = dev_get_platdata(dev); + plat->font_size = 100; + + ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); + ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); + for (s = test_string; *s; s++) + vidconsole_put_char(con, *s); + ut_asserteq(33849, compress_frame_buffer(dev)); + + return 0; +} +DM_TEST(dm_test_video_truetype_scroll, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); + +/* Test TrueType backspace, within and across lines */ +static int dm_test_video_truetype_bs(struct unit_test_state *uts) +{ + struct sandbox_sdl_plat *plat; + struct udevice *dev, *con; + const char *test_string = "...Criticism may or may\b\b\b\b\b\bnot be agreeable, but seldom it is necessary\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bit is necessary. It fulfils the same function as pain in the human body. It calls attention to an unhealthy state of things."; + const char *s; + + ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev)); + ut_assert(!device_active(dev)); + plat = dev_get_platdata(dev); + plat->font_size = 100; + + ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev)); + ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con)); + for (s = test_string; *s; s++) + vidconsole_put_char(con, *s); + ut_asserteq(34871, compress_frame_buffer(dev)); + + return 0; +} +DM_TEST(dm_test_video_truetype_bs, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 19/19] video: Correct 'tor' typo in comment 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (17 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 18/19] video: test: Add console tests for truetype Simon Glass @ 2016-01-15 1:10 ` Simon Glass 2016-01-30 13:11 ` [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Anatolij Gustschin 19 siblings, 0 replies; 33+ messages in thread From: Simon Glass @ 2016-01-15 1:10 UTC (permalink / raw) To: u-boot This should be 'rot', not 'tor'. Signed-off-by: Simon Glass <sjg@chromium.org> --- include/video.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/video.h b/include/video.h index 5ed0999..cc2b35a 100644 --- a/include/video.h +++ b/include/video.h @@ -54,7 +54,7 @@ enum video_log2_bpp { * * @xsize: Number of pixel columns (e.g. 1366) * @ysize: Number of pixels rows (e.g.. 768) - * @tor: Display rotation (0=none, 1=90 degrees clockwise, etc.) + * @rot: Display rotation (0=none, 1=90 degrees clockwise, etc.) * @bpix: Encoded bits per pixel * @vidconsole_drv_name: Driver to use for the text console, NULL to * select automatically -- 2.6.0.rc2.230.g3dd15c0 ^ permalink raw reply related [flat|nested] 33+ messages in thread
* [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass ` (18 preceding siblings ...) 2016-01-15 1:10 ` [U-Boot] [PATCH 19/19] video: Correct 'tor' typo in comment Simon Glass @ 2016-01-30 13:11 ` Anatolij Gustschin 19 siblings, 0 replies; 33+ messages in thread From: Anatolij Gustschin @ 2016-01-30 13:11 UTC (permalink / raw) To: u-boot Hi Simon, On Thu, 14 Jan 2016 18:10:33 -0700 Simon Glass sjg at chromium.org wrote: ... > Simon Glass (19): > video: Add stb TrueType font renderer > Makefile: Add rules to build in .ttf files > video kconfig console_normal > video: Use fractional units for X coordinates > video: Handle the 'bell' character > video: Provide a left margin for the text console > video: Provide a signal when a new console line is started > video: Provide a backspace method > video: Add a console driver that uses TrueType fonts > video: Add the Nimbus sans font > video: Add the AnkaCoder mono-spaced font > video: Add the Rufscript handwriting font > video: Add the Cantoraone decorative font > License: Add the Open Font License > video: Allow selection of the driver and font size > video: sandbox: Allow selection of font size and console name > video: sandbox: Enable truetype fonts for sandbox > video: test: Add console tests for truetype > video: Correct 'tor' typo in comment > > Licenses/OFL.txt | 97 + > Licenses/README | 1 + > configs/sandbox_defconfig | 4 +- > drivers/video/Kconfig | 36 +- > drivers/video/Makefile | 6 +- > drivers/video/console_normal.c | 24 +- > drivers/video/console_rotate.c | 66 +- > drivers/video/console_truetype.c | 550 +++++ > drivers/video/fonts/Kconfig | 51 + > drivers/video/fonts/Makefile | 11 + > drivers/video/fonts/ankacoder_c75_r.ttf | Bin 0 -> 65596 bytes > drivers/video/fonts/cantoraone_regular.ttf | Bin 0 -> 163116 bytes > drivers/video/fonts/nimbus_sans_l_regular.ttf | Bin 0 -> 61660 bytes > drivers/video/fonts/rufscript010.ttf | Bin 0 -> 23080 bytes > drivers/video/sandbox_sdl.c | 2 + > drivers/video/stb_truetype.h | 3240 +++++++++++++++++++++++++ > drivers/video/vidconsole-uclass.c | 84 +- > drivers/video/video-uclass.c | 29 +- > include/dm/test.h | 2 + > include/video.h | 7 +- > include/video_console.h | 70 +- > scripts/Makefile.lib | 21 + > test/dm/video.c | 90 +- > 23 files changed, 4326 insertions(+), 65 deletions(-) > create mode 100644 Licenses/OFL.txt > create mode 100644 drivers/video/console_truetype.c > create mode 100644 drivers/video/fonts/Kconfig > create mode 100644 drivers/video/fonts/Makefile > create mode 100644 drivers/video/fonts/ankacoder_c75_r.ttf > create mode 100644 drivers/video/fonts/cantoraone_regular.ttf > create mode 100644 drivers/video/fonts/nimbus_sans_l_regular.ttf > create mode 100644 drivers/video/fonts/rufscript010.ttf > create mode 100644 drivers/video/stb_truetype.h Series applied to u-boot-video/master. Thanks! Anatolij ^ permalink raw reply [flat|nested] 33+ messages in thread
end of thread, other threads:[~2016-01-30 13:11 UTC | newest] Thread overview: 33+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2016-01-15 1:10 [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 01/19] video: Add stb TrueType font renderer Simon Glass 2016-01-15 1:50 ` Måns Rullgård 2016-01-15 1:56 ` Simon Glass 2016-01-21 16:56 ` Tom Rini 2016-01-21 17:05 ` Måns Rullgård 2016-01-15 1:10 ` [U-Boot] [PATCH 02/19] Makefile: Add rules to build in .ttf files Simon Glass 2016-01-21 19:11 ` Tom Rini 2016-01-15 1:10 ` [U-Boot] [PATCH 03/19] video kconfig console_normal Simon Glass 2016-01-23 0:14 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 04/19] video: Use fractional units for X coordinates Simon Glass 2016-01-23 0:19 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 05/19] video: Handle the 'bell' character Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 06/19] video: Provide a left margin for the text console Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 07/19] video: Provide a signal when a new console line is started Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 08/19] video: Provide a backspace method Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 09/19] video: Add a console driver that uses TrueType fonts Simon Glass 2016-01-23 0:21 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 10/19] video: Add the Nimbus sans font Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 11/19] video: Add the AnkaCoder mono-spaced font Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 12/19] video: Add the Rufscript handwriting font Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 13/19] video: Add the Cantoraone decorative font Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 14/19] License: Add the Open Font License Simon Glass 2016-01-21 19:11 ` Tom Rini 2016-01-23 0:23 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 15/19] video: Allow selection of the driver and font size Simon Glass 2016-01-23 0:24 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 16/19] video: sandbox: Allow selection of font size and console name Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 17/19] video: sandbox: Enable truetype fonts for sandbox Simon Glass 2016-01-23 0:32 ` [U-Boot] [PATCH v2 " Anatolij Gustschin 2016-01-15 1:10 ` [U-Boot] [PATCH 18/19] video: test: Add console tests for truetype Simon Glass 2016-01-15 1:10 ` [U-Boot] [PATCH 19/19] video: Correct 'tor' typo in comment Simon Glass 2016-01-30 13:11 ` [U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts Anatolij Gustschin
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox