近来用skia试着做文字的相关排版,当然安卓上面已经有相关的接口了,但出于学习目的(安卓底层调用的也是skia),这里就自己写一个极其简单的,其中功能包括设置行间距,对齐方式等等,目前只能实现从左到右排版,至于垂直排版因为我在调用skia的一个接口时遇到一个问题,目前还不能解决(我在后面也将问题贴了出来,希望有大佬可以帮我解决一下),下面就直接分享一下代码,很low,但作为初学者拿来学习skia还多少有点用途吧。(至于怎么在vs中建立工程使之运行起来,可以参考前一篇文章“skia入门例子(在vs中怎么创建一个skia例子),傻瓜式说明,简单易懂”),后续已有更新更多内容,,,,,
// HelloSkia.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "SkBitmap.h"
#include "SkDevice.h"
#include "SkPaint.h"
#include "SkRect.h"
#include "SkImageEncoder.h"
#include "SkTypeface.h"
#include "SkCanvas.h"
#include "iostream"
/*
void drawtext(char text[], size_t byteLength,float testhighth, const SkPaint& paint, SkCanvas canvas,const int width, const int height) {
SkScalar measuredWidth;
int partialBytes = paint.breakText(text, byteLength, width, &measuredWidth);
if (partialBytes < byteLength) {
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
int j = 0;
for (int i = partialBytes; i < byteLength; i++) {
text[j] = text[i];
j++;
}
text[j] = '\0';
testhighth += testhighth;
drawtext(text, strlen(text), testhighth, paint, canvas, width, height);
}
else
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
}
*/
void ConversionText(char text[], int partialBytes, int byteLength) {
int j = 0;
for (int i = partialBytes; i < byteLength; i++) {
text[j] = text[i];
j++;
}
text[j] = '\0';
}
void setTypeText(char text[], SkPaint& paint, float LineSpacing,char Alignment) {
const int width = 512;
const int height = 512;
bool flag = true;
SkBitmap bitmap;
SkImageInfo ii = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
bitmap.allocPixels(ii, ii.minRowBytes());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.clear(0x00000000);// 背景为透明色
{
SkRect bounds;
paint.measureText(text, strlen(text), &bounds);
float y = bounds.fBottom - bounds.fTop;
while (flag) {
int partialBytes = paint.breakText(text, strlen(text), width);
//SkDebugf("%d/n", partialBytes);
if (partialBytes < strlen(text)) {
SkRect bounds;
paint.measureText(text, partialBytes, &bounds);
float testwidth = bounds.fRight - bounds.fLeft;
switch (Alignment)
{
case 'L':case 'l':
canvas.drawText(text, partialBytes, 0.0f, y, paint);
break;
case 'R':case 'r':
canvas.drawText(text, partialBytes, width - testwidth, y, paint);
break;
case 'C':case 'c':
paint.setTextAlign(SkPaint::kCenter_Align);
canvas.drawText(text, partialBytes, width / 2.0, y, paint);
break;
default:
canvas.drawText(text, partialBytes, 0.0f, y, paint);
}
ConversionText(text, partialBytes, strlen(text));
SkRect bounds2;
paint.measureText(text, strlen(text), &bounds2);
y += (bounds2.fBottom - bounds2.fTop) + LineSpacing;
if (y > height) {
flag = false;
}
}
else {
SkRect bounds;
paint.measureText(text, strlen(text), &bounds);
float testwidth = bounds.fRight - bounds.fLeft;
switch (Alignment)
{
case 'L':case 'l':
canvas.drawText(text, partialBytes, 0.0f, y, paint);
break;
case 'R':case 'r':
canvas.drawText(text, partialBytes, width - testwidth, y, paint);
break;
case 'C':case 'c':
paint.setTextAlign(SkPaint::kCenter_Align);
canvas.drawText(text, partialBytes, width / 2.0, y, paint);
break;
default:
canvas.drawText(text, partialBytes, 0.0f, y, paint);
}
flag = false;
}
}
}
SkFILEWStream stream("D:\\Image\\board.jpg");
SkEncodeImage(&stream, bitmap, SkEncodedImageFormat::kPNG, 100);
}
int main(int argc, _TCHAR* argv[])
{
char text[] = "Life needs one person to struggle.Only by continuous learning can you improve yourself.";
SkPaint paint;
paint.setTextSize(70.0);
paint.setAntiAlias(true);
paint.setColor(0xff4281A4);
paint.setStyle(SkPaint::kFill_Style);
paint.setTextSkewX(-.3f);
//paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
//第三个参数是行间隔,第四个参数是对齐方式,有r,R右对齐,l,L左对齐,C,c居中三种方式
//输入关键字不正确时按左对齐处理
setTypeText(text, paint, 5.0, 'R');
return 0;
}
运行结果为:
其中还有很多问题有待解决,比如单词的连续性,如果是中文怎么办等等,有时间的话后续我会继续做相关的工作。
下面来说一下开头说的问题即遇到使用paint.setFlags(SkPaint::kVerticalText_Flag)或paint.setVerticalText无效,
官网给出的类子是这样的,在使用了paint.setVerticalText(true)之后,可以竖着draw出字,然而我加上后缺没有什么变化!!!
我的就是这样:
没加之前如下,这算正常吧!!!!
可是加上之后,并没有什么卵用:
就是这个问题搞了很久。。。。。还是无果,不知道是怎么回事
更新,,,,,,,,
这次解决了输出中文,因为每个中文占两个字节,而每个英文字母占一个字节,所以一段话中如果既有中文又有英文会在处理宽度等等方面会比较难弄,这里我采用的是wchar_t类型,paint.setTextEncoding(SkPaint::kUTF16_TextEncoding)编码,注意wchar_t字符串中即使是英文字母其也是按照两个字节来处理的,所以这样的话就可以统一来处理了,反正都是两个字节对吧,另一个问题就是对齐方式中的居中方式,在之前的英文版中是调用skia接口的paint.setTextAlign(SkPaint::kCenter_Align);这个接口的作用就是将字符串的中间对准canvas.drawText中指定的坐标,但是当有中文、标点符号混在一起的话,标点符号在视觉上是一个字节但却按两个字节处理,所以如果还是采用老办法那么居中效果看起来并不是很居中,如下:其中红色下标线的两行就是不理想的结果
为了解决这一问题,在这里抛弃了前面的方法,直接采用了简单粗暴的方式(对比之前的代码就可以看到哪里不同)
// HelloSkia.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "SkBitmap.h"
#include "SkDevice.h"
#include "SkPaint.h"
#include "SkRect.h"
#include "SkImageEncoder.h"
#include "SkTypeface.h"
#include "SkCanvas.h"
#include "iostream"
/*
void drawtext(char text[], size_t byteLength,float testhighth, const SkPaint& paint, SkCanvas canvas,const int width, const int height) {
SkScalar measuredWidth;
int partialBytes = paint.breakText(text, byteLength, width, &measuredWidth);
if (partialBytes < byteLength) {
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
int j = 0;
for (int i = partialBytes; i < byteLength; i++) {
text[j] = text[i];
j++;
}
text[j] = '\0';
testhighth += testhighth;
drawtext(text, strlen(text), testhighth, paint, canvas, width, height);
}
else
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
}
*/
void ConversionText(wchar_t text[], int partialBytes, int byteLength) {
int j = 0;
for (int i = partialBytes/2; i < byteLength; i++) {
text[j] = text[i];
j++;
}
text[j] = '\0';
}
void setTypeText(wchar_t text[], SkPaint& paint, float LineSpacing,char Alignment) {
const int width = 512;
const int height = 512;
bool flag = true;
SkBitmap bitmap;
SkImageInfo ii = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
bitmap.allocPixels(ii, ii.minRowBytes());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.clear(0x00000000);// 背景为透明色
{
SkRect bounds;
paint.measureText(text, wcslen(text)*2, &bounds);
float y = bounds.fBottom - bounds.fTop;
while (flag) {
int partialBytes = paint.breakText(text, wcslen(text)*2, width);
//SkDebugf("%d/n", partialBytes);
if (partialBytes < wcslen(text)*2) {
SkRect bounds;
paint.measureText(text, partialBytes, &bounds);
float testwidth = bounds.fRight - bounds.fLeft;
switch (Alignment)
{
case 'L':case 'l':
canvas.drawText(text, partialBytes, 0.0f, y, paint);
break;
case 'R':case 'r':
canvas.drawText(text, partialBytes, width - testwidth, y, paint);
break;
case 'C':case 'c':
//paint.setTextAlign(SkPaint::kCenter_Align);
canvas.drawText(text, partialBytes, (width - testwidth) / 2, y, paint);
break;
default:
canvas.drawText(text, partialBytes, 0.0f, y, paint);
}
ConversionText(text, partialBytes, wcslen(text));
SkRect bounds2;
paint.measureText(text, wcslen(text)*2, &bounds2);
y += (bounds2.fBottom - bounds2.fTop) + LineSpacing;
if (y > height) {
flag = false;
}
}
else {
SkRect bounds;
paint.measureText(text, wcslen(text)*2, &bounds);
float testwidth = bounds.fRight - bounds.fLeft;
switch (Alignment)
{
case 'L':case 'l':
canvas.drawText(text, partialBytes, 0.0f, y, paint);
break;
case 'R':case 'r':
canvas.drawText(text, partialBytes, width - testwidth, y, paint);
break;
case 'C':case 'c':
//paint.setTextAlign(SkPaint::kCenter_Align);
canvas.drawText(text, partialBytes, (width - testwidth) / 2, y, paint);
break;
default:
canvas.drawText(text, partialBytes, 0.0f, y, paint);
}
flag = false;
}
}
}
SkFILEWStream stream("D:\\Image\\board.jpg");
SkEncodeImage(&stream, bitmap, SkEncodedImageFormat::kPNG, 100);
}
int main(int argc, _TCHAR* argv[])
{
SkPaint paint;
paint.setTextSize(70.0);
paint.setAntiAlias(true);
paint.setColor(0xff4281A4);
paint.setStyle(SkPaint::kFill_Style);
paint.setTextSkewX(-.3f);
paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
wchar_t text[] = L"这skia的一个小例子,从中可以看到排版的效果,输入的字可以是中英文都有的!相较于上次有所改进";
setTypeText(text, paint, 5.0, 'c');
return 0;
}
运行结果
如果非要实现用char类型来完成排版也可以如下考虑:
现将wchar_t转化为char类型,这个可以使用WideCharToMultiByte,记得加上windows.h头文件,关于怎么使用这个函数可以参考如下
https://msdn.microsoft.com/en-us/library/windows/desktop/dd374130(v=vs.85).aspx
代码中dBuf 就是wchar_t转化后对应的char类型。
下面就是调用skia的相关接口了,还是那个老问题,现在虽然是char型了,但是一句话中有可能既有中文又有英文,标点符号等等中文是2字节,英文是1字节,所以在操作长度是很麻烦,难道还要先判断一下是不是中文?是不是英文?如果一句话中还有其他使用不同字节编码符号呢?这样显然有点麻烦,所以这里考虑使用textToGlyphs这个接口,它是根据符号形状来划分的,才不管字节不字节的,它相当于先将每个符号的轮廓存下来,再调用drawText描绘出其轮廓,官网例子如下:
需要注意的点是这里需要使用SkPaint::kGlyphID_TextEncoding编码,但是在使用breakText等一些处理时需要的是 kUTF8 TextEncoding 编码,如果没有及时切换过来的话,可能出现乱码,例如我第一次就是如下结果,因为处理第一行得文字话之前是没用SkPaint::kGlyphID_TextEncoding的,但后面在使用了
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);所以就乱码了
// HelloSkia.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "SkBitmap.h"
#include "SkDevice.h"
#include "SkPaint.h"
#include "SkRect.h"
#include "SkImageEncoder.h"
#include "SkTypeface.h"
#include "SkCanvas.h"
#include "iostream"
#include <vector>
#include <windows.h>
/*
void drawtext(char text[], size_t byteLength,float testhighth, const SkPaint& paint, SkCanvas canvas,const int width, const int height) {
SkScalar measuredWidth;
int partialBytes = paint.breakText(text, byteLength, width, &measuredWidth);
if (partialBytes < byteLength) {
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
int j = 0;
for (int i = partialBytes; i < byteLength; i++) {
text[j] = text[i];
j++;
}
text[j] = '\0';
testhighth += testhighth;
drawtext(text, strlen(text), testhighth, paint, canvas, width, height);
}
else
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
}
*/
//void ConversionText(wchar_t text[], int partialBytes, int byteLength) {
// int j = 0;
// for (int i = partialBytes/2; i < byteLength; i++) {
// text[j] = text[i];
// j++;
// }
// text[j] = '\0';
//}
void setTypeText(char * text, SkPaint& paint, float LineSpacing,char Alignment) {
const int width = 512;
const int height = 512;
bool flag = true;
SkBitmap bitmap;
SkImageInfo ii = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
bitmap.allocPixels(ii, ii.minRowBytes());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.clear(0x00000000);// 背景为透明色
int partialBytes = paint.breakText(text, strlen(text), width);
SkRect bounds;
paint.measureText(text, partialBytes, &bounds);
float y = bounds.fBottom - bounds.fTop;
float testwidth = bounds.fRight - bounds.fLeft;
{
while (flag) {
std::vector<SkGlyphID> glyphs;
int count = paint.textToGlyphs(text, partialBytes, nullptr);
glyphs.resize(count);
paint.textToGlyphs(text, partialBytes, &glyphs.front());
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
switch (Alignment)
{
case 'L':case 'l':
canvas.drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), 0, y, paint);
break;
case 'R':case 'r':
canvas.drawText(&glyphs.front(),glyphs.size() * sizeof(SkGlyphID), width - testwidth, y, paint);
break;
case 'C':case 'c':
//paint.setTextAlign(SkPaint::kCenter_Align);
canvas.drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), (width - testwidth) / 2, y, paint);
break;
default:
canvas.drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), 0, y, paint);
}
//paint.setTextEncoding(SkPaint::kUTF8_TextEncoding);
if (partialBytes < strlen(text)){
text += partialBytes;
int partialBytes = paint.breakText(text, strlen(text), width);
SkRect bounds;
paint.measureText(text, partialBytes, &bounds);
y+= bounds.fBottom - bounds.fTop;
testwidth = bounds.fRight - bounds.fLeft;
if(y>height){
flag = false;
}
}
else {
flag = false;
}
}
}
SkFILEWStream stream("D:\\Image\\board.jpg");
SkEncodeImage(&stream, bitmap, SkEncodedImageFormat::kPNG, 100);
}
int main(int argc, _TCHAR* argv[])
{
SkPaint paint;
paint.setTextSize(70.0);
paint.setAntiAlias(true);
paint.setColor(0xff4281A4);
paint.setStyle(SkPaint::kFill_Style);
paint.setTextSkewX(-.3f);
//paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
wchar_t text[] = L"这是一个将wchar_t类型转化为char类型后排版的例子!";
DWORD dBufSize = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, FALSE);
char *dBuf = new char[dBufSize];
memset(dBuf, 0, dBufSize);
int nRet = WideCharToMultiByte(CP_UTF8, 0, text, -1, dBuf, dBufSize, NULL, FALSE);
if (nRet <= 0)
{
printf("转换失败\n");
}
///*_bstr_t b(text);
//char* c = b;*/
else {
setTypeText(dBuf, paint, 5.0, 'l');
}
delete[]dBuf;
return 0;
}
运行之后就是这样
此时可能想到的方法就是及时切换编码不就行了?程序哪里需要什么编码就及时设置什么编码,我也是怎么想的,于是将红色的那一行注释去掉,开始运行,很遗憾报错了,具体原因我还不是很清楚,这样直接切换编码可能会造成一些错误,方法总归还是有的,那就是在SkPaint::kGlyphID_TextEncoding之前将所有kUTF8 TextEncoding编码所得事情都处理好,所以这里弄了几个数组,具体如下;
// HelloSkia.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "SkBitmap.h"
#include "SkDevice.h"
#include "SkPaint.h"
#include "SkRect.h"
#include "SkImageEncoder.h"
#include "SkTypeface.h"
#include "SkCanvas.h"
#include "iostream"
#include <vector>
#include <windows.h>
/*
void drawtext(char text[], size_t byteLength,float testhighth, const SkPaint& paint, SkCanvas canvas,const int width, const int height) {
SkScalar measuredWidth;
int partialBytes = paint.breakText(text, byteLength, width, &measuredWidth);
if (partialBytes < byteLength) {
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
int j = 0;
for (int i = partialBytes; i < byteLength; i++) {
text[j] = text[i];
j++;
}
text[j] = '\0';
testhighth += testhighth;
drawtext(text, strlen(text), testhighth, paint, canvas, width, height);
}
else
canvas.drawText(text, partialBytes, 0.0f, testhighth, paint);
}
*/
//void ConversionText(wchar_t text[], int partialBytes, int byteLength) {
// int j = 0;
// for (int i = partialBytes/2; i < byteLength; i++) {
// text[j] = text[i];
// j++;
// }
// text[j] = '\0';
//}
void setTypeText(char * text, SkPaint& paint, float LineSpacing,char Alignment) {
const int width = 512;
const int height = 512;
bool flag = true;
SkBitmap bitmap;
SkImageInfo ii = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType);
bitmap.allocPixels(ii, ii.minRowBytes());
bitmap.allocPixels();
SkCanvas canvas(bitmap);
canvas.clear(0x00000000);// 背景为透明色
int StrNumber=0;
char * text1 = text;
char * text2 = text;
//排版
while (flag) {
int partialBytes = paint.breakText(text1, strlen(text1), width);
if (partialBytes < strlen(text1)) {
text1 += partialBytes;
}
else {
flag = false;
}
StrNumber++;
}
std::vector<std::vector<SkGlyphID> > GlyphsArray(StrNumber);
float *StrWidth = new float[StrNumber];
float *StrHeight = new float[StrNumber];
for (int i = 0; i < StrNumber; i++) {
int partialBytes = paint.breakText(text2, strlen(text2), width);
int count = paint.textToGlyphs(text2, partialBytes, nullptr);
GlyphsArray[i].resize(count);
paint.textToGlyphs(text2, partialBytes, &GlyphsArray[i].front());
SkRect bounds;
paint.measureText(text2, partialBytes, &bounds);
StrWidth[i] = bounds.fRight - bounds.fLeft;
StrHeight[i] = bounds.fBottom - bounds.fTop;
text2 += partialBytes;
}
{
float y = 0;
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < StrNumber; i++) {
y += StrHeight[i];
if (y < height) {
switch (Alignment)
{
case 'L':case 'l':
canvas.drawText(&GlyphsArray[i].front(), GlyphsArray[i].size() * sizeof(SkGlyphID), 0, y, paint);
break;
case 'R':case 'r':
canvas.drawText(&GlyphsArray[i].front(), GlyphsArray[i].size() * sizeof(SkGlyphID), width - StrWidth[i], y, paint);
break;
case 'C':case 'c':
//paint.setTextAlign(SkPaint::kCenter_Align);
canvas.drawText(&GlyphsArray[i].front(), GlyphsArray[i].size() * sizeof(SkGlyphID), (width - StrWidth[i]) / 2, y, paint);
break;
default:
canvas.drawText(&GlyphsArray[i].front(), GlyphsArray[i].size() * sizeof(SkGlyphID), 0, y, paint);
}
}
else {
break;
}
y += LineSpacing;
//break;
}
}
SkFILEWStream stream("D:\\Image\\board.jpg");
SkEncodeImage(&stream, bitmap, SkEncodedImageFormat::kPNG, 100);
delete[] StrWidth;
delete[] StrHeight;
}
int main(int argc, _TCHAR* argv[])
{
SkPaint paint;
paint.setTextSize(70.0);
paint.setAntiAlias(true);
paint.setColor(0xff4281A4);
paint.setStyle(SkPaint::kFill_Style);
paint.setTextSkewX(-.3f);
//paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
wchar_t text[] = L"这是一个将wchar_t类型转化为char类型后排版的例子!";
DWORD dBufSize = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, FALSE);
char *dBuf = new char[dBufSize];
memset(dBuf, 0, dBufSize);
int nRet = WideCharToMultiByte(CP_UTF8, 0, text, -1, dBuf, dBufSize, NULL, FALSE);
if (nRet <= 0)
{
printf("转换失败\n");
}
else {
setTypeText(dBuf, paint, 50.0, 'c');
}
delete[]dBuf;
return 0;
}
运行结果: