mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 09:57:43 +03:00 
			
		
		
		
	Corrected ttb text positioning
This commit is contained in:
		
							parent
							
								
									169961649d
								
							
						
					
					
						commit
						1c1447f563
					
				| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
 | 
			
		||||
NotoNastaliqUrdu-Regular.ttf:
 | 
			
		||||
 | 
			
		||||
(from https://github.com/googlei18n/noto-fonts)
 | 
			
		||||
NotoNastaliqUrdu-Regular.ttf, from https://github.com/googlei18n/noto-fonts
 | 
			
		||||
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
 | 
			
		||||
 | 
			
		||||
All Noto fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								Tests/fonts/NotoSansJP-Regular.otf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/fonts/NotoSansJP-Regular.otf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/test_direction_ttb.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/test_direction_ttb.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.7 KiB  | 
| 
						 | 
				
			
			@ -104,6 +104,22 @@ class TestImagecomplextext(PillowTestCase):
 | 
			
		|||
 | 
			
		||||
        self.assert_image_similar(im, target_img, .5)
 | 
			
		||||
 | 
			
		||||
    def test_text_direction_ttb(self):
 | 
			
		||||
        ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE)
 | 
			
		||||
 | 
			
		||||
        im = Image.new(mode='RGB', size=(100, 300))
 | 
			
		||||
        draw = ImageDraw.Draw(im)
 | 
			
		||||
        try:
 | 
			
		||||
            draw.text((0, 0), 'English あい', font=ttf, fill=500, direction='ttb')
 | 
			
		||||
        except ValueError as ex:
 | 
			
		||||
            if str(ex) == "libraqm 0.6 or greater required for 'ttb' direction":
 | 
			
		||||
                self.skipTest('libraqm 0.6 or greater not available')
 | 
			
		||||
 | 
			
		||||
        target = 'Tests/images/test_direction_ttb.png'
 | 
			
		||||
        target_img = Image.open(target)
 | 
			
		||||
 | 
			
		||||
        self.assert_image_similar(im, target_img, 1.15)
 | 
			
		||||
 | 
			
		||||
    def test_ligature_features(self):
 | 
			
		||||
        ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										159
									
								
								src/_imagingft.c
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								src/_imagingft.c
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -54,7 +54,7 @@
 | 
			
		|||
 | 
			
		||||
typedef struct
 | 
			
		||||
{
 | 
			
		||||
  int index, x_offset, x_advance, y_offset;
 | 
			
		||||
  int index, x_offset, x_advance, y_offset, y_advance;
 | 
			
		||||
  unsigned int cluster;
 | 
			
		||||
} GlyphInfo;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -407,9 +407,17 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
 | 
			
		|||
            direction = RAQM_DIRECTION_RTL;
 | 
			
		||||
        else if (strcmp(dir, "ltr") == 0)
 | 
			
		||||
            direction = RAQM_DIRECTION_LTR;
 | 
			
		||||
        else if (strcmp(dir, "ttb") == 0)
 | 
			
		||||
        else if (strcmp(dir, "ttb") == 0) {
 | 
			
		||||
            direction = RAQM_DIRECTION_TTB;
 | 
			
		||||
        else {
 | 
			
		||||
#if !defined(_MSC_VER)
 | 
			
		||||
            if (!dlsym(p_raqm.raqm, "raqm_set_invisible_glyph")) {
 | 
			
		||||
#else
 | 
			
		||||
            if (!GetProcAddress(p_raqm.raqm, "raqm_set_invisible_glyph")) {
 | 
			
		||||
#endif
 | 
			
		||||
                PyErr_SetString(PyExc_ValueError, "libraqm 0.6 or greater required for 'ttb' direction");
 | 
			
		||||
                goto failed;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            PyErr_SetString(PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
 | 
			
		||||
            goto failed;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -502,6 +510,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
 | 
			
		|||
            (*glyph_info)[i].x_offset = glyphs_01[i].x_offset;
 | 
			
		||||
            (*glyph_info)[i].x_advance = glyphs_01[i].x_advance;
 | 
			
		||||
            (*glyph_info)[i].y_offset = glyphs_01[i].y_offset;
 | 
			
		||||
            (*glyph_info)[i].y_advance = glyphs_01[i].y_advance;
 | 
			
		||||
            (*glyph_info)[i].cluster = glyphs_01[i].cluster;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -510,6 +519,7 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *
 | 
			
		|||
            (*glyph_info)[i].x_offset = glyphs[i].x_offset;
 | 
			
		||||
            (*glyph_info)[i].x_advance = glyphs[i].x_advance;
 | 
			
		||||
            (*glyph_info)[i].y_offset = glyphs[i].y_offset;
 | 
			
		||||
            (*glyph_info)[i].y_advance = glyphs[i].y_advance;
 | 
			
		||||
            (*glyph_info)[i].cluster = glyphs[i].cluster;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -576,9 +586,11 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje
 | 
			
		|||
            if (FT_Get_Kerning(self->face, last_index, (*glyph_info)[i].index,
 | 
			
		||||
                           ft_kerning_default,&delta) == 0)
 | 
			
		||||
            (*glyph_info)[i-1].x_advance += PIXEL(delta.x);
 | 
			
		||||
            (*glyph_info)[i-1].y_advance += PIXEL(delta.y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        (*glyph_info)[i].x_advance = glyph->metrics.horiAdvance;
 | 
			
		||||
        (*glyph_info)[i].y_advance = glyph->metrics.vertAdvance;
 | 
			
		||||
        last_index = (*glyph_info)[i].index;
 | 
			
		||||
        (*glyph_info)[i].cluster = ch;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -602,9 +614,10 @@ text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *featu
 | 
			
		|||
static PyObject*
 | 
			
		||||
font_getsize(FontObject* self, PyObject* args)
 | 
			
		||||
{
 | 
			
		||||
    int x, y_max, y_min;
 | 
			
		||||
    int x_max, x_min, y_max, y_min;
 | 
			
		||||
    FT_Face face;
 | 
			
		||||
    int xoffset, yoffset;
 | 
			
		||||
    int horizontal_dir;
 | 
			
		||||
    const char *dir = NULL;
 | 
			
		||||
    const char *lang = NULL;
 | 
			
		||||
    size_t i, count;
 | 
			
		||||
| 
						 | 
				
			
			@ -619,15 +632,15 @@ font_getsize(FontObject* self, PyObject* args)
 | 
			
		|||
 | 
			
		||||
    face = NULL;
 | 
			
		||||
    xoffset = yoffset = 0;
 | 
			
		||||
    y_max = y_min = 0;
 | 
			
		||||
    x_max = x_min = y_max = y_min = 0;
 | 
			
		||||
 | 
			
		||||
    count = text_layout(string, self, dir, features, lang, &glyph_info, 0);
 | 
			
		||||
    if (PyErr_Occurred()) {
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    for (x = i = 0; i < count; i++) {
 | 
			
		||||
    horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
 | 
			
		||||
    for (i = 0; i < count; i++) {
 | 
			
		||||
        int index, error;
 | 
			
		||||
        FT_BBox bbox;
 | 
			
		||||
        FT_Glyph glyph;
 | 
			
		||||
| 
						 | 
				
			
			@ -640,25 +653,35 @@ font_getsize(FontObject* self, PyObject* args)
 | 
			
		|||
        if (error)
 | 
			
		||||
            return geterror(error);
 | 
			
		||||
 | 
			
		||||
        if (i == 0 && face->glyph->metrics.horiBearingX < 0) {
 | 
			
		||||
        if (i == 0) {
 | 
			
		||||
            if (horizontal_dir) {
 | 
			
		||||
                if (face->glyph->metrics.horiBearingX < 0) {
 | 
			
		||||
                    xoffset = face->glyph->metrics.horiBearingX;
 | 
			
		||||
            x -= xoffset;
 | 
			
		||||
                    x_max -= xoffset;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (face->glyph->metrics.vertBearingY < 0) {
 | 
			
		||||
                    yoffset = face->glyph->metrics.vertBearingY;
 | 
			
		||||
                    y_max -= yoffset;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        x += glyph_info[i].x_advance;
 | 
			
		||||
        FT_Get_Glyph(face->glyph, &glyph);
 | 
			
		||||
        FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
 | 
			
		||||
        if (horizontal_dir) {
 | 
			
		||||
            x_max += glyph_info[i].x_advance;
 | 
			
		||||
 | 
			
		||||
        if (i == count - 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (i == count - 1) {
 | 
			
		||||
                // trim end gap from final glyph
 | 
			
		||||
                int offset;
 | 
			
		||||
                offset = glyph_info[i].x_advance -
 | 
			
		||||
                        face->glyph->metrics.width -
 | 
			
		||||
                        face->glyph->metrics.horiBearingX;
 | 
			
		||||
                if (offset < 0)
 | 
			
		||||
                x -= offset;
 | 
			
		||||
                    x_max -= offset;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        FT_Get_Glyph(face->glyph, &glyph);
 | 
			
		||||
        FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox);
 | 
			
		||||
            bbox.yMax -= glyph_info[i].y_offset;
 | 
			
		||||
            bbox.yMin -= glyph_info[i].y_offset;
 | 
			
		||||
            if (bbox.yMax > y_max)
 | 
			
		||||
| 
						 | 
				
			
			@ -666,9 +689,27 @@ font_getsize(FontObject* self, PyObject* args)
 | 
			
		|||
            if (bbox.yMin < y_min)
 | 
			
		||||
                y_min = bbox.yMin;
 | 
			
		||||
 | 
			
		||||
        /* find max distance of baseline from top */
 | 
			
		||||
            // find max distance of baseline from top
 | 
			
		||||
            if (face->glyph->metrics.horiBearingY > yoffset)
 | 
			
		||||
                yoffset = face->glyph->metrics.horiBearingY;
 | 
			
		||||
        } else {
 | 
			
		||||
            y_max -= glyph_info[i].y_advance;
 | 
			
		||||
 | 
			
		||||
            if (i == count - 1) {
 | 
			
		||||
                // trim end gap from final glyph
 | 
			
		||||
                int offset;
 | 
			
		||||
                offset = -glyph_info[i].y_advance -
 | 
			
		||||
                        face->glyph->metrics.height -
 | 
			
		||||
                        face->glyph->metrics.vertBearingY;
 | 
			
		||||
                if (offset < 0)
 | 
			
		||||
                    y_max -= offset;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (bbox.xMax > x_max)
 | 
			
		||||
                x_max = bbox.xMax;
 | 
			
		||||
            if (i == 0 || bbox.xMin < x_min)
 | 
			
		||||
                x_min = bbox.xMin;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FT_Done_Glyph(glyph);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -679,20 +720,28 @@ font_getsize(FontObject* self, PyObject* args)
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    if (face) {
 | 
			
		||||
 | 
			
		||||
        /* left bearing */
 | 
			
		||||
        if (horizontal_dir) {
 | 
			
		||||
            // left bearing
 | 
			
		||||
            if (xoffset < 0)
 | 
			
		||||
            x -= xoffset;
 | 
			
		||||
                x_max -= xoffset;
 | 
			
		||||
            else
 | 
			
		||||
                xoffset = 0;
 | 
			
		||||
 | 
			
		||||
            /* difference between the font ascender and the distance of
 | 
			
		||||
             * the baseline from the top */
 | 
			
		||||
            yoffset = PIXEL(self->face->size->metrics.ascender - yoffset);
 | 
			
		||||
        } else {
 | 
			
		||||
            // top bearing
 | 
			
		||||
            if (yoffset < 0)
 | 
			
		||||
                y_max -= yoffset;
 | 
			
		||||
            else
 | 
			
		||||
                yoffset = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Py_BuildValue(
 | 
			
		||||
        "(ii)(ii)",
 | 
			
		||||
        PIXEL(x), PIXEL(y_max - y_min),
 | 
			
		||||
        PIXEL(x_max - x_min), PIXEL(y_max - y_min),
 | 
			
		||||
        PIXEL(xoffset), yoffset
 | 
			
		||||
        );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -703,7 +752,7 @@ font_render(FontObject* self, PyObject* args)
 | 
			
		|||
    int x;
 | 
			
		||||
    unsigned int y;
 | 
			
		||||
    Imaging im;
 | 
			
		||||
    int index, error, ascender;
 | 
			
		||||
    int index, error, ascender, horizontal_dir;
 | 
			
		||||
    int load_flags;
 | 
			
		||||
    unsigned char *source;
 | 
			
		||||
    FT_GlyphSlot glyph;
 | 
			
		||||
| 
						 | 
				
			
			@ -714,6 +763,8 @@ font_render(FontObject* self, PyObject* args)
 | 
			
		|||
    int mask = 0;
 | 
			
		||||
    int temp;
 | 
			
		||||
    int xx, x0, x1;
 | 
			
		||||
    int yy;
 | 
			
		||||
    unsigned int bitmap_y;
 | 
			
		||||
    const char *dir = NULL;
 | 
			
		||||
    const char *lang = NULL;
 | 
			
		||||
    size_t i, count;
 | 
			
		||||
| 
						 | 
				
			
			@ -747,27 +798,34 @@ font_render(FontObject* self, PyObject* args)
 | 
			
		|||
            return geterror(error);
 | 
			
		||||
 | 
			
		||||
        glyph = self->face->glyph;
 | 
			
		||||
        temp = (glyph->bitmap.rows - glyph->bitmap_top);
 | 
			
		||||
        temp = glyph->bitmap.rows - glyph->bitmap_top;
 | 
			
		||||
        temp -= PIXEL(glyph_info[i].y_offset);
 | 
			
		||||
        if (temp > ascender)
 | 
			
		||||
            ascender = temp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (x = i = 0; i < count; i++) {
 | 
			
		||||
    x = y = 0;
 | 
			
		||||
    horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
 | 
			
		||||
    for (i = 0; i < count; i++) {
 | 
			
		||||
        index = glyph_info[i].index;
 | 
			
		||||
        error = FT_Load_Glyph(self->face, index, load_flags);
 | 
			
		||||
        if (error)
 | 
			
		||||
            return geterror(error);
 | 
			
		||||
 | 
			
		||||
        glyph = self->face->glyph;
 | 
			
		||||
        if (horizontal_dir) {
 | 
			
		||||
            if (i == 0 && self->face->glyph->metrics.horiBearingX < 0) {
 | 
			
		||||
                x = -self->face->glyph->metrics.horiBearingX;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        glyph = self->face->glyph;
 | 
			
		||||
 | 
			
		||||
        source = (unsigned char*) glyph->bitmap.buffer;
 | 
			
		||||
            xx = PIXEL(x) + glyph->bitmap_left;
 | 
			
		||||
            xx += PIXEL(glyph_info[i].x_offset);
 | 
			
		||||
        } else {
 | 
			
		||||
            if (self->face->glyph->metrics.vertBearingX < 0) {
 | 
			
		||||
                x = -self->face->glyph->metrics.vertBearingX;
 | 
			
		||||
            }
 | 
			
		||||
            xx = im->xsize / 2 - glyph->bitmap.width / 2;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        x0 = 0;
 | 
			
		||||
        x1 = glyph->bitmap.width;
 | 
			
		||||
        if (xx < 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -775,45 +833,42 @@ font_render(FontObject* self, PyObject* args)
 | 
			
		|||
        if (xx + x1 > im->xsize)
 | 
			
		||||
            x1 = im->xsize - xx;
 | 
			
		||||
 | 
			
		||||
        if (mask) {
 | 
			
		||||
            /* use monochrome mask (on palette images, etc) */
 | 
			
		||||
            for (y = 0; y < glyph->bitmap.rows; y++) {
 | 
			
		||||
                int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
 | 
			
		||||
        source = (unsigned char*) glyph->bitmap.buffer;
 | 
			
		||||
        for (bitmap_y = 0; bitmap_y < glyph->bitmap.rows; bitmap_y++) {
 | 
			
		||||
            if (horizontal_dir) {
 | 
			
		||||
                yy = bitmap_y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
 | 
			
		||||
                yy -= PIXEL(glyph_info[i].y_offset);
 | 
			
		||||
            } else {
 | 
			
		||||
                yy = bitmap_y + PIXEL(y + glyph->metrics.vertBearingY) + ascender;
 | 
			
		||||
                yy += PIXEL(glyph_info[i].y_offset);
 | 
			
		||||
            }
 | 
			
		||||
            if (yy >= 0 && yy < im->ysize) {
 | 
			
		||||
                    /* blend this glyph into the buffer */
 | 
			
		||||
                // blend this glyph into the buffer
 | 
			
		||||
                unsigned char *target = im->image8[yy] + xx;
 | 
			
		||||
                    int i, j, m = 128;
 | 
			
		||||
                    for (i = j = 0; j < x1; j++) {
 | 
			
		||||
                        if (j >= x0 && (source[i] & m))
 | 
			
		||||
                if (mask) {
 | 
			
		||||
                    // use monochrome mask (on palette images, etc)
 | 
			
		||||
                    int j, k, m = 128;
 | 
			
		||||
                    for (j = k = 0; j < x1; j++) {
 | 
			
		||||
                        if (j >= x0 && (source[k] & m))
 | 
			
		||||
                            target[j] = 255;
 | 
			
		||||
                        if (!(m >>= 1)) {
 | 
			
		||||
                            m = 128;
 | 
			
		||||
                            i++;
 | 
			
		||||
                            k++;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                source += glyph->bitmap.pitch;
 | 
			
		||||
            }
 | 
			
		||||
                } else {
 | 
			
		||||
            /* use antialiased rendering */
 | 
			
		||||
            for (y = 0; y < glyph->bitmap.rows; y++) {
 | 
			
		||||
                int yy = y + im->ysize - (PIXEL(glyph->metrics.horiBearingY) + ascender);
 | 
			
		||||
                yy -= PIXEL(glyph_info[i].y_offset);
 | 
			
		||||
                if (yy >= 0 && yy < im->ysize) {
 | 
			
		||||
                    /* blend this glyph into the buffer */
 | 
			
		||||
 | 
			
		||||
                    int i;
 | 
			
		||||
                    unsigned char *target = im->image8[yy] + xx;
 | 
			
		||||
                    for (i = x0; i < x1; i++) {
 | 
			
		||||
                        if (target[i] < source[i])
 | 
			
		||||
                            target[i] = source[i];
 | 
			
		||||
                    // use antialiased rendering
 | 
			
		||||
                    int k;
 | 
			
		||||
                    for (k = x0; k < x1; k++) {
 | 
			
		||||
                        if (target[k] < source[k])
 | 
			
		||||
                            target[k] = source[k];
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            source += glyph->bitmap.pitch;
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
        x += glyph_info[i].x_advance;
 | 
			
		||||
        y -= glyph_info[i].y_advance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PyMem_Del(glyph_info);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user